diff --git a/Cargo.toml b/Cargo.toml index b9f7a496d0..3758950c13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,15 @@ # https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html#details resolver = "2" members = [ + "examples/abigen", "examples/contracts", "examples/cookbook", + "examples/debugging", "examples/predicates", "examples/providers", "examples/rust_bindings", "examples/types", "examples/wallets", - "examples/debugging", "packages/fuels", "packages/fuels-abigen-macro", "packages/fuels-contract", @@ -22,7 +23,7 @@ members = [ "packages/fuels-test-helpers", "packages/fuels-types", "packages/wasm-tests", - "scripts/test-projects", "scripts/check-docs", + "scripts/test-projects", "tools/fuels-abi-cli", ] \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 268d3a822a..140bebfd27 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -18,12 +18,12 @@ - [Setting up test wallets](./wallets/test-wallets.md) - [Signing](./wallets/signing.md) - [Transferring assets](./wallets/transferring-assets.md) + - [Generating bindings with `abigen!`](./getting-started/abigen.md) + - [The JSON ABI file](abigen/the-json-abi-file.md) + - [The `abigen!` macro](abigen/the-abigen-macro.md) - [Deploying contracts](./getting-started/contracts.md) - [Interacting with contracts](./contracts/interacting-with-contracts.md) - [The FuelVM Binary file](./contracts/the-fuelvm-binary-file.md) - - [The JSON ABI file](./contracts/the-json-abi-file.md) - - [The `abigen!` macro](./contracts/the-abigen-macro.md) - - [The `setup_contract_test!` macro](./contracts/the-setup-contract-test-macro.md) - [Calling contracts](./getting-started/calling-contracts.md) - [Connecting wallets](./calling-contracts/calls-with-different-wallets.md) - [Transaction parameters](./calling-contracts/tx-params.md) @@ -61,6 +61,7 @@ - [The Function selector](./debugging/function-selector.md) - [Testing](./testing/index.md) - [Tweaking the blockchain](./testing/chains.md) + - [The `setup_contract_test!` macro](testing/the-setup-contract-test-macro.md) - [Cookbook](./cookbook/cookbook.md) - [Custom consensus parameters](./cookbook/custom-chain.md) - [Deposit and Withdraw](./cookbook/deposit-and-withdraw.md) diff --git a/docs/src/abigen/the-abigen-macro.md b/docs/src/abigen/the-abigen-macro.md new file mode 100644 index 0000000000..1659660120 --- /dev/null +++ b/docs/src/abigen/the-abigen-macro.md @@ -0,0 +1,96 @@ +# 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/abigen/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 programs that use it. + +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}} +``` \ No newline at end of file diff --git a/docs/src/contracts/the-json-abi-file.md b/docs/src/abigen/the-json-abi-file.md similarity index 100% rename from docs/src/contracts/the-json-abi-file.md rename to docs/src/abigen/the-json-abi-file.md diff --git a/docs/src/contracts/the-abigen-macro.md b/docs/src/contracts/the-abigen-macro.md deleted file mode 100644 index f9f1f897b7..0000000000 --- a/docs/src/contracts/the-abigen-macro.md +++ /dev/null @@ -1,46 +0,0 @@ -# The abigen! macro - -You might have noticed this section in the previous example: - -```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. - -For instance, 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}} -``` - -Can become 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}} -``` - -To generate these bindings, all you have to do is: - -```rust,ignore -{{#include ../../../examples/rust_bindings/src/lib.rs:use_abigen}} -``` - -And this `abigen!` macro will _expand_ the code with the type-safe Rust bindings. It takes two arguments: - -1. The name of the struct that will be generated (`MyContractName`); -2. Either a path as a string to the JSON ABI file or the JSON ABI as a multiline string directly. - -The same as the example above but passing the ABI definition directly: - -```rust,ignore -{{#include ../../../examples/rust_bindings/src/lib.rs:abigen_with_string}} -``` diff --git a/docs/src/contracts/the-setup-contract-test-macro.md b/docs/src/contracts/the-setup-contract-test-macro.md deleted file mode 100644 index 5c978b2b8d..0000000000 --- a/docs/src/contracts/the-setup-contract-test-macro.md +++ /dev/null @@ -1,37 +0,0 @@ -# The setup_contract_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 test only the contract methods and we want to simply deploy the contract with the default configuration parameters. The `setup_contract_test!` macro does exactly that. When expanded, the `setup_contract_test!` macro will: -1. run the `abigen` -2. launch a local provider -3. setup one wallet -4. deploy the selected contract - -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}} -``` - -The input of the macro are the contract instance variable name, wallet variable name and the forc project path. Both the contract instance and wallet variables get brought into context and they can be used further in the code. - ->**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 using a shared wallet. - -If you want to deploy contracts to the same provider, you have to set the wallet name of the first macro to `wallet` and all the remaining wallet names to `None`. The first macro will create `wallet` and bring it into context, and the other macros will use it instead of creating new ones. Let's see it in an example. - -```rust,ignore -{{#include ../../../packages/fuels/tests/contracts.rs:contract_setup_macro_multi}} -``` - -In this example, three contracts are deployed on the same provider using the `wallet`. The second and third macro use the same contract but have different IDs because of the deployment with salt. Both of them can call the first contract by using its 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 ../../../packages/fuels/tests/contracts.rs:contract_setup_macro_manual_wallet}} -``` diff --git a/docs/src/getting-started/abigen.md b/docs/src/getting-started/abigen.md new file mode 100644 index 0000000000..22bf3a8b01 --- /dev/null +++ b/docs/src/getting-started/abigen.md @@ -0,0 +1,12 @@ +# 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. \ No newline at end of file diff --git a/docs/src/getting-started/api.md b/docs/src/getting-started/api.md index f6d5ff6caf..40660240cc 100644 --- a/docs/src/getting-started/api.md +++ b/docs/src/getting-started/api.md @@ -1,3 +1,3 @@ # API -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. +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. diff --git a/docs/src/getting-started/contracts.md b/docs/src/getting-started/contracts.md index 4c0ac16007..6e9f2381ff 100644 --- a/docs/src/getting-started/contracts.md +++ b/docs/src/getting-started/contracts.md @@ -6,7 +6,7 @@ There are two main ways of working with contracts in the SDK: deploying a contra Once you've written a contract in Sway and compiled it with `forc build` (read [here](https://fuellabs.github.io/sway/master/introduction/sway_quickstart.html) for more on how to work with Sway), you'll have in your hands two important artifacts: the compiled binary file and the JSON ABI file. -Below is how you can deploy your contracts using the SDK. For more details about each component in this process, read [The abigen macro](../contracts/the-abigen-macro.md), [The FuelVM binary file](../contracts/the-fuelvm-binary-file.md), and [The JSON ABI file](../contracts/the-json-abi-file.md). +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](../contracts/the-fuelvm-binary-file.md), and [The JSON ABI file](../abigen/the-json-abi-file.md). ### The deploy functions diff --git a/docs/src/getting-started/predicates.md b/docs/src/getting-started/predicates.md index f30242ad28..ad1af6def7 100644 --- a/docs/src/getting-started/predicates.md +++ b/docs/src/getting-started/predicates.md @@ -4,7 +4,7 @@ Predicates, in Sway, are programs that return a Boolean value, and they do not h ## Instantiating predicates -Once you've written a predicate in Sway and compiled it with `forc build`, you can use the `predicate_abigen!` to generate all the types specified in the predicate. Additionally, you will get a `predicate` instance with methods for receiving and spending funds and encoding the predicate data. The code snippet below shows how to use the abigen and generate a predicate instance. +Once you've written a predicate in Sway and compiled it with `forc build`, you can use the `abigen!` to generate all the types specified in the predicate. Additionally, you will get a `predicate` instance with methods for receiving and spending funds and encoding the predicate data. The code snippet below shows how to use the abigen and generate a predicate instance. ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:predicate_load}} diff --git a/docs/src/getting-started/running-scripts.md b/docs/src/getting-started/running-scripts.md index fd92875e3f..7782a22f70 100644 --- a/docs/src/getting-started/running-scripts.md +++ b/docs/src/getting-started/running-scripts.md @@ -1,6 +1,6 @@ # 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 `script_abigen!` macro, which is similar to the `abigen!` macro seen [previously](../contracts/the-abigen-macro.md). +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 ../../../packages/fuels/tests/scripts.rs:script_with_arguments}} diff --git a/docs/src/predicates/predicate-data.md b/docs/src/predicates/predicate-data.md index 974dd48d9e..f885c4773a 100644 --- a/docs/src/predicates/predicate-data.md +++ b/docs/src/predicates/predicate-data.md @@ -6,7 +6,7 @@ Let's consider the following predicate example: {{#include ../../../packages/fuels/tests/predicates/predicate_basic/src/main.sw}} ``` -Similarly to contracts and scripts, the `predicate_abigen!` generates a function that will conveniently encode all the arguments of the main function for us. This function is called `encode_data`, and it is accessed through the predicate instance as shown below: +Similarly to contracts and scripts, the `abigen!` generates a function that will conveniently encode all the arguments of the main function for us. This function is called `encode_data`, and it is accessed through the predicate instance as shown below: ```rust,ignore {{#include ../../../examples/predicates/src/lib.rs:encode_predicate_data}} diff --git a/docs/src/testing/the-setup-contract-test-macro.md b/docs/src/testing/the-setup-contract-test-macro.md new file mode 100644 index 0000000000..f59c2fedcb --- /dev/null +++ b/docs/src/testing/the-setup-contract-test-macro.md @@ -0,0 +1,73 @@ +# The setup_contract_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 test only the contract methods and we want to deploy the contract with the default configuration parameters. The `setup_contract_test!` macro can do exactly that. + +--- + +Used to reduce boilerplate in integration tests. Accepts input in the form +of `COMMAND(ARG...)...` + +`COMMAND` is either `Wallets`, `Abigen` or `Deploy`. + +`ARG` is either a: + +* name-value (e.g. `name="MyContract"`), or, +* a literal (e.g. `"some_str_literal"`, `true`, `5`, ...) + +Available `COMMAND`s: + +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: `Abigen(name="MyContract", abi="some_folder")` + +Description: Generates the contract bindings under the name `name`. `abi` +should point to the folder containing the `out` directory of the forc build. + +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. + + +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 ../../../packages/fuels/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 ../../../packages/fuels/tests/contracts.rs:contract_setup_macro_manual_wallet}} +``` \ No newline at end of file diff --git a/examples/abigen/Cargo.toml b/examples/abigen/Cargo.toml new file mode 100644 index 0000000000..32f1c52280 --- /dev/null +++ b/examples/abigen/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "fuels-example-abigen" +publish = false +version = "0.0.0" +authors = ["Fuel Labs "] +edition = "2021" +homepage = "https://fuel.network/" +license = "Apache-2.0" +repository = "https://github.com/FuelLabs/fuels-rs" +description = "Fuel Rust SDK abigen examples." + +[dev-dependencies] +fuels = { version = "0.33.0", path = "../../packages/fuels" } +tokio = { version = "1.10", features = ["full"] } +anyhow = "1.0.68" diff --git a/examples/abigen/src/lib.rs b/examples/abigen/src/lib.rs new file mode 100644 index 0000000000..73a6313075 --- /dev/null +++ b/examples/abigen/src/lib.rs @@ -0,0 +1,19 @@ +#[cfg(test)] +mod tests { + use anyhow::Result; + use fuels::prelude::*; + + #[tokio::test] + async fn example_of_abigen_usage() -> Result<()> { + // ANCHOR: multiple_abigen_program_types + abigen!( + Contract(name="ContractA", abi="packages/fuels/tests/bindings/sharing_types/contract_a/out/debug/contract_a-abi.json"), + Contract(name="ContractB", abi="packages/fuels/tests/bindings/sharing_types/contract_b/out/debug/contract_b-abi.json"), + Script(name="MyScript", abi="packages/fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments-abi.json"), + Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json"), + ); + // ANCHOR_END: multiple_abigen_program_types + + Ok(()) + } +} diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index e773d1bf91..beffdbf74f 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -26,11 +26,11 @@ mod tests { // 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! - abigen!( - MyContract, + abigen!(Contract( + name = "MyContract", // This path is relative to the workspace (repository) root - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); // This helper will launch a local node and provide a test wallet linked to it let wallet = launch_provider_and_get_wallet().await; @@ -84,9 +84,16 @@ mod tests { // ANCHOR: deploy_contract_setup_macro_short setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let response = contract_instance @@ -105,10 +112,10 @@ mod tests { async fn contract_call_cost_estimation() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; @@ -143,10 +150,10 @@ mod tests { use rand::prelude::{Rng, SeedableRng, StdRng}; // ANCHOR: abigen_example - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); // ANCHOR_END: abigen_example let wallet = launch_provider_and_get_wallet().await; @@ -184,10 +191,10 @@ mod tests { async fn deploy_with_multiple_wallets() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallets = launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await; @@ -238,10 +245,10 @@ mod tests { #[allow(unused_variables)] async fn contract_tx_and_call_params() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let contract_id = Contract::deploy( @@ -314,10 +321,10 @@ mod tests { #[allow(unused_variables)] async fn token_ops_tests() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let contract_id = Contract::deploy( @@ -352,10 +359,10 @@ mod tests { #[allow(unused_variables)] async fn output_messages_test() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let contract_id = Contract::deploy( @@ -393,9 +400,9 @@ mod tests { async fn dependency_estimation() -> Result<(), Error> { use fuels::prelude::*; abigen!( - MyContract, - "packages/fuels/tests/contracts/lib_contract_caller/out/debug/lib_contract_caller-abi.json" - ); + Contract(name="MyContract", + abi="packages/fuels/tests/contracts/lib_contract_caller/out/debug/lib_contract_caller-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let called_contract_id: ContractId = Contract::deploy( @@ -463,71 +470,77 @@ mod tests { async fn get_contract_outputs() -> Result<(), Error> { use fuels::prelude::*; use fuels::tx::Receipt; - abigen!( - TestContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); - let wallet = launch_provider_and_get_wallet().await; - let contract_id = Contract::deploy( - "../../packages/fuels/tests/contracts/contract_test/out/debug/contract_test\ + { + abigen!(Contract( + name = "TestContract", + abi = + "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); + let wallet = launch_provider_and_get_wallet().await; + let contract_id = Contract::deploy( + "../../packages/fuels/tests/contracts/contract_test/out/debug/contract_test\ .bin", - &wallet, - TxParameters::default(), - StorageConfiguration::default(), - ) - .await?; - let contract_methods = TestContract::new(contract_id, wallet).methods(); - - let response = contract_methods.increment_counter(162).call().await?; - let response = contract_methods.increment_counter(162).call().await; - match response { - // The transaction is valid and executes to completion - Ok(call_response) => { - let receipts: Vec = call_response.receipts; - // Do things with logs and receipts - } - // The transaction is malformed - Err(Error::ValidationError(e)) => { - println!("Transaction is malformed (ValidationError): {}", e); - } - // Failed request to provider - Err(Error::ProviderError(reason)) => { - println!("Provider request failed with reason: {}", reason); - } - // The transaction is valid but reverts - Err(Error::RevertTransactionError(reason, receipts)) => { - println!("ContractCall failed with reason: {}", reason); - println!("Transaction receipts are: {:?}", receipts); + &wallet, + TxParameters::default(), + StorageConfiguration::default(), + ) + .await?; + let contract_methods = TestContract::new(contract_id, wallet).methods(); + + let response = contract_methods.increment_counter(162).call().await?; + let response = contract_methods.increment_counter(162).call().await; + match response { + // The transaction is valid and executes to completion + Ok(call_response) => { + let receipts: Vec = call_response.receipts; + // Do things with logs and receipts + } + // The transaction is malformed + Err(Error::ValidationError(e)) => { + println!("Transaction is malformed (ValidationError): {}", e); + } + // Failed request to provider + Err(Error::ProviderError(reason)) => { + println!("Provider request failed with reason: {}", reason); + } + // The transaction is valid but reverts + Err(Error::RevertTransactionError(reason, receipts)) => { + println!("ContractCall failed with reason: {}", reason); + println!("Transaction receipts are: {:?}", receipts); + } + Err(_) => {} } - Err(_) => {} } - // ANCHOR: deployed_contracts - // Replace with your contract ABI.json path - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); - let wallet_original = launch_provider_and_get_wallet().await; - - let wallet = wallet_original.clone(); - // Your bech32m encoded contract ID. - let contract_id: Bech32ContractId = - "fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy" - .parse() - .expect("Invalid ID"); - - let connected_contract_instance = MyContract::new(contract_id, wallet); - // You can now use the `connected_contract_instance` just as you did above! - // ANCHOR_END: deployed_contracts - - let wallet = wallet_original; - // ANCHOR: deployed_contracts_hex - let contract_id: ContractId = - "0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3" - .parse() - .expect("Invalid ID"); - let connected_contract_instance = MyContract::new(contract_id.into(), wallet); - // ANCHOR_END: deployed_contracts_hex + { + // ANCHOR: deployed_contracts + abigen!(Contract( + name = "MyContract", + // Replace with your contract ABI.json path + abi = + "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); + let wallet_original = launch_provider_and_get_wallet().await; + + let wallet = wallet_original.clone(); + // Your bech32m encoded contract ID. + let contract_id: Bech32ContractId = + "fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy" + .parse() + .expect("Invalid ID"); + + let connected_contract_instance = MyContract::new(contract_id, wallet); + // You can now use the `connected_contract_instance` just as you did above! + // ANCHOR_END: deployed_contracts + + let wallet = wallet_original; + // ANCHOR: deployed_contracts_hex + let contract_id: ContractId = + "0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3" + .parse() + .expect("Invalid ID"); + let connected_contract_instance = MyContract::new(contract_id.into(), wallet); + // ANCHOR_END: deployed_contracts_hex + } Ok(()) } @@ -536,10 +549,10 @@ mod tests { #[allow(unused_variables)] async fn call_params_gas() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; @@ -575,10 +588,10 @@ mod tests { async fn multi_call_example() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; @@ -624,10 +637,10 @@ mod tests { async fn multi_call_cost_estimation() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; @@ -666,10 +679,10 @@ mod tests { #[allow(unused_variables)] async fn connect_wallet() -> Result<(), Error> { use fuels::prelude::*; - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/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; diff --git a/examples/cookbook/src/lib.rs b/examples/cookbook/src/lib.rs index ae25cd0e54..19bc0e892e 100644 --- a/examples/cookbook/src/lib.rs +++ b/examples/cookbook/src/lib.rs @@ -8,10 +8,10 @@ mod tests { use fuels::test_helpers::{AssetConfig, WalletsConfig}; // ANCHOR: liquidity_abigen - abigen!( - MyContract, - "packages/fuels/tests/contracts/liquidity_pool/out/debug/liquidity_pool-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/liquidity_pool/out/debug/liquidity_pool-abi.json" + )); // ANCHOR_END: liquidity_abigen // ANCHOR: liquidity_wallet @@ -181,10 +181,10 @@ mod tests { // ANCHOR_END: modify_call_inputs_include // ANCHOR: modify_call_inputs_setup - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let some_asset_id = AssetId::new([3; 32usize]); let coin_amount = 1_000_000; @@ -244,6 +244,7 @@ mod tests { let balance_2 = wallet_2.get_asset_balance(&some_asset_id).await?; assert_eq!(balance_2, coin_amount + SEND_AMOUNT); // ANCHOR_END: modify_call_inputs_verify + Ok(()) } } diff --git a/examples/predicates/src/lib.rs b/examples/predicates/src/lib.rs index 9470bdee3d..7ce49f6237 100644 --- a/examples/predicates/src/lib.rs +++ b/examples/predicates/src/lib.rs @@ -49,10 +49,7 @@ mod tests { // ANCHOR_END: predicate_coins // ANCHOR: predicate_load - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_signatures/out/debug/predicate_signatures-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_signatures/out/debug/predicate_signatures-abi.json")); let predicate = MyPredicate::load_from( "../../packages/fuels/tests/predicates/predicate_signatures/out/debug/predicate_signatures.bin", @@ -134,10 +131,7 @@ mod tests { let first_wallet = &wallets[0]; let second_wallet = &wallets[1]; - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json")); let predicate = MyPredicate::load_from( "../../packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic.bin", diff --git a/examples/rust_bindings/src/lib.rs b/examples/rust_bindings/src/lib.rs index 51ef8f41ba..a20faa82a2 100644 --- a/examples/rust_bindings/src/lib.rs +++ b/examples/rust_bindings/src/lib.rs @@ -9,17 +9,23 @@ mod tests { async fn transform_json_to_bindings() -> Result<(), Error> { 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!(MyContractName, "examples/rust_bindings/src/abi.json"); - // ANCHOR_END: use_abigen + { + // 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 - // Don't forget to import the `abigen` macro as above - abigen!( - MyContract, - r#" + { + // ANCHOR: abigen_with_string + use fuels::prelude::*; + abigen!(Contract( + name = "MyContract", + abi = r#" { "types": [ { @@ -63,8 +69,9 @@ mod tests { ] } "# - ); - // ANCHOR_END: abigen_with_string + )); + // ANCHOR_END: abigen_with_string + } Ok(()) } diff --git a/examples/rust_bindings/src/rust_bindings_formatted.rs b/examples/rust_bindings/src/rust_bindings_formatted.rs index 578762590f..248cddc151 100644 --- a/examples/rust_bindings/src/rust_bindings_formatted.rs +++ b/examples/rust_bindings/src/rust_bindings_formatted.rs @@ -1,37 +1,91 @@ -pub struct MyContract { - contract_id: ContractId, - wallet: WalletUnlocked, -} -impl MyContract { - pub fn new(contract_id: String, wallet: WalletUnlocked) -> Self { - let contract_id = ContractId::from_str(&contract_id).expect("Invalid contract id"); - Self { - contract_id, - wallet, +pub mod abigen_bindings { + pub mod my_contract_name_mod { + pub struct MyContractName { + contract_id: Bech32ContractId, + wallet: WalletUnlocked, + } + impl MyContractName { + pub fn new(contract_id: Bech32ContractId, wallet: WalletUnlocked) -> Self { + Self { + contract_id, + wallet, + } + } + pub fn get_contract_id(&self) -> &Bech32ContractId { + &self.contract_id + } + pub fn get_wallet(&self) -> WalletUnlocked { + self.wallet.clone() + } + pub fn with_wallet(&self, mut wallet: WalletUnlocked) -> Result { + let provider = self.wallet.get_provider()?; + wallet.set_provider(provider.clone()); + Ok(Self { + contract_id: self.contract_id.clone(), + wallet, + }) + } + pub async fn get_balances(&self) -> Result, Error> { + self.wallet + .get_provider()? + .get_contract_balances(&self.contract_id) + .await + .map_err(Into::into) + } + pub fn methods(&self) -> MyContractNameMethods { + MyContractNameMethods { + contract_id: self.contract_id.clone(), + wallet: self.wallet.clone(), + logs_map: get_logs_hashmap(&[], Some(self.contract_id.clone())), + } + } + } + pub struct MyContractNameMethods { + contract_id: Bech32ContractId, + wallet: WalletUnlocked, + logs_map: HashMap<(Bech32ContractId, u64), ParamType>, + } + impl MyContractNameMethods { + #[doc = "Calls the contract's `initialize_counter` function"] + pub fn initialize_counter(&self, value: u64) -> ContractCallHandler { + let provider = self.wallet.get_provider().expect("Provider not set up"); + let log_decoder = LogDecoder { + logs_map: self.logs_map.clone(), + }; + Contract::method_hash( + provider, + self.contract_id.clone(), + &self.wallet, + resolve_fn_selector( + "initialize_counter", + &[::param_type()], + ), + &[Tokenizable::into_token(value)], + log_decoder, + ) + .expect("method not found (this should never happen)") + } + #[doc = "Calls the contract's `increment_counter` function"] + pub fn increment_counter(&self, value: u64) -> ContractCallHandler { + let provider = self.wallet.get_provider().expect("Provider not set up"); + let log_decoder = LogDecoder { + logs_map: self.logs_map.clone(), + }; + Contract::method_hash( + provider, + self.contract_id.clone(), + &self.wallet, + resolve_fn_selector( + "increment_counter", + &[::param_type()], + ), + &[Tokenizable::into_token(value)], + log_decoder, + ) + .expect("method not found (this should never happen)") + } } - } - #[doc = "Calls the contract's `initialize_counter` (0x00000000ab64e5f2) function"] - pub fn initialize_counter(&self, arg: u64) -> ContractCallHandler { - Contract::method_hash( - &self.wallet.get_provider().expect("Provider not set up"), - self.contract_id, - &self.wallet, - [0, 0, 0, 0, 171, 100, 229, 242], - &[ParamType::U64], - &[arg.into_token()], - ) - .expect("method not found (this should never happen)") - } - #[doc = "Calls the contract's `increment_counter` (0x00000000faf90dd3) function"] - pub fn increment_counter(&self, arg: u64) -> ContractCallHandler { - Contract::method_hash( - &self.wallet.get_provider().expect("Provider not set up"), - self.contract_id, - &self.wallet, - [0, 0, 0, 0, 250, 249, 13, 211], - &[ParamType::U64], - &[arg.into_token()], - ) - .expect("method not found (this should never happen)") } } +pub use abigen_bindings::my_contract_name_mod::MyContractName; +pub use abigen_bindings::my_contract_name_mod::MyContractNameMethods; diff --git a/packages/fuels-abigen-macro/.gitignore b/packages/fuels-abigen-macro/.gitignore new file mode 100644 index 0000000000..b9b0ca2a32 --- /dev/null +++ b/packages/fuels-abigen-macro/.gitignore @@ -0,0 +1 @@ +wip/* \ No newline at end of file diff --git a/packages/fuels-abigen-macro/Cargo.toml b/packages/fuels-abigen-macro/Cargo.toml index d6077b6056..f337f88d52 100644 --- a/packages/fuels-abigen-macro/Cargo.toml +++ b/packages/fuels-abigen-macro/Cargo.toml @@ -18,4 +18,8 @@ proc-macro2 = "1.0" quote = "1.0" rand = "0.8" syn = "1.0.12" +itertools = "0.10.5" + +[dev-dependencies] +trybuild = "1.0.73" diff --git a/packages/fuels-abigen-macro/src/abigen_macro.rs b/packages/fuels-abigen-macro/src/abigen_macro.rs new file mode 100644 index 0000000000..af0d4a1a3d --- /dev/null +++ b/packages/fuels-abigen-macro/src/abigen_macro.rs @@ -0,0 +1,3 @@ +mod parsing; + +pub(crate) use parsing::MacroAbigenTargets; diff --git a/packages/fuels-abigen-macro/src/abigen_macro/parsing.rs b/packages/fuels-abigen-macro/src/abigen_macro/parsing.rs new file mode 100644 index 0000000000..8a379b4ea9 --- /dev/null +++ b/packages/fuels-abigen-macro/src/abigen_macro/parsing.rs @@ -0,0 +1,78 @@ +use proc_macro2::Ident; +use syn::{ + parse::{Parse, ParseStream}, + Error, Result as ParseResult, +}; + +use fuels_core::code_gen::abigen::{AbigenTarget, ProgramType}; + +use crate::parse_utils::{Command, UniqueNameValues}; + +impl From for Vec { + fn from(targets: MacroAbigenTargets) -> Self { + targets.targets.into_iter().map(Into::into).collect() + } +} + +impl From for AbigenTarget { + fn from(macro_target: MacroAbigenTarget) -> Self { + AbigenTarget { + name: macro_target.name, + abi: macro_target.abi, + program_type: macro_target.program_type, + } + } +} + +// Although identical to `AbigenTarget` from fuels-core, due to the orphan rule +// we cannot implement Parse for the latter. +struct MacroAbigenTarget { + name: String, + abi: String, + program_type: ProgramType, +} + +pub(crate) struct MacroAbigenTargets { + targets: Vec, +} + +impl Parse for MacroAbigenTargets { + fn parse(input: ParseStream) -> ParseResult { + let targets = Command::parse_multiple(input)? + .into_iter() + .map(MacroAbigenTarget::new) + .collect::, _>>()?; + + Ok(Self { targets }) + } +} + +impl MacroAbigenTarget { + pub fn new(command: Command) -> syn::Result { + let program_type = Self::parse_program_type(command.name)?; + + let name_values = UniqueNameValues::new(command.contents)?; + name_values.validate_has_no_other_names(&["name", "abi"])?; + + let name = name_values.get_as_lit_str("name")?.value(); + let abi = name_values.get_as_lit_str("abi")?.value(); + + Ok(Self { + name, + abi, + program_type, + }) + } + + fn parse_program_type(ident: Ident) -> ParseResult { + match ident.to_string().as_ref() { + "Contract" => Ok(ProgramType::Contract), + "Script" => Ok(ProgramType::Script), + "Predicate" => Ok(ProgramType::Predicate), + _ => Err(Error::new_spanned( + ident, + "Unsupported program type. Expected: 'Contract', 'Script' or 'Predicate'", + )), + } + } +} diff --git a/packages/fuels-abigen-macro/src/lib.rs b/packages/fuels-abigen-macro/src/lib.rs index 4241c3e423..3dcddd37fa 100644 --- a/packages/fuels-abigen-macro/src/lib.rs +++ b/packages/fuels-abigen-macro/src/lib.rs @@ -1,254 +1,79 @@ -use fuels_core::code_gen::abigen::{Abigen, AbigenType}; -use inflector::Inflector; +extern crate core; + use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::quote; -use rand::prelude::{Rng, SeedableRng, StdRng}; -use std::{ops::Deref, path::Path}; -use syn::{ - parse::{Parse, ParseStream, Result as ParseResult}, - parse_macro_input, Ident, LitStr, Token, -}; -/// Abigen proc macro definition and helper functions/types. +use syn::parse_macro_input; + +use fuels_core::code_gen::abigen::Abigen; + +use crate::abigen_macro::MacroAbigenTargets; +use crate::setup_contract_test_macro::{generate_setup_contract_test_code, TestContractCommands}; + +mod abigen_macro; +mod parse_utils; +mod setup_contract_test_macro; + +/// Used to generate bindings for Contracts, Scripts and Predicates. Accepts +/// input in the form of `ProgramType(name="MyBindings", abi=ABI_SOURCE)...` +/// +/// `ProgramType` is either `Contract`, `Script` or `Predicate`. +/// +/// `ABI_SOURCE` is a string literal representing either a path to the JSON ABI +/// file or the contents of the JSON ABI file itself. +/// +///```text +/// abigen!(Contract( +/// name = "MyContract", +/// abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" +/// )); +///``` +/// +/// More details can be found in the [`Fuel Rust SDK Book`](https://fuellabs.github.io/fuels-rs/latest) #[proc_macro] pub fn abigen(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as Spanned); - - Abigen::new(&args.name, &args.abi, AbigenType::Contract) - .unwrap() - .expand() - .unwrap() - .into() -} + let targets = parse_macro_input!(input as MacroAbigenTargets); -/// Abigen proc macro definition and helper functions/types for scripts -#[proc_macro] -pub fn script_abigen(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as Spanned); - - Abigen::new(&args.name, &args.abi, AbigenType::Script) - .unwrap() - .expand() - .unwrap() - .into() -} - -/// Abigen proc macro definition and helper functions/types for scripts -#[proc_macro] -pub fn predicate_abigen(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as Spanned); - - Abigen::new(&args.name, &args.abi, AbigenType::Predicate) - .unwrap() - .expand() - .unwrap() - .into() + Abigen::generate(targets.into(), false).unwrap().into() } #[proc_macro] pub fn wasm_abigen(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as Spanned); + let targets = parse_macro_input!(input as MacroAbigenTargets); - Abigen::new(&args.name, &args.abi, AbigenType::Contract) - .unwrap() - .no_std() - .expand() - .unwrap() - .into() + Abigen::generate(targets.into(), true).unwrap().into() } -/// This proc macro is used to reduce the amount of boilerplate code in integration tests. -/// When expanded, the proc macro will: launch a local provider, generate one wallet, -/// deploy the selected contract and create a contract instance. -/// The names for the contract instance and wallet variables must be provided as inputs. -/// This macro can be called multiple times inside a function if the variables names are changed. -/// The same contract can be deployed multiple times as the macro uses deployment with salt. -/// However, if you need to have a shared wallet between macros, the first macro must set the -/// wallet name to `wallet`. The other ones must set the wallet name to `None`. +/// Used to reduce boilerplate in integration tests. +/// +/// More details can be found in the [`Fuel Rust SDK Book`](https://fuellabs.github.io/fuels-rs/latest) +///```text +///setup_contract_test!( +/// Wallets("wallet"), +/// Abigen( +/// name = "FooContract", +/// abi = "packages/fuels/tests/contracts/foo_contract" +/// ), +/// Abigen( +/// name = "FooCallerContract", +/// abi = "packages/fuels/tests/contracts/foo_caller_contract" +/// ), +/// Deploy( +/// name = "foo_contract_instance", +/// contract = "FooContract", +/// wallet = "wallet" +/// ), +/// Deploy( +/// name = "foo_caller_contract_instance", +/// contract = "FooCallerContract", +/// wallet = "my_own_wallet" +/// ), +///); +///``` #[proc_macro] pub fn setup_contract_test(input: TokenStream) -> TokenStream { - let args = parse_macro_input!(input as Spanned); - - let abs_forc_dir = Path::new(&args.project_path) - .canonicalize() - .unwrap_or_else(|_| { - panic!( - "Unable to canonicalize forc project path: {}. Make sure the path is valid!", - &args.project_path - ) - }); - - let forc_project_name = abs_forc_dir - .file_name() - .expect("failed to get project name") - .to_str() - .expect("failed to convert project name to string"); - - let compiled_file_path = |suffix: &str, desc: &str| { - abs_forc_dir - .join(["out/debug/", forc_project_name, suffix].concat()) - .to_str() - .unwrap_or_else(|| panic!("could not join path for {desc}")) - .to_string() - }; - - let abi_path = compiled_file_path("-abi.json", "the ABI file"); - let bin_path = compiled_file_path(".bin", "the binary file"); - let storage_path = compiled_file_path("-storage_slots.json", "the storage slots file"); - - let contract_struct_name = args.instance_name.to_class_case(); - let mut abigen_token_stream: TokenStream = - Abigen::new(&contract_struct_name, abi_path, AbigenType::Contract) - .unwrap() - .expand() - .unwrap() - .into(); - - // Generate random salt for contract deployment - let mut rng = StdRng::from_entropy(); - let salt: [u8; 32] = rng.gen(); - - let contract_instance_name = Ident::new(&args.instance_name, Span::call_site()); - let contract_struct_name = Ident::new(&contract_struct_name, Span::call_site()); - - // If the wallet name is None, do not launch a new provider and use the default `wallet` name - let (wallet_name, wallet_token_stream): (Ident, TokenStream) = if args.wallet_name == "None" { - (Ident::new("wallet", Span::call_site()), quote! {}.into()) - } else { - let wallet_name = Ident::new(&args.wallet_name, Span::call_site()); - ( - wallet_name.clone(), - quote! {let #wallet_name = launch_provider_and_get_wallet().await;}.into(), - ) - }; - - let contract_deploy_token_stream: TokenStream = quote! { - let #contract_instance_name = #contract_struct_name::new( - Contract::deploy_with_parameters( - #bin_path, - &#wallet_name, - TxParameters::default(), - StorageConfiguration::with_storage_path(Some( - #storage_path.to_string(), - )), - Salt::from([#(#salt),*]), - ) - .await - .expect("Failed to deploy the contract"), - #wallet_name.clone(), - ); - } - .into(); - - abigen_token_stream.extend(wallet_token_stream); - abigen_token_stream.extend(contract_deploy_token_stream); - abigen_token_stream -} - -/// Trait that abstracts functionality for inner data that can be parsed and -/// wrapped with a specific `Span`. -trait ParseInner: Sized { - fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)>; -} - -impl ParseInner for T { - fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> { - Ok((input.span(), T::parse(input)?)) - } -} - -impl Parse for Spanned { - fn parse(input: ParseStream) -> ParseResult { - let (span, value) = T::spanned_parse(input)?; - Ok(Spanned(span, value)) - } -} - -/// A struct that captures `Span` information for inner parsable data. -#[cfg_attr(test, derive(Clone, Debug))] -struct Spanned(Span, T); - -impl Spanned { - /// Retrieves the captured `Span` information for the parsed data. - #[allow(dead_code)] - pub fn span(&self) -> Span { - self.0 - } + let test_contract_commands = parse_macro_input!(input as TestContractCommands); - /// Retrieves the inner data. - #[allow(dead_code)] - pub fn into_inner(self) -> T { - self.1 - } -} - -impl Deref for Spanned { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.1 - } -} - -/// Contract procedural macro arguments. -#[cfg_attr(test, derive(Debug, Eq, PartialEq))] -pub(crate) struct AbigenArgs { - name: String, - abi: String, -} - -impl ParseInner for AbigenArgs { - fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> { - // read the contract name - let name = input.parse::()?.to_string(); - - // skip the comma - input.parse::()?; - - let (span, abi) = { - let literal = input.parse::()?; - (literal.span(), literal.value()) - }; - if !input.is_empty() { - input.parse::()?; - } - - Ok((span, AbigenArgs { name, abi })) - } -} - -/// Contract procedural macro arguments. -#[cfg_attr(test, derive(Debug, Eq, PartialEq))] -pub(crate) struct ContractTestArgs { - instance_name: String, - wallet_name: String, - project_path: String, -} - -impl ParseInner for ContractTestArgs { - fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> { - let instance_name = input.parse::()?.to_string(); - input.parse::()?; - - let wallet_name = input.parse::()?.to_string(); - input.parse::()?; - - let (span, project_path) = { - let literal = input.parse::()?; - (literal.span(), literal.value()) - }; - if !input.is_empty() { - input.parse::()?; - } - - Ok(( - span, - ContractTestArgs { - instance_name, - wallet_name, - project_path, - }, - )) - } + generate_setup_contract_test_code(test_contract_commands) + .unwrap_or_else(|e| e.to_compile_error()) + .into() } diff --git a/packages/fuels-abigen-macro/src/parse_utils.rs b/packages/fuels-abigen-macro/src/parse_utils.rs new file mode 100644 index 0000000000..4a5ae8a9ff --- /dev/null +++ b/packages/fuels-abigen-macro/src/parse_utils.rs @@ -0,0 +1,83 @@ +use itertools::{chain, Itertools}; +use quote::ToTokens; +use syn::Error; + +pub(crate) use command::Command; +pub(crate) use unique_lit_strs::UniqueLitStrs; +pub(crate) use unique_name_values::UniqueNameValues; + +mod command; +mod unique_lit_strs; +mod unique_name_values; + +pub(crate) trait ErrorsExt: Iterator + Sized { + fn combine_errors(self) -> Option; + fn validate_no_errors(self) -> Result<(), Self::Item>; +} + +impl ErrorsExt for T +where + T: Iterator + Sized, +{ + fn combine_errors(self) -> Option { + self.reduce(|mut errors, error| { + errors.combine(error); + errors + }) + } + + fn validate_no_errors(self) -> Result<(), Self::Item> { + if let Some(err) = self.combine_errors() { + Err(err) + } else { + Ok(()) + } + } +} + +fn generate_duplicate_error(duplicates: &[&T]) -> Error +where + T: ToTokens, +{ + let mut iter = duplicates.iter(); + + let original_error = iter + .next() + .map(|first_el| Error::new_spanned(first_el, "Original defined here:")); + + let the_rest = iter.map(|duplicate| Error::new_spanned(duplicate, "Duplicate!")); + + chain!(original_error, the_rest) + .combine_errors() + .expect("Has to be at least one error!") +} + +fn group_up_duplicates(name_values: &[T], key: KeyFn) -> Vec> +where + KeyFn: Fn(&&T) -> K, + K: Ord, +{ + name_values + .iter() + .sorted_by_key(&key) + .group_by(&key) + .into_iter() + .filter_map(|(_, group)| { + let group = group.collect::>(); + + (group.len() > 1).then_some(group) + }) + .collect() +} + +fn validate_no_duplicates(elements: &[T], key_fn: KeyFn) -> syn::Result<()> +where + KeyFn: Fn(&&T) -> K + Copy, + T: ToTokens, + K: Ord, +{ + group_up_duplicates(elements, key_fn) + .into_iter() + .map(|duplicates| generate_duplicate_error(&duplicates)) + .validate_no_errors() +} diff --git a/packages/fuels-abigen-macro/src/parse_utils/command.rs b/packages/fuels-abigen-macro/src/parse_utils/command.rs new file mode 100644 index 0000000000..22fe93057d --- /dev/null +++ b/packages/fuels-abigen-macro/src/parse_utils/command.rs @@ -0,0 +1,77 @@ +use proc_macro2::Ident; +use syn::{ + parse::ParseStream, parse_macro_input::ParseMacroInput, punctuated::Punctuated, AttributeArgs, + Error, Meta::List, MetaList, NestedMeta, NestedMeta::Meta, +}; + +#[derive(Debug)] +pub struct Command { + pub name: Ident, + pub contents: Punctuated, +} + +impl Command { + pub fn parse_multiple(input: ParseStream) -> syn::Result> { + AttributeArgs::parse(input)? + .into_iter() + .map(Command::new) + .collect() + } + + pub fn new(nested_meta: NestedMeta) -> syn::Result { + if let Meta(List(MetaList { path, nested, .. })) = nested_meta { + let name = path.get_ident().cloned().ok_or_else(|| { + Error::new_spanned(path, "Command name cannot be a Path -- i.e. contain ':'.") + })?; + Ok(Self { + name, + contents: nested, + }) + } else { + Err(Error::new_spanned( + nested_meta, + "Expected a command name literal -- e.g. `Something(...)`", + )) + } + } + + #[cfg(test)] + pub(crate) fn parse_multiple_from_token_stream( + stream: proc_macro2::TokenStream, + ) -> syn::Result> { + syn::parse::Parser::parse2(Command::parse_multiple, stream) + } + #[cfg(test)] + pub(crate) fn parse_single_from_token_stream( + stream: proc_macro2::TokenStream, + ) -> syn::Result { + syn::parse::Parser::parse2(Command::parse_multiple, stream.clone())? + .pop() + .ok_or_else(|| Error::new_spanned(stream, "Expected to have at least one command!")) + } +} +#[cfg(test)] +mod tests { + use quote::quote; + + use crate::parse_utils::command::Command; + + #[test] + fn command_name_is_properly_extracted() -> syn::Result<()> { + // given + let macro_contents = quote! {SomeCommand(), OtherCommand()}; + + // when + let commands = Command::parse_multiple_from_token_stream(macro_contents)?; + + // then + let command_names = commands + .into_iter() + .map(|command| command.name.to_string()) + .collect::>(); + + assert_eq!(command_names, vec!["SomeCommand", "OtherCommand"]); + + Ok(()) + } +} diff --git a/packages/fuels-abigen-macro/src/parse_utils/unique_lit_strs.rs b/packages/fuels-abigen-macro/src/parse_utils/unique_lit_strs.rs new file mode 100644 index 0000000000..7643ca75a5 --- /dev/null +++ b/packages/fuels-abigen-macro/src/parse_utils/unique_lit_strs.rs @@ -0,0 +1,112 @@ +use crate::parse_utils; +use itertools::{chain, Itertools}; +use parse_utils::{validate_no_duplicates, ErrorsExt}; +use proc_macro2::Span; +use quote::ToTokens; +use std::vec::IntoIter; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::{Error, Lit, LitStr, NestedMeta}; + +#[derive(Debug)] +pub struct UniqueLitStrs { + span: Span, + lit_strs: Vec, +} + +impl UniqueLitStrs { + pub fn new(nested_metas: Punctuated) -> Result { + let span = nested_metas.span(); + + let (lit_strs, errors): (Vec<_>, Vec<_>) = nested_metas + .into_iter() + .map(|meta| { + if let NestedMeta::Lit(Lit::Str(lit_str)) = meta { + Ok(lit_str) + } else { + Err(Error::new_spanned(meta, "Expected a string!")) + } + }) + .partition_result(); + + let maybe_error = validate_no_duplicates(&lit_strs, |e| e.value()).err(); + + chain!(errors, maybe_error).validate_no_errors()?; + + Ok(Self { span, lit_strs }) + } + + #[allow(dead_code)] + pub fn iter(&self) -> impl Iterator { + self.lit_strs.iter() + } + + #[allow(dead_code)] + pub fn span(&self) -> Span { + self.span + } +} + +impl IntoIterator for UniqueLitStrs { + type Item = LitStr; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.lit_strs.into_iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse_utils::Command; + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn correctly_reads_lit_strs() -> syn::Result<()> { + // given + let stream = quote! {SomeCommand("lit1", "lit2")}; + + // when + let unique_lit_strs = parse_unique_lit_strs(stream)?; + + // then + let stringified = unique_lit_strs + .iter() + .map(|lit_str| lit_str.value()) + .collect::>(); + + assert_eq!(stringified, vec!["lit1", "lit2"]); + + Ok(()) + } + + #[test] + fn doesnt_allow_duplicates() { + // given + let stream = quote! {SomeCommand("lit1", "lit2", "lit1")}; + + // when + let err = parse_unique_lit_strs(stream).expect_err("Should have failed"); + + // then + let messages = err.into_iter().map(|e| e.to_string()).collect::>(); + assert_eq!(messages, vec!["Original defined here:", "Duplicate!"]); + } + + #[test] + fn only_strings_allowed() { + let stream = quote! {SomeCommand("lit1", "lit2", true)}; + + let err = parse_unique_lit_strs(stream).expect_err("Should have failed"); + + assert_eq!(err.to_string(), "Expected a string!"); + } + + fn parse_unique_lit_strs(stream: TokenStream) -> syn::Result { + let command = Command::parse_single_from_token_stream(stream)?; + + UniqueLitStrs::new(command.contents) + } +} diff --git a/packages/fuels-abigen-macro/src/parse_utils/unique_name_values.rs b/packages/fuels-abigen-macro/src/parse_utils/unique_name_values.rs new file mode 100644 index 0000000000..f402925f6d --- /dev/null +++ b/packages/fuels-abigen-macro/src/parse_utils/unique_name_values.rs @@ -0,0 +1,252 @@ +use std::collections::HashMap; + +use itertools::{chain, Itertools}; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::Meta::NameValue; +use syn::NestedMeta::Meta; +use syn::{Error, Lit, LitStr, MetaNameValue, NestedMeta}; + +use fuels_core::utils::ident; + +use crate::parse_utils; +use crate::parse_utils::ErrorsExt; + +#[derive(Debug)] +pub struct UniqueNameValues { + span: Span, + name_values: HashMap, +} + +impl UniqueNameValues { + pub fn new(nested_metas: Punctuated) -> syn::Result { + let span = nested_metas.span(); + let name_values = Self::extract_name_values(nested_metas.into_iter())?; + + let names = name_values.iter().map(|(name, _)| name).collect::>(); + parse_utils::validate_no_duplicates(&names, |&&name| name.clone())?; + + Ok(Self { + span, + name_values: name_values.into_iter().collect(), + }) + } + + pub fn try_get(&self, name: &str) -> Option<&Lit> { + self.name_values.get(&ident(name)) + } + + pub fn validate_has_no_other_names(&self, allowed_names: &[&str]) -> syn::Result<()> { + let expected_names = allowed_names + .iter() + .map(|name| format!("'{name}'")) + .join(", "); + + self.name_values + .keys() + .filter(|name| !allowed_names.contains(&name.to_string().as_str())) + .map(|name| { + Error::new_spanned( + name.clone(), + format!( + "Attribute '{name}' not recognized! Expected one of: {expected_names}." + ), + ) + }) + .validate_no_errors() + } + + pub fn get_as_lit_str(&self, name: &str) -> syn::Result<&LitStr> { + let value = self + .try_get(name) + .ok_or_else(|| Error::new(self.span, format!("Missing attribute '{name}'.")))?; + + if let Lit::Str(lit_str) = value { + Ok(lit_str) + } else { + Err(Error::new_spanned( + value.clone(), + format!("Expected the attribute '{name}' to have a string value!"), + )) + } + } + + fn extract_name_values>( + nested_metas: T, + ) -> syn::Result> { + let (name_values, name_value_errs): (Vec<_>, Vec<_>) = nested_metas + .map(Self::extract_name_value) + .partition_result(); + + let (ident_values, name_format_errors): (Vec<_>, Vec) = name_values + .into_iter() + .map(|nv| { + let ident = nv.path.get_ident().cloned().ok_or_else(|| { + Error::new_spanned( + nv.path, + "Attribute name cannot be a `Path` -- i.e. must not contain ':'.", + ) + })?; + + Ok((ident, nv.lit)) + }) + .partition_result(); + + chain!(name_value_errs, name_format_errors).validate_no_errors()?; + + Ok(ident_values) + } + + fn extract_name_value(meta: NestedMeta) -> syn::Result { + if let Meta(NameValue(nv)) = meta { + Ok(nv) + } else { + Err(Error::new_spanned(meta, "Expected name='value'.")) + } + } +} + +#[cfg(test)] +mod tests { + use proc_macro2::TokenStream; + use quote::quote; + use syn::LitBool; + + use crate::parse_utils::command::Command; + + use super::*; + + #[test] + fn name_values_correctly_parsed() -> syn::Result<()> { + // given + let name_values = extract_name_values(quote! {SomeCommand(attr1="value1", attr2=true)})?; + + // when + let attr_values = ["attr1", "attr2"].map(|attr| { + name_values + .try_get(attr) + .unwrap_or_else(|| panic!("Attribute {attr} should have existed!")) + .clone() + }); + + // then + let expected_values = [ + Lit::Str(LitStr::new("value1", Span::call_site())), + Lit::Bool(LitBool::new(true, Span::call_site())), + ]; + + assert_eq!(attr_values, expected_values); + + Ok(()) + } + + #[test] + fn duplicates_cause_errors() { + // given + let tokens = quote! {SomeCommand(duplicate=1, something=2, duplicate=3)}; + + // when + let err = extract_name_values(tokens).expect_err("Should have failed"); + + // then + let messages = err.into_iter().map(|e| e.to_string()).collect::>(); + assert_eq!(messages, vec!["Original defined here:", "Duplicate!"]); + } + + #[test] + fn attr_names_cannot_be_paths() { + let tokens = quote! {SomeCommand(something::duplicate=1)}; + + let err = extract_name_values(tokens).expect_err("Should have failed"); + + assert_eq!( + err.to_string(), + "Attribute name cannot be a `Path` -- i.e. must not contain ':'." + ); + } + + #[test] + fn only_name_value_is_accepted() { + let tokens = quote! {SomeCommand(name="value", "something_else")}; + + let err = extract_name_values(tokens).expect_err("Should have failed"); + + assert_eq!(err.to_string(), "Expected name='value'."); + } + + #[test] + fn validates_correct_names() -> syn::Result<()> { + let tokens = quote! {SomeCommand(name="value", other="something_else")}; + let name_values = extract_name_values(tokens)?; + + let result = name_values.validate_has_no_other_names(&["name", "other", "another"]); + + assert!(result.is_ok()); + + Ok(()) + } + + #[test] + fn catches_incorrect_names() -> syn::Result<()> { + let name_values = + extract_name_values(quote! {SomeCommand(name="value", other="something_else")})?; + + let err = name_values + .validate_has_no_other_names(&["name", "other_is_not_allowed"]) + .expect_err("Should have failed"); + + assert_eq!( + err.to_string(), + "Attribute 'other' not recognized! Expected one of: 'name', 'other_is_not_allowed'." + ); + + Ok(()) + } + + #[test] + fn can_get_lit_strs() -> syn::Result<()> { + let name_values = extract_name_values(quote! {SomeCommand(name="value")})?; + + let lit_str = name_values.get_as_lit_str("name")?; + + assert_eq!(lit_str.value(), "value"); + + Ok(()) + } + + #[test] + fn cannot_get_lit_str_if_type_is_wrong() -> syn::Result<()> { + let name_values = extract_name_values(quote! {SomeCommand(name=true)})?; + + let err = name_values + .get_as_lit_str("name") + .expect_err("Should have failed"); + + assert_eq!( + err.to_string(), + "Expected the attribute 'name' to have a string value!" + ); + + Ok(()) + } + + #[test] + fn lit_str_getter_complains_value_is_missing() -> syn::Result<()> { + let name_values = extract_name_values(quote! {SomeCommand(name=true)})?; + + let err = name_values + .get_as_lit_str("missing") + .expect_err("Should have failed"); + + assert_eq!(err.to_string(), "Missing attribute 'missing'."); + + Ok(()) + } + + fn extract_name_values(stream: TokenStream) -> syn::Result { + let command = Command::parse_single_from_token_stream(stream)?; + UniqueNameValues::new(command.contents) + } +} diff --git a/packages/fuels-abigen-macro/src/setup_contract_test_macro.rs b/packages/fuels-abigen-macro/src/setup_contract_test_macro.rs new file mode 100644 index 0000000000..d71ed1a573 --- /dev/null +++ b/packages/fuels-abigen-macro/src/setup_contract_test_macro.rs @@ -0,0 +1,5 @@ +pub(crate) use code_gen::generate_setup_contract_test_code; +pub(crate) use parsing::TestContractCommands; + +mod code_gen; +mod parsing; diff --git a/packages/fuels-abigen-macro/src/setup_contract_test_macro/code_gen.rs b/packages/fuels-abigen-macro/src/setup_contract_test_macro/code_gen.rs new file mode 100644 index 0000000000..a10f945f5a --- /dev/null +++ b/packages/fuels-abigen-macro/src/setup_contract_test_macro/code_gen.rs @@ -0,0 +1,195 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use rand::{prelude::StdRng, Rng}; +use syn::LitStr; + +use fuels_core::{ + code_gen::abigen::{Abigen, AbigenTarget, ProgramType}, + utils::ident, + vm::fuel_crypto::coins_bip32::ecdsa::signature::rand_core::SeedableRng, +}; + +use crate::setup_contract_test_macro::parsing::{ + DeployContract, GenerateContract, InitializeWallet, TestContractCommands, +}; + +pub(crate) fn generate_setup_contract_test_code( + commands: TestContractCommands, +) -> syn::Result { + let TestContractCommands { + initialize_wallets, + generate_contract, + deploy_contract, + } = commands; + + let project_lookup = generate_project_lookup(&generate_contract)?; + + let abigen_code = abigen_code(&project_lookup); + + let wallet_code = wallet_initialization_code(initialize_wallets); + + let deploy_code = contract_deploying_code(&deploy_contract, &project_lookup); + + Ok(quote! { + #abigen_code + #wallet_code + #deploy_code + }) +} + +fn generate_project_lookup(commands: &[GenerateContract]) -> syn::Result> { + let pairs = commands + .iter() + .map(|command| -> syn::Result<_> { + let project = Project::new(&command.abi)?; + Ok((command.name.value(), project)) + }) + .collect::, _>>()?; + + Ok(pairs.into_iter().collect()) +} + +fn abigen_code(project_lookup: &HashMap) -> TokenStream { + let targets = generate_abigen_targets(project_lookup); + Abigen::generate(targets, false).expect("Failed to generate abigen") +} + +fn generate_abigen_targets(project_lookup: &HashMap) -> Vec { + project_lookup + .iter() + .map(|(name, project)| AbigenTarget { + name: name.clone(), + abi: project.abi_path(), + program_type: ProgramType::Contract, + }) + .collect() +} + +fn wallet_initialization_code(maybe_command: Option) -> TokenStream { + let command = if let Some(command) = maybe_command { + command + } else { + return Default::default(); + }; + + let wallet_names = extract_wallet_names(&command); + + if wallet_names.is_empty() { + return Default::default(); + } + + let num_wallets = wallet_names.len(); + quote! { + let [#(#wallet_names),*]: [_; #num_wallets] = launch_custom_provider_and_get_wallets( + WalletsConfig::new(Some(#num_wallets as u64), None, None), + None, + None, + ) + .await + .try_into() + .expect("Should have the exact number of wallets"); + } +} + +fn extract_wallet_names(command: &InitializeWallet) -> Vec { + command + .names + .iter() + .map(|name| ident(&name.value())) + .collect() +} + +fn contract_deploying_code( + commands: &[DeployContract], + project_lookup: &HashMap, +) -> TokenStream { + commands + .iter() + .map(|command| { + // Generate random salt for contract deployment + let mut rng = StdRng::from_entropy(); + let salt: [u8; 32] = rng.gen(); + + let contract_instance_name = ident(&command.name); + let contract_struct_name = ident(&command.contract.value()); + let wallet_name = ident(&command.wallet); + + let project = project_lookup + .get(&command.contract.value()) + .expect("Project should be in lookup"); + let bin_path = project.bin_path(); + let storage_path = project.storage_path(); + + quote! { + let #contract_instance_name = #contract_struct_name::new( + Contract::deploy_with_parameters( + #bin_path, + &#wallet_name, + TxParameters::default(), + StorageConfiguration::with_storage_path(Some( + #storage_path.to_string(), + )), + Salt::from([#(#salt),*]), + ) + .await + .expect("Failed to deploy the contract"), + #wallet_name.clone(), + ); + } + }) + .reduce(|mut all_code, code| { + all_code.extend(code); + all_code + }) + .unwrap_or_default() +} + +struct Project { + path: PathBuf, +} + +impl Project { + fn new(dir: &LitStr) -> syn::Result { + let path = Path::new(&dir.value()).canonicalize().map_err(|_| { + syn::Error::new_spanned( + dir.clone(), + "Unable to canonicalize forc project path. Make sure the path is valid!", + ) + })?; + + Ok(Self { path }) + } + + fn compile_file_path(&self, suffix: &str, description: &str) -> String { + self.path + .join(["out/debug/", self.project_name(), suffix].concat()) + .to_str() + .unwrap_or_else(|| panic!("could not join path for {description}")) + .to_string() + } + + fn project_name(&self) -> &str { + self.path + .file_name() + .expect("failed to get project name") + .to_str() + .expect("failed to convert project name to string") + } + + fn abi_path(&self) -> String { + self.compile_file_path("-abi.json", "the ABI file") + } + + fn bin_path(&self) -> String { + self.compile_file_path(".bin", "the binary file") + } + + fn storage_path(&self) -> String { + self.compile_file_path("-storage_slots.json", "the storage slots file") + } +} diff --git a/packages/fuels-abigen-macro/src/setup_contract_test_macro/parsing.rs b/packages/fuels-abigen-macro/src/setup_contract_test_macro/parsing.rs new file mode 100644 index 0000000000..997d35b593 --- /dev/null +++ b/packages/fuels-abigen-macro/src/setup_contract_test_macro/parsing.rs @@ -0,0 +1,230 @@ +use std::collections::HashSet; + +use itertools::{chain, Itertools}; +use proc_macro2::Span; +use syn::{ + parse::{Parse, ParseStream}, + Error, LitStr, Result as ParseResult, +}; + +use crate::parse_utils::{Command, ErrorsExt, UniqueLitStrs, UniqueNameValues}; + +trait MacroCommand { + fn expected_name() -> &'static str; + fn validate_command_name(command: &Command) -> syn::Result<()> { + let expected_name = Self::expected_name(); + if command.name == expected_name { + Ok(()) + } else { + Err(Error::new_spanned( + command.name.clone(), + format!("Expected command to have name: '{expected_name}'."), + )) + } + } +} + +pub(crate) struct InitializeWallet { + pub(crate) span: Span, + pub(crate) names: Vec, +} + +impl MacroCommand for InitializeWallet { + fn expected_name() -> &'static str { + "Wallets" + } +} + +impl TryFrom for InitializeWallet { + type Error = Error; + + fn try_from(command: Command) -> Result { + Self::validate_command_name(&command)?; + + let wallets = UniqueLitStrs::new(command.contents)?; + + Ok(Self { + span: command.name.span(), + names: wallets.into_iter().collect(), + }) + } +} + +pub(crate) struct GenerateContract { + pub(crate) name: LitStr, + pub(crate) abi: LitStr, +} + +impl MacroCommand for GenerateContract { + fn expected_name() -> &'static str { + "Abigen" + } +} + +impl TryFrom for GenerateContract { + type Error = Error; + + fn try_from(command: Command) -> Result { + Self::validate_command_name(&command)?; + + let name_values = UniqueNameValues::new(command.contents)?; + name_values.validate_has_no_other_names(&["name", "abi"])?; + + let name = name_values.get_as_lit_str("name")?.clone(); + let abi = name_values.get_as_lit_str("abi")?.clone(); + + Ok(Self { name, abi }) + } +} + +pub(crate) struct DeployContract { + pub name: String, + pub contract: LitStr, + pub wallet: String, +} + +impl MacroCommand for DeployContract { + fn expected_name() -> &'static str { + "Deploy" + } +} + +impl TryFrom for DeployContract { + type Error = Error; + + fn try_from(command: Command) -> Result { + Self::validate_command_name(&command)?; + let name_values = UniqueNameValues::new(command.contents)?; + name_values.validate_has_no_other_names(&["name", "contract", "wallet"])?; + + let name = name_values.get_as_lit_str("name")?.value(); + let contract = name_values.get_as_lit_str("contract")?.clone(); + let wallet = name_values.get_as_lit_str("wallet")?.value(); + + Ok(Self { + name, + contract, + wallet, + }) + } +} + +fn parse_test_contract_commands( + input: ParseStream, +) -> syn::Result<( + Vec, + Vec, + Vec, +)> { + let commands = Command::parse_multiple(input)?; + + let mut init_wallets: Vec> = vec![]; + let mut gen_contracts: Vec> = vec![]; + let mut deploy_contracts: Vec> = vec![]; + + let mut errors = vec![]; + + for command in commands { + let command_name = &command.name; + if command_name == InitializeWallet::expected_name() { + init_wallets.push(command.try_into()); + } else if command_name == GenerateContract::expected_name() { + gen_contracts.push(command.try_into()); + } else if command_name == DeployContract::expected_name() { + deploy_contracts.push(command.try_into()); + } else { + errors.push(Error::new_spanned( + command.name, + "Unsupported command. Expected: 'Wallets', 'Abigen' or 'Deploy'", + )) + } + } + + let (init_wallets, wallet_errs): (Vec<_>, Vec<_>) = init_wallets.into_iter().partition_result(); + let (gen_contracts, gen_errs): (Vec<_>, Vec<_>) = gen_contracts.into_iter().partition_result(); + let (deploy_contracts, deploy_errs): (Vec<_>, Vec<_>) = + deploy_contracts.into_iter().partition_result(); + + chain!(errors, wallet_errs, gen_errs, deploy_errs).validate_no_errors()?; + + Ok((init_wallets, gen_contracts, deploy_contracts)) +} + +// Contains the result of parsing the input to the `setup_contract_test` macro. +// Contents represent the users wishes with regards to wallet initialization, +// bindings generation and contract deployment. +pub(crate) struct TestContractCommands { + pub(crate) initialize_wallets: Option, + pub(crate) generate_contract: Vec, + pub(crate) deploy_contract: Vec, +} + +impl Parse for TestContractCommands { + fn parse(input: ParseStream) -> ParseResult { + let span = input.span(); + + let (mut initialize_wallets, generate_contract, deploy_contract) = + parse_test_contract_commands(input)?; + + Self::validate_all_contracts_are_known( + span, + generate_contract.as_slice(), + deploy_contract.as_slice(), + )?; + + Self::validate_zero_or_one_wallet_command_present(initialize_wallets.as_slice())?; + + Ok(Self { + initialize_wallets: initialize_wallets.pop(), + generate_contract, + deploy_contract, + }) + } +} + +impl TestContractCommands { + fn contracts_to_generate(commands: &[GenerateContract]) -> HashSet<&LitStr> { + commands.iter().map(|c| &c.name).collect() + } + + fn contracts_to_deploy(commands: &[DeployContract]) -> HashSet<&LitStr> { + commands.iter().map(|c| &c.contract).collect() + } + + fn validate_all_contracts_are_known( + span: Span, + generate_contracts: &[GenerateContract], + deploy_contracts: &[DeployContract], + ) -> syn::Result<()> { + Self::contracts_to_deploy(deploy_contracts) + .difference(&Self::contracts_to_generate(generate_contracts)) + .flat_map(|unknown_contract| { + [ + Error::new_spanned(unknown_contract, "Contract is unknown"), + Error::new( + span, + format!( + "Consider adding: Abigen(name=\"{}\", abi=...)", + unknown_contract.value() + ), + ), + ] + }) + .validate_no_errors() + } + + fn validate_zero_or_one_wallet_command_present( + commands: &[InitializeWallet], + ) -> syn::Result<()> { + if commands.len() > 1 { + commands + .iter() + .map(|command| Error::new(command.span, "Only one `Wallets` command allowed")) + .combine_errors() + .map(Err) + .expect("Known to have at least one error") + } else { + Ok(()) + } + } +} diff --git a/packages/fuels-abigen-macro/tests/macro_usage.rs b/packages/fuels-abigen-macro/tests/macro_usage.rs new file mode 100644 index 0000000000..68f5375934 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/macro_usage.rs @@ -0,0 +1,9 @@ +#[cfg(test)] +mod tests { + #[test] + fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/abigen_macro/*.rs"); + t.compile_fail("tests/ui/setup_contract_test_macro/*.rs"); + } +} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.rs new file mode 100644 index 0000000000..119467e6ca --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.rs @@ -0,0 +1,10 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract( + abi = "some-abi.json", + abi = "some-abi2.json", + name = "SomeName", + abi = "some-abi3.json", +)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.stderr new file mode 100644 index 0000000000..877b2352ab --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/duplicate_attribute.stderr @@ -0,0 +1,17 @@ +error: Original defined here: + --> tests/ui/abigen_macro/duplicate_attribute.rs:4:5 + | +4 | abi = "some-abi.json", + | ^^^ + +error: Duplicate! + --> tests/ui/abigen_macro/duplicate_attribute.rs:5:5 + | +5 | abi = "some-abi2.json", + | ^^^ + +error: Duplicate! + --> tests/ui/abigen_macro/duplicate_attribute.rs:7:5 + | +7 | abi = "some-abi3.json", + | ^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.rs new file mode 100644 index 0000000000..66c90ce84d --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.rs @@ -0,0 +1,5 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract(name = "SomeName", abi = true,)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.stderr new file mode 100644 index 0000000000..60f52e0faf --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_abi_value.stderr @@ -0,0 +1,5 @@ +error: Expected the attribute 'abi' to have a string value! + --> tests/ui/abigen_macro/invalid_abi_value.rs:3:43 + | +3 | abigen!(Contract(name = "SomeName", abi = true,)); + | ^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.rs new file mode 100644 index 0000000000..96e3856a2d --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.rs @@ -0,0 +1,5 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract(name = true, abi = "some-abi.json",)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.stderr new file mode 100644 index 0000000000..ba65c3f6a2 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_name_value.stderr @@ -0,0 +1,5 @@ +error: Expected the attribute 'name' to have a string value! + --> tests/ui/abigen_macro/invalid_name_value.rs:3:25 + | +3 | abigen!(Contract(name = true, abi = "some-abi.json",)); + | ^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.rs new file mode 100644 index 0000000000..08c6d9cb83 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.rs @@ -0,0 +1,8 @@ +use fuels_abigen_macro::abigen; + +abigen!(SomeInvalidProgramType( + name = "SomeName", + abi = "some-abi.json" +)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.stderr new file mode 100644 index 0000000000..1b6d23c4d4 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/invalid_program_type.stderr @@ -0,0 +1,5 @@ +error: Unsupported program type. Expected: 'Contract', 'Script' or 'Predicate' + --> tests/ui/abigen_macro/invalid_program_type.rs:3:9 + | +3 | abigen!(SomeInvalidProgramType( + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.rs new file mode 100644 index 0000000000..f010fc2955 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.rs @@ -0,0 +1,5 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract(name = "SomeName")); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.stderr new file mode 100644 index 0000000000..e92fbdaba5 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_abi_attribute.stderr @@ -0,0 +1,5 @@ +error: Missing attribute 'abi'. + --> tests/ui/abigen_macro/missing_abi_attribute.rs:3:18 + | +3 | abigen!(Contract(name = "SomeName")); + | ^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.rs new file mode 100644 index 0000000000..61e5365402 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.rs @@ -0,0 +1,5 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract(abi = "some-abi.json")); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.stderr new file mode 100644 index 0000000000..8fe23baf8f --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/missing_name_attr.stderr @@ -0,0 +1,5 @@ +error: Missing attribute 'name'. + --> tests/ui/abigen_macro/missing_name_attr.rs:3:18 + | +3 | abigen!(Contract(abi = "some-abi.json")); + | ^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.rs b/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.rs new file mode 100644 index 0000000000..3ef17bcfac --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.rs @@ -0,0 +1,9 @@ +use fuels_abigen_macro::abigen; + +abigen!(Contract( + name = "SomeName", + abi = "some-abi.json", + unknown = "something" +)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.stderr b/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.stderr new file mode 100644 index 0000000000..c32d30e061 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/abigen_macro/unrecognized_attribute.stderr @@ -0,0 +1,5 @@ +error: Attribute 'unknown' not recognized! Expected one of: 'name', 'abi'. + --> tests/ui/abigen_macro/unrecognized_attribute.rs:6:5 + | +6 | unknown = "something" + | ^^^^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.rs b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.rs new file mode 100644 index 0000000000..c8f3c84625 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.rs @@ -0,0 +1,9 @@ +use fuels_abigen_macro::setup_contract_test; + +setup_contract_test!(Deploy( + name = "some_instance", + contract = "SomeUnknownContract", + wallet = "some_wallet" +)); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.stderr b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.stderr new file mode 100644 index 0000000000..511de60c3e --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/contract_not_generated.stderr @@ -0,0 +1,11 @@ +error: Contract is unknown + --> tests/ui/setup_contract_test_macro/contract_not_generated.rs:5:16 + | +5 | contract = "SomeUnknownContract", + | ^^^^^^^^^^^^^^^^^^^^^ + +error: Consider adding: Abigen(name="SomeUnknownContract", abi=...) + --> tests/ui/setup_contract_test_macro/contract_not_generated.rs:3:22 + | +3 | setup_contract_test!(Deploy( + | ^^^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.rs b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.rs new file mode 100644 index 0000000000..5991f85bc8 --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.rs @@ -0,0 +1,9 @@ +use fuels_abigen_macro::setup_contract_test; + +setup_contract_test!( + Wallets("wallet1"), + Wallets("wallet2"), + Abigen(name = "MyContract", abi = "some_file.json") +); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.stderr b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.stderr new file mode 100644 index 0000000000..689065756b --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_command.stderr @@ -0,0 +1,11 @@ +error: Only one `Wallets` command allowed + --> tests/ui/setup_contract_test_macro/duplicate_wallet_command.rs:4:5 + | +4 | Wallets("wallet1"), + | ^^^^^^^ + +error: Only one `Wallets` command allowed + --> tests/ui/setup_contract_test_macro/duplicate_wallet_command.rs:5:5 + | +5 | Wallets("wallet2"), + | ^^^^^^^ diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.rs b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.rs new file mode 100644 index 0000000000..3e2849971f --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.rs @@ -0,0 +1,8 @@ +use fuels_abigen_macro::setup_contract_test; + +setup_contract_test!( + Wallets("wallet1", "wallet1"), + Abigen(name = "MyContract", abi = "some_file.json") +); + +fn main() {} diff --git a/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.stderr b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.stderr new file mode 100644 index 0000000000..411d8efffc --- /dev/null +++ b/packages/fuels-abigen-macro/tests/ui/setup_contract_test_macro/duplicate_wallet_names.stderr @@ -0,0 +1,11 @@ +error: Original defined here: + --> tests/ui/setup_contract_test_macro/duplicate_wallet_names.rs:4:13 + | +4 | Wallets("wallet1", "wallet1"), + | ^^^^^^^^^ + +error: Duplicate! + --> tests/ui/setup_contract_test_macro/duplicate_wallet_names.rs:4:24 + | +4 | Wallets("wallet1", "wallet1"), + | ^^^^^^^^^ diff --git a/packages/fuels-contract/src/contract.rs b/packages/fuels-contract/src/contract.rs index 063e57785c..767d431019 100644 --- a/packages/fuels-contract/src/contract.rs +++ b/packages/fuels-contract/src/contract.rs @@ -99,7 +99,7 @@ impl Contract { /// } /// ``` /// - /// For more details see `code_gen/functions_gen.rs` in `fuels-core`. + /// For more details see `code_gen` in `fuels-core`. /// /// Note that this needs a wallet because the contract instance needs a wallet for the calls pub fn method_hash( @@ -499,8 +499,8 @@ where { /// Sets external contracts as dependencies to this contract's call. /// Effectively, this will be used to create [`Input::Contract`]/[`Output::Contract`] - /// pairs and set them into the transaction. - /// Note that this is a builder method, i.e. use it as a chain: + /// pairs and set them into the transaction. Note that this is a builder + /// method, i.e. use it as a chain: /// /// ```ignore /// my_contract_instance.my_method(...).set_contract_ids(&[another_contract_id]).call() @@ -529,11 +529,11 @@ where self } - /// Appends additional external contracts as dependencies to this contract's call. - /// Effectively, this will be used to create additional - /// [`Input::Contract`]/[`Output::Contract`] pairs - /// and set them into the transaction. - /// Note that this is a builder method, i.e. use it as a chain: + /// Appends additional external contracts as dependencies to this contract's + /// call. Effectively, this will be used to create additional + /// [`Input::Contract`]/[`Output::Contract`] + /// pairs and set them into the transaction. Note that this is a builder + /// method, i.e. use it as a chain: /// /// ```ignore /// my_contract_instance.my_method(...).append_contracts(additional_contract_id).call() @@ -780,9 +780,9 @@ impl MultiContractCallHandler { /// Call contract methods on the node, in a simulated manner, meaning the state of the /// blockchain is *not* modified but simulated. - /// It is the same as the [`call`] method because the API is more user-friendly this way. + /// It is the same as the [call] method because the API is more user-friendly this way. /// - /// [`call`]: Self::call + /// [call]: Self::call pub async fn simulate(&self) -> Result, Error> { Self::call_or_simulate(self, true) .await diff --git a/packages/fuels-contract/src/execution_script.rs b/packages/fuels-contract/src/execution_script.rs index b9e81766b9..0ee7db936f 100644 --- a/packages/fuels-contract/src/execution_script.rs +++ b/packages/fuels-contract/src/execution_script.rs @@ -19,7 +19,7 @@ use crate::contract_calls_utils::{ get_transaction_inputs_outputs, }; -/// [`TransactionExecution`] provides methods to create and call/simulate a transaction that carries +/// [`ExecutableFuelCall`] provides methods to create and call/simulate a transaction that carries /// out contract method calls or script calls #[derive(Debug)] pub struct ExecutableFuelCall { @@ -31,7 +31,7 @@ impl ExecutableFuelCall { Self { tx } } - /// Creates a [`TransactionExecution`] from contract calls. The internal [`Transaction`] is + /// Creates a [`ExecutableFuelCall`] from contract calls. The internal [Transaction] is /// initialized with the actual script instructions, script data needed to perform the call and /// transaction inputs/outputs consisting of assets and contracts. pub async fn from_contract_calls( diff --git a/packages/fuels-contract/src/lib.rs b/packages/fuels-contract/src/lib.rs index c8249a8472..767c87826f 100644 --- a/packages/fuels-contract/src/lib.rs +++ b/packages/fuels-contract/src/lib.rs @@ -4,11 +4,3 @@ pub mod contract_calls_utils; pub mod execution_script; pub mod logs; pub mod script_calls; - -pub mod abi_encoder { - pub use fuels_core::abi_encoder::*; -} - -pub mod abi_decoder { - pub use fuels_core::abi_decoder::*; -} diff --git a/packages/fuels-contract/src/script_calls.rs b/packages/fuels-contract/src/script_calls.rs index ee557cd5ca..b2ea9b9a30 100644 --- a/packages/fuels-contract/src/script_calls.rs +++ b/packages/fuels-contract/src/script_calls.rs @@ -1,5 +1,4 @@ use crate::{ - abi_encoder::UnresolvedBytes, call_response::FuelCallResponse, contract::{get_decoded_output, SettableContract}, contract_calls_utils::{generate_contract_inputs, generate_contract_outputs}, @@ -11,15 +10,19 @@ use fuel_gql_client::{ fuel_types::bytes::padded_len_usize, }; use fuel_tx::{ContractId, Input}; +use fuels_core::abi_encoder::UnresolvedBytes; use fuels_core::{ offsets::base_offset, parameters::{CallParameters, TxParameters}, - Tokenizable, + Parameterize, Tokenizable, }; use fuels_signers::{provider::Provider, WalletUnlocked}; -use fuels_types::{bech32::Bech32ContractId, errors::Error, param_types::ParamType}; +use fuels_types::bech32::Bech32ContractId; +use fuels_types::errors::Error; use itertools::chain; -use std::{collections::HashSet, fmt::Debug, marker::PhantomData}; +use std::collections::HashSet; +use std::fmt::Debug; +use std::marker::PhantomData; #[derive(Debug)] /// Contains all data relevant to a single script call @@ -60,21 +63,19 @@ pub struct ScriptCallHandler { pub tx_parameters: TxParameters, pub wallet: WalletUnlocked, pub provider: Provider, - pub output_param: ParamType, pub datatype: PhantomData, pub log_decoder: LogDecoder, } impl ScriptCallHandler where - D: Tokenizable + Debug, + D: Parameterize + Tokenizable + Debug, { pub fn new( script_binary: Vec, encoded_args: UnresolvedBytes, wallet: WalletUnlocked, provider: Provider, - output_param: ParamType, log_decoder: LogDecoder, ) -> Self { let script_call = ScriptCall { @@ -90,7 +91,6 @@ where tx_parameters: TxParameters::default(), wallet, provider, - output_param, datatype: PhantomData, log_decoder, } @@ -213,7 +213,7 @@ where /// Create a [`FuelCallResponse`] from call receipts pub fn get_response(&self, receipts: Vec) -> Result, Error> { - let token = get_decoded_output(&receipts, None, &self.output_param)?; + let token = get_decoded_output(&receipts, None, &D::param_type())?; Ok(FuelCallResponse::new( D::from_token(token)?, receipts, diff --git a/packages/fuels-core/src/abi_decoder.rs b/packages/fuels-core/src/abi_decoder.rs index 53bd142ce8..75df683b19 100644 --- a/packages/fuels-core/src/abi_decoder.rs +++ b/packages/fuels-core/src/abi_decoder.rs @@ -1,9 +1,12 @@ -use crate::{unzip_param_types, StringToken, Token}; +use std::{convert::TryInto, str}; + use fuel_types::bytes::padded_len_usize; + use fuels_types::{ constants::WORD_SIZE, enum_variants::EnumVariants, errors::CodecError, param_types::ParamType, }; -use std::{convert::TryInto, str}; + +use crate::{unzip_param_types, StringToken, Token}; #[derive(Debug, Clone)] struct DecodeResult { @@ -305,10 +308,12 @@ fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8], CodecError> { #[cfg(test)] mod tests { - use super::*; + use std::vec; + use fuels_test_helpers::generate_unused_field_names; use fuels_types::{enum_variants::EnumVariants, errors::Error}; - use std::vec; + + use super::*; #[test] fn decode_int() -> Result<(), Error> { diff --git a/packages/fuels-core/src/abi_encoder.rs b/packages/fuels-core/src/abi_encoder.rs index 94436b1737..4b88dce01f 100644 --- a/packages/fuels-core/src/abi_encoder.rs +++ b/packages/fuels-core/src/abi_encoder.rs @@ -1,7 +1,9 @@ -use crate::{pad_string, pad_u16, pad_u32, pad_u8, EnumSelector, StringToken, Token}; -use fuels_types::{constants::WORD_SIZE, errors::CodecError}; use itertools::Itertools; +use fuels_types::{constants::WORD_SIZE, errors::CodecError}; + +use crate::{pad_string, pad_u16, pad_u32, pad_u8, EnumSelector, StringToken, Token}; + pub struct ABIEncoder; #[derive(Debug, Clone)] @@ -216,13 +218,17 @@ impl ABIEncoder { #[cfg(test)] mod tests { - use super::*; - use crate::utils::first_four_bytes_of_sha256_hash; - use fuels_test_helpers::generate_unused_field_names; - use fuels_types::{enum_variants::EnumVariants, errors::Error, param_types::ParamType}; + use std::slice; + use itertools::chain; use sha2::{Digest, Sha256}; - use std::slice; + + use fuels_test_helpers::generate_unused_field_names; + use fuels_types::{enum_variants::EnumVariants, errors::Error, param_types::ParamType}; + + use crate::utils::first_four_bytes_of_sha256_hash; + + use super::*; const VEC_METADATA_SIZE: usize = 3 * WORD_SIZE; const DISCRIMINANT_SIZE: usize = WORD_SIZE; diff --git a/packages/fuels-core/src/code_gen.rs b/packages/fuels-core/src/code_gen.rs index 2d330981d5..7765e716e4 100644 --- a/packages/fuels-core/src/code_gen.rs +++ b/packages/fuels-core/src/code_gen.rs @@ -1,11 +1,10 @@ //! This module implements everything related to code generation/expansion //! from a FuelVM ABI. +pub mod abi_types; pub mod abigen; -pub mod bindings; pub mod custom_types; -pub mod docs_gen; pub mod function_selector; -pub mod functions_gen; +mod generated_code; mod resolved_type; - -pub use abigen::get_logs_hashmap; +mod type_path; +mod utils; diff --git a/packages/fuels-core/src/code_gen/abi_types.rs b/packages/fuels-core/src/code_gen/abi_types.rs new file mode 100644 index 0000000000..673941d350 --- /dev/null +++ b/packages/fuels-core/src/code_gen/abi_types.rs @@ -0,0 +1,364 @@ +use std::collections::HashMap; + +use fuel_abi_types::program_abi::{ + ABIFunction, LoggedType, ProgramABI, TypeApplication, TypeDeclaration, +}; +use fuels_types::errors::Error; +use fuels_types::errors::Error::InvalidData; + +/// 'Full' versions of the ABI structures are needed to simplify duplicate +/// detection later on. The original ones([`ProgramABI`], [`TypeApplication`], +/// [`TypeDeclaration`] and others) are not suited for this due to their use of +/// ids, which might differ between contracts even though the type they +/// represent is virtually the same. +#[derive(Debug, Clone)] +pub(crate) struct FullProgramABI { + pub types: Vec, + pub functions: Vec, + pub logged_types: Vec, +} + +impl FullProgramABI { + pub fn from_json_abi(abi: &str) -> Result { + let parsed_abi: ProgramABI = serde_json::from_str(abi)?; + FullProgramABI::from_counterpart(&parsed_abi) + } + + fn from_counterpart(program_abi: &ProgramABI) -> Result { + let lookup: HashMap<_, _> = program_abi + .types + .iter() + .map(|ttype| (ttype.type_id, ttype.clone())) + .collect(); + + let types = program_abi + .types + .iter() + .map(|ttype| FullTypeDeclaration::from_counterpart(ttype, &lookup)) + .collect(); + + let functions = program_abi + .functions + .iter() + .map(|fun| FullABIFunction::from_counterpart(fun, &lookup)) + .collect::, _>>()?; + + let logged_types = program_abi + .logged_types + .iter() + .flatten() + .map(|logged_type| FullLoggedType::from_counterpart(logged_type, &lookup)) + .collect(); + + Ok(Self { + types, + functions, + logged_types, + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct FullABIFunction { + name: String, + inputs: Vec, + output: FullTypeApplication, +} + +impl FullABIFunction { + pub(crate) fn new( + name: String, + inputs: Vec, + output: FullTypeApplication, + ) -> Result { + if name.is_empty() { + Err(InvalidData( + "FullABIFunction's name cannot be empty!".to_string(), + )) + } else { + Ok(Self { + name, + inputs, + output, + }) + } + } + + pub(crate) fn name(&self) -> &str { + self.name.as_str() + } + + pub(crate) fn inputs(&self) -> &[FullTypeApplication] { + self.inputs.as_slice() + } + + pub(crate) fn output(&self) -> &FullTypeApplication { + &self.output + } + + pub(crate) fn from_counterpart( + abi_function: &ABIFunction, + types: &HashMap, + ) -> Result { + let inputs = abi_function + .inputs + .iter() + .map(|input| FullTypeApplication::from_counterpart(input, types)) + .collect(); + + FullABIFunction::new( + abi_function.name.clone(), + inputs, + FullTypeApplication::from_counterpart(&abi_function.output, types), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) struct FullTypeDeclaration { + pub type_field: String, + pub components: Vec, + pub type_parameters: Vec, +} + +impl FullTypeDeclaration { + pub(crate) fn from_counterpart( + type_decl: &TypeDeclaration, + types: &HashMap, + ) -> FullTypeDeclaration { + let components = type_decl + .components + .clone() + .unwrap_or_default() + .into_iter() + .map(|application| FullTypeApplication::from_counterpart(&application, types)) + .collect(); + let type_parameters = type_decl + .type_parameters + .clone() + .unwrap_or_default() + .into_iter() + .map(|id| FullTypeDeclaration::from_counterpart(types.get(&id).unwrap(), types)) + .collect(); + FullTypeDeclaration { + type_field: type_decl.type_field.clone(), + components, + type_parameters, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) struct FullTypeApplication { + pub name: String, + pub type_decl: FullTypeDeclaration, + pub type_arguments: Vec, +} + +impl FullTypeApplication { + pub(crate) fn from_counterpart( + type_application: &TypeApplication, + types: &HashMap, + ) -> FullTypeApplication { + let type_arguments = type_application + .type_arguments + .clone() + .unwrap_or_default() + .into_iter() + .map(|application| FullTypeApplication::from_counterpart(&application, types)) + .collect(); + + let type_decl = FullTypeDeclaration::from_counterpart( + types.get(&type_application.type_id).unwrap(), + types, + ); + + FullTypeApplication { + name: type_application.name.clone(), + type_decl, + type_arguments, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct FullLoggedType { + pub log_id: u64, + pub application: FullTypeApplication, +} + +impl FullLoggedType { + fn from_counterpart( + logged_type: &LoggedType, + types: &HashMap, + ) -> FullLoggedType { + FullLoggedType { + log_id: logged_type.log_id, + application: FullTypeApplication::from_counterpart(&logged_type.application, types), + } + } +} + +impl FullTypeDeclaration { + pub fn is_enum_type(&self) -> bool { + let type_field = &self.type_field; + type_field.starts_with("enum ") + } + + pub fn is_struct_type(&self) -> bool { + let type_field = &self.type_field; + type_field.starts_with("struct ") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + + #[test] + fn abi_function_cannot_have_an_empty_name() { + let fn_output = FullTypeApplication { + name: "".to_string(), + type_decl: FullTypeDeclaration { + type_field: "SomeType".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![], + }; + + let err = FullABIFunction::new("".to_string(), vec![], fn_output) + .expect_err("Should have failed."); + + if let InvalidData(msg) = err { + assert_eq!(msg, "FullABIFunction's name cannot be empty!"); + } else { + panic!("Unexpected error: {err}"); + } + } + #[test] + fn can_convert_into_full_type_decl() { + // given + let type_0 = TypeDeclaration { + type_id: 0, + type_field: "type_0".to_string(), + components: Some(vec![TypeApplication { + name: "type_0_component_a".to_string(), + type_id: 1, + type_arguments: Some(vec![TypeApplication { + name: "type_0_type_arg_0".to_string(), + type_id: 2, + type_arguments: None, + }]), + }]), + type_parameters: Some(vec![2]), + }; + + let type_1 = TypeDeclaration { + type_id: 1, + type_field: "type_1".to_string(), + components: None, + type_parameters: None, + }; + + let type_2 = TypeDeclaration { + type_id: 2, + type_field: "type_2".to_string(), + components: None, + type_parameters: None, + }; + + let types = [&type_0, &type_1, &type_2] + .iter() + .map(|&ttype| (ttype.type_id, ttype.clone())) + .collect::>(); + + // when + let sut = FullTypeDeclaration::from_counterpart(&type_0, &types); + + // then + let type_2_decl = FullTypeDeclaration { + type_field: "type_2".to_string(), + components: vec![], + type_parameters: vec![], + }; + assert_eq!( + sut, + FullTypeDeclaration { + type_field: "type_0".to_string(), + components: vec![FullTypeApplication { + name: "type_0_component_a".to_string(), + type_decl: FullTypeDeclaration { + type_field: "type_1".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![FullTypeApplication { + name: "type_0_type_arg_0".to_string(), + type_decl: type_2_decl.clone(), + type_arguments: vec![], + },], + },], + type_parameters: vec![type_2_decl], + } + ) + } + + #[test] + fn can_convert_into_full_type_appl() { + let application = TypeApplication { + name: "ta_0".to_string(), + type_id: 0, + type_arguments: Some(vec![TypeApplication { + name: "ta_1".to_string(), + type_id: 1, + type_arguments: None, + }]), + }; + + let type_0 = TypeDeclaration { + type_id: 0, + type_field: "type_0".to_string(), + components: None, + type_parameters: None, + }; + + let type_1 = TypeDeclaration { + type_id: 1, + type_field: "type_1".to_string(), + components: None, + type_parameters: None, + }; + + let types = [&type_0, &type_1] + .into_iter() + .map(|ttype| (ttype.type_id, ttype.clone())) + .collect::>(); + + // given + let sut = FullTypeApplication::from_counterpart(&application, &types); + + // then + assert_eq!( + sut, + FullTypeApplication { + name: "ta_0".to_string(), + type_decl: FullTypeDeclaration { + type_field: "type_0".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![FullTypeApplication { + name: "ta_1".to_string(), + type_decl: FullTypeDeclaration { + type_field: "type_1".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![], + },], + } + ) + } +} diff --git a/packages/fuels-core/src/code_gen/abigen.rs b/packages/fuels-core/src/code_gen/abigen.rs index cf957404d5..491a6c35fd 100644 --- a/packages/fuels-core/src/code_gen/abigen.rs +++ b/packages/fuels-core/src/code_gen/abigen.rs @@ -1,590 +1,171 @@ -use super::{ - custom_types::{expand_custom_enum, expand_custom_struct, single_param_type_call}, - functions_gen::expand_function, - resolved_type::resolve_type, -}; +use std::collections::HashSet; -use crate::{ - code_gen::{ - bindings::ContractBindings, - functions_gen::{generate_predicate_encode_function, generate_script_main_function}, - }, - source::Source, - utils::ident, -}; -use fuel_abi_types::program_abi::{ABIFunction, ProgramABI, ResolvedLog, TypeDeclaration}; -use fuel_tx::ContractId; -use fuels_types::{ - bech32::Bech32ContractId, errors::Error, param_types::ParamType, utils::custom_type_name, -}; use inflector::Inflector; +use itertools::Itertools; use proc_macro2::TokenStream; use quote::quote; -use std::collections::HashMap; -pub enum AbigenType { - Contract, - Script, - Predicate, -} - -pub struct Abigen { - /// Format the code using a locally installed copy of `rustfmt`. - rustfmt: bool, - /// Generate no-std safe code - no_std: bool, - /// The contract or script name as an identifier. - name: String, +pub use abigen_target::{AbigenTarget, ProgramType}; +use fuels_types::errors::Error; - abi: ProgramABI, +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::abigen::abigen_target::ParsedAbigenTarget; +use crate::code_gen::abigen::bindings::generate_bindings; +use crate::code_gen::custom_types::generate_types; +use crate::code_gen::generated_code::GeneratedCode; +use crate::utils::ident; - types: HashMap, +mod abigen_target; +mod bindings; +mod logs; - abigen_type: AbigenType, -} +pub struct Abigen; impl Abigen { - /// Creates a new contract with the given ABI JSON source. - pub fn new>( - instance_name: &str, - abi_source: S, - abigen_type: AbigenType, - ) -> Result { - let source = Source::parse(abi_source).expect("failed to parse JSON ABI"); - - let json_abi_str = source.get().expect("failed to parse JSON ABI from string"); - let parsed_abi: ProgramABI = serde_json::from_str(&json_abi_str)?; - - Ok(Self { - types: Abigen::get_types(&parsed_abi), - abi: parsed_abi, - name: instance_name.to_string(), - rustfmt: true, - no_std: false, - abigen_type, - }) - } - - pub fn no_std(mut self) -> Self { - self.no_std = true; - self - } - - /// Generates the contract bindings. - pub fn generate(self) -> Result { - let rustfmt = self.rustfmt; - let tokens = self.expand_contract()?; - - Ok(ContractBindings { tokens, rustfmt }) - } - - pub fn expand(&self) -> Result { - match self.abigen_type { - AbigenType::Contract => self.expand_contract(), - AbigenType::Script => self.expand_script(), - AbigenType::Predicate => self.expand_predicate(), - } - } - - /// Entry point of the Abigen's expansion logic. - /// The high-level goal of this function is to expand* a contract defined as a JSON ABI - /// into type-safe bindings of that contract that can be used after it is brought into - /// scope after a successful generation. + /// Generate code which can be used to interact with the underlying + /// contract, script or predicate in a type-safe manner. /// - /// *: To expand, in procedural macro terms, means to automatically generate Rust code after a - /// transformation of `TokenStream` to another set of `TokenStream`. This generated Rust code is - /// the brought into scope after it is called through a procedural macro - /// (`abigen!()` in our case). - pub fn expand_contract(&self) -> Result { - let name = ident(&self.name); - let methods_name = ident(&format!("{}Methods", name)); - let name_mod = ident(&format!("{}_mod", self.name.to_string().to_snake_case())); - - let contract_functions = self.contract_functions()?; - let abi_structs = self.abi_structs()?; - let abi_enums = self.abi_enums()?; - - let resolved_logs = self.resolve_logs(); - let log_id_param_type_pairs = generate_log_id_param_type_pairs(&resolved_logs); - - let includes = self.includes(); - - let code = if self.no_std { - quote! {} - } else { - quote! { - pub struct #name { - contract_id: Bech32ContractId, - wallet: WalletUnlocked, - log_decoder: LogDecoder - } - - impl #name { - pub fn new(contract_id: Bech32ContractId, wallet: WalletUnlocked) -> Self { - Self { - contract_id: contract_id.clone(), - wallet, - log_decoder: LogDecoder { - logs_map: get_logs_hashmap(&[#(#log_id_param_type_pairs),*], - Some(contract_id)) - } - } - } - - pub fn get_contract_id(&self) -> &Bech32ContractId { - &self.contract_id - } - - pub fn get_wallet(&self) -> WalletUnlocked { - self.wallet.clone() - } - - pub fn with_wallet(&self, mut wallet: WalletUnlocked) -> Result { - let provider = self.wallet.get_provider()?; - wallet.set_provider(provider.clone()); - Ok(Self { - contract_id: self.contract_id.clone(), - wallet: wallet, - log_decoder: self.log_decoder.clone() - }) - } - - pub async fn get_balances(&self) -> Result, SDKError> { - self.wallet.get_provider()?.get_contract_balances(&self.contract_id).await.map_err(Into::into) - } - - pub fn methods(&self) -> #methods_name { - #methods_name { - contract_id: self.contract_id.clone(), - wallet: self.wallet.clone(), - log_decoder: self.log_decoder.clone() - } - } - } - - // Implement struct that holds the contract methods - pub struct #methods_name { - contract_id: Bech32ContractId, - wallet: WalletUnlocked, - log_decoder: LogDecoder - } + /// # 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 parsed_targets = Self::parse_targets(targets)?; - impl #methods_name { - #contract_functions - } + let generated_code = Self::generate_code(no_std, parsed_targets)?; - impl SettableContract for #name { - fn id(&self) -> Bech32ContractId{ - self.contract_id.clone() - } - fn log_decoder(&self) -> LogDecoder{ - self.log_decoder.clone() - } - } - } - }; + let use_statements = generated_code.use_statements_for_uniquely_named_types(); + let code = generated_code.code; Ok(quote! { - pub use #name_mod::*; - - #[allow(clippy::too_many_arguments)] - pub mod #name_mod { - #![allow(clippy::enum_variant_names)] - #![allow(dead_code)] - #![allow(unused_imports)] - - #includes - - #code - - #abi_structs - #abi_enums - - } + #code + #use_statements }) } - /// Expand a script into type-safe Rust bindings based on its ABI. See `expand_contract` for - /// more details. - pub fn expand_script(&self) -> Result { - let name = ident(&self.name); - let name_mod = ident(&format!("{}_mod", self.name.to_string().to_snake_case())); - - let includes = self.includes(); - let resolved_logs = self.resolve_logs(); - let log_id_param_type_pairs = generate_log_id_param_type_pairs(&resolved_logs); - - let main_script_function = self.main_function()?; - let code = if self.no_std { - quote! {} - } else { - quote! { - #[derive(Debug)] - pub struct #name{ - wallet: WalletUnlocked, - binary_filepath: String, - log_decoder: LogDecoder - } - - impl #name { - pub fn new(wallet: WalletUnlocked, binary_filepath: &str) -> Self { - Self { - wallet: wallet, - binary_filepath: binary_filepath.to_string(), - log_decoder: LogDecoder { - logs_map: get_logs_hashmap(&[#(#log_id_param_type_pairs),*], - None) - } - } - } - - #main_script_function - } - } - }; + fn generate_code( + no_std: bool, + parsed_targets: Vec, + ) -> Result { + let all_custom_types = Self::extract_custom_types(&parsed_targets); + let shared_types = Self::filter_shared_types(all_custom_types); - let abi_structs = self.abi_structs()?; - let abi_enums = self.abi_enums()?; - Ok(quote! { - pub use #name_mod::*; - - #[allow(clippy::too_many_arguments)] - pub mod #name_mod { - #![allow(clippy::enum_variant_names)] - #![allow(dead_code)] - - #includes - - #code + let bindings = Self::generate_all_bindings(parsed_targets, no_std, &shared_types)?; + let shared_types = Self::generate_shared_types(shared_types)?; - #abi_structs - #abi_enums - - } - }) + Ok(shared_types + .append(bindings) + .wrap_in_mod(&ident("abigen_bindings"))) } - /// Expand a predicate into type-safe Rust bindings based on its ABI. See `expand_contract` for - /// more details. - pub fn expand_predicate(&self) -> Result { - let name = ident(&self.name); - let name_mod = ident(&format!("{}_mod", self.name.to_string().to_snake_case())); - - let includes = self.includes(); - - let encode_data_function = self.main_function()?; - let code = if self.no_std { - quote! {} - } else { - quote! { - #[derive(Debug)] - pub struct #name{ - address: Bech32Address, - code: Vec, - data: UnresolvedBytes - } - - impl #name { - pub fn new(code: Vec) -> Self { - let address: Address = (*Contract::root_from_code(&code)).into(); - Self { - address: address.into(), - code, - data: UnresolvedBytes::new() - } - } - - pub fn load_from(file_path: &str) -> Result { - Ok(Self::new(std::fs::read(file_path)?)) - } - - pub fn address(&self) -> &Bech32Address { - &self.address - } - - pub fn code(&self) -> Vec { - self.code.clone() - } - - pub fn data(&self) -> UnresolvedBytes { - self.data.clone() - } - - pub async fn receive(&self, from: &WalletUnlocked, amount:u64, asset_id: AssetId, tx_parameters: Option) -> Result<(String, Vec), SDKError> { - let tx_parameters = tx_parameters.unwrap_or(TxParameters::default()); - from - .transfer( - self.address(), - amount, - asset_id, - tx_parameters - ) - .await - } - - pub async fn spend(&self, to: &WalletUnlocked, amount:u64, asset_id: AssetId, tx_parameters: Option) -> Result, SDKError> { - let tx_parameters = tx_parameters.unwrap_or(TxParameters::default()); - to - .receive_from_predicate( - self.address(), - self.code(), - amount, - asset_id, - self.data(), - tx_parameters, - ) - .await - } - - #encode_data_function - } - } - }; - - let abi_structs = self.abi_structs()?; - let abi_enums = self.abi_enums()?; - Ok(quote! { - pub use #name_mod::*; - - #[allow(clippy::too_many_arguments)] - pub mod #name_mod { - #![allow(clippy::enum_variant_names)] - #![allow(dead_code)] - - #includes - - #code + fn generate_all_bindings( + parsed_targets: Vec, + no_std: bool, + shared_types: &HashSet, + ) -> Result { + parsed_targets + .into_iter() + .map(|target| Self::generate_binding(target, no_std, shared_types)) + .fold_ok(GeneratedCode::default(), |acc, generated_code| { + acc.append(generated_code) + }) + } - #abi_structs - #abi_enums + fn generate_binding( + target: ParsedAbigenTarget, + no_std: bool, + shared_types: &HashSet, + ) -> Result { + let mod_name = ident(&format!("{}_mod", &target.name.to_snake_case())); - } - }) - } + let types = generate_types(target.source.types.clone(), shared_types)?; + let bindings = generate_bindings(target, no_std, shared_types)?; - /// Generates the includes necessary for the abigen. - fn includes(&self) -> TokenStream { - if self.no_std { - quote! { - use alloc::{vec, vec::Vec}; - use fuels_core::{ - code_gen::function_selector::resolve_fn_selector, try_from_bytes, types::*, - EnumSelector, Identity, Parameterize, Token, Tokenizable, - }; - use fuels_types::{ - enum_variants::EnumVariants, errors::Error as SDKError, - param_types::ParamType, - }; - } - } else { - let specific_includes = match self.abigen_type { - AbigenType::Contract => quote! { - use fuels::contract::contract::{ - get_decoded_output, Contract, ContractCallHandler, - }; - use fuels::core::{ - abi_decoder::ABIDecoder, code_gen::function_selector::resolve_fn_selector, - EnumSelector, Identity, StringToken, - }; - use std::str::FromStr; - }, - AbigenType::Script => quote! { - use fuels::{ - contract::script_calls::{ScriptCall, ScriptCallHandler}, - core::{abi_encoder::ABIEncoder, parameters::TxParameters}, - }; - use std::marker::PhantomData; - }, - AbigenType::Predicate => quote! { - use fuels::{ - core::{abi_encoder::{ABIEncoder, UnresolvedBytes}, parameters::TxParameters}, - tx::{Contract, AssetId}, - signers::provider::Provider - }; - }, - }; - quote! { - use fuels::contract::{contract::SettableContract, logs::LogDecoder}; - use fuels::core::{ - code_gen::get_logs_hashmap, try_from_bytes, types::*, Parameterize, Token, - Tokenizable, - }; - use fuels::signers::WalletUnlocked; - use fuels::tx::{Address, ContractId, Receipt}; - use fuels::types::{ - bech32::{Bech32ContractId, Bech32Address}, enum_variants::EnumVariants, - errors::Error as SDKError, param_types::ParamType, - }; - use std::collections::{HashMap, HashSet}; - #specific_includes - } - } + Ok(limited_std_prelude() + .append(types) + .append(bindings) + .wrap_in_mod(&mod_name)) } - pub fn contract_functions(&self) -> Result { - let tokenized_functions = self - .abi - .functions - .iter() - .map(|function| expand_function(function, &self.types)) - .collect::, Error>>()?; - Ok(quote! { #( #tokenized_functions )* }) + fn parse_targets(targets: Vec) -> Result, Error> { + targets + .into_iter() + .map(|target| target.try_into()) + .collect() } - pub fn main_function(&self) -> Result { - let functions = self - .abi - .functions - .iter() - .filter(|function| function.name == "main") - .collect::>(); - - if let [main_function] = functions.as_slice() { - let tokenized_function = match self.abigen_type { - AbigenType::Script => generate_script_main_function(main_function, &self.types), - - AbigenType::Predicate => { - generate_predicate_encode_function(main_function, &self.types) - } - AbigenType::Contract => Err(Error::CompilationError( - "Contract does not have a `main` function!".to_string(), - )), - }?; + fn generate_shared_types( + shared_types: HashSet, + ) -> Result { + let types = generate_types(shared_types, &HashSet::default())?; - Ok(quote! { #tokenized_function }) + if types.is_empty() { + Ok(Default::default()) } else { - Err(Error::CompilationError( - "Only one function named `main` allowed!".to_string(), - )) - } - } - - fn abi_structs(&self) -> Result { - let mut structs = TokenStream::new(); - - // Prevent expanding the same struct more than once - let mut seen_struct: Vec<&str> = vec![]; - - for prop in &self.abi.types { - // If it isn't a struct, skip. - if !prop.is_struct_type() { - continue; - } - - if Abigen::should_skip_codegen(&prop.type_field)? { - continue; - } - - if !seen_struct.contains(&prop.type_field.as_str()) { - structs.extend(expand_custom_struct(prop, &self.types)?); - seen_struct.push(&prop.type_field); - } + Ok(limited_std_prelude() + .append(types) + .wrap_in_mod(&ident("shared_types"))) } - - Ok(structs) } - // 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 'raw untyped ptr' or '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_field: &str) -> anyhow::Result { - let name = custom_type_name(type_field).unwrap_or_else(|_| type_field.to_string()); - - Ok([ - "ContractId", - "Address", - "Option", - "Identity", - "Result", - "Vec", - "raw untyped ptr", - "RawVec", - "EvmAddress", - "B512", - ] - .into_iter() - .any(|e| e == name)) - } - - fn abi_enums(&self) -> Result { - let mut enums = TokenStream::new(); - - // Prevent expanding the same enum more than once - let mut seen_enum: Vec<&str> = vec![]; - - for prop in &self.abi.types { - if !prop.is_enum_type() || Abigen::should_skip_codegen(&prop.type_field)? { - continue; - } - - if !seen_enum.contains(&prop.type_field.as_str()) { - enums.extend(expand_custom_enum(prop, &self.types)?); - seen_enum.push(&prop.type_field); - } - } - - Ok(enums) + fn extract_custom_types( + all_types: &[ParsedAbigenTarget], + ) -> impl Iterator { + all_types + .iter() + .flat_map(|target| &target.source.types) + .filter(|ttype| ttype.is_enum_type() || ttype.is_struct_type()) } - /// Reads the parsed ABI and returns all the types in it. - pub fn get_types(abi: &ProgramABI) -> HashMap { - abi.types.iter().map(|t| (t.type_id, t.clone())).collect() + /// 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() } +} - /// Reads the parsed logged types from the ABI and creates ResolvedLogs - fn resolve_logs(&self) -> Vec { - self.abi - .logged_types - .as_ref() - .into_iter() - .flatten() - .map(|l| { - let resolved_type = - resolve_type(&l.application, &self.types).expect("Failed to resolve log type"); - let param_type_call = single_param_type_call(&resolved_type); - let resolved_type_name = TokenStream::from(resolved_type); +fn limited_std_prelude() -> GeneratedCode { + let code = quote! { + use ::std::{ + clone::Clone, + convert::{Into, TryFrom, From}, + format, + iter::IntoIterator, + iter::Iterator, + marker::Sized, + panic, vec, + string::ToString + }; + }; - ResolvedLog { - log_id: l.log_id, - param_type_call, - resolved_type_name, - } - }) - .collect() + GeneratedCode { + code, + ..Default::default() } } -fn generate_log_id_param_type_pairs(resolved_logs: &[ResolvedLog]) -> Vec { - resolved_logs - .iter() - .map(|r| { - let id = r.log_id; - let param_type_call = &r.param_type_call; +#[cfg(test)] +mod tests { + use super::*; - quote! { - (#id, #param_type_call) - } - }) - .collect() -} + #[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![], + }); -pub fn get_logs_hashmap( - id_param_pairs: &[(u64, ParamType)], - contract_id: Option, -) -> HashMap<(Bech32ContractId, u64), ParamType> { - let contract_id = contract_id.unwrap_or_else(|| Bech32ContractId::from(ContractId::zeroed())); - id_param_pairs - .iter() - .map(|(id, param_type)| ((contract_id.clone(), *id), param_type.to_owned())) - .collect() -} + let shared_types = Abigen::filter_shared_types(&types); -// @todo all (or most, the applicable ones at least) tests in `abigen.rs` should be -// reimplemented for the new JSON ABI format. -// I (@digorithm) skipped writing these tests for now because all this is indirectly -// tested at a higher level in the main harness file. So, I incurred a bit of test debt here. -// Yet, we should test this code directly as well. + assert_eq!(shared_types, HashSet::from([types[0].clone()])) + } +} diff --git a/packages/fuels-core/src/code_gen/abigen/abigen_target.rs b/packages/fuels-core/src/code_gen/abigen/abigen_target.rs new file mode 100644 index 0000000000..00bd8449a0 --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/abigen_target.rs @@ -0,0 +1,44 @@ +use std::convert::TryFrom; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::FullProgramABI; +use crate::source::Source; + +#[derive(Debug, Clone)] +pub struct AbigenTarget { + pub name: String, + pub abi: String, + pub program_type: ProgramType, +} + +pub(crate) struct ParsedAbigenTarget { + pub name: String, + pub source: FullProgramABI, + pub program_type: ProgramType, +} + +impl TryFrom for ParsedAbigenTarget { + type Error = Error; + + fn try_from(value: AbigenTarget) -> Result { + Ok(Self { + name: value.name, + source: parse_program_abi(&value.abi)?, + program_type: value.program_type, + }) + } +} + +fn parse_program_abi(abi_source: &str) -> Result { + let source = Source::parse(abi_source).expect("failed to parse JSON ABI"); + let json_abi_str = source.get().expect("failed to parse JSON ABI from string"); + FullProgramABI::from_json_abi(&json_abi_str) +} + +#[derive(Debug, Clone, Copy)] +pub enum ProgramType { + Script, + Contract, + Predicate, +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings.rs b/packages/fuels-core/src/code_gen/abigen/bindings.rs new file mode 100644 index 0000000000..f095970b8a --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings.rs @@ -0,0 +1,33 @@ +use std::collections::HashSet; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::abigen::abigen_target::{ParsedAbigenTarget, ProgramType}; +use crate::code_gen::abigen::bindings::contract::contract_bindings; +use crate::code_gen::abigen::bindings::predicate::predicate_bindings; +use crate::code_gen::abigen::bindings::script::script_bindings; +use crate::code_gen::generated_code::GeneratedCode; +use crate::utils::ident; + +mod contract; +mod function_generator; +mod predicate; +mod script; +mod utils; + +pub(crate) fn generate_bindings( + target: ParsedAbigenTarget, + no_std: bool, + shared_types: &HashSet, +) -> 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; + bindings_generator(&name, abi, no_std, shared_types) +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings/contract.rs b/packages/fuels-core/src/code_gen/abigen/bindings/contract.rs new file mode 100644 index 0000000000..ba585e4cf9 --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings/contract.rs @@ -0,0 +1,535 @@ +use itertools::Itertools; +use std::collections::HashSet; + +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, TokenStreamExt}; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::{FullABIFunction, FullProgramABI, FullTypeDeclaration}; +use crate::code_gen::abigen::bindings::function_generator::FunctionGenerator; +use crate::code_gen::abigen::logs::logs_hashmap_instantiation_code; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::type_path::TypePath; +use crate::utils::ident; + +pub(crate) fn contract_bindings( + name: &Ident, + abi: FullProgramABI, + no_std: bool, + shared_types: &HashSet, +) -> Result { + if no_std { + return Ok(GeneratedCode::default()); + } + + let logs_map = logs_hashmap_instantiation_code( + Some(quote! {contract_id.clone()}), + &abi.logged_types, + shared_types, + ); + + let methods_name = ident(&format!("{}Methods", name)); + + let contract_functions = expand_functions(&abi.functions, shared_types)?; + + let code = quote! { + pub struct #name { + contract_id: ::fuels::types::bech32::Bech32ContractId, + wallet: ::fuels::signers::wallet::WalletUnlocked, + log_decoder: ::fuels::contract::logs::LogDecoder + } + + impl #name { + pub fn new(contract_id: ::fuels::types::bech32::Bech32ContractId, wallet: ::fuels::signers::wallet::WalletUnlocked) -> Self { + let log_decoder = ::fuels::contract::logs::LogDecoder { logs_map: #logs_map }; + Self { contract_id, wallet, log_decoder } + } + + pub fn get_contract_id(&self) -> &::fuels::types::bech32::Bech32ContractId { + &self.contract_id + } + + pub fn get_wallet(&self) -> ::fuels::signers::wallet::WalletUnlocked { + self.wallet.clone() + } + + pub fn with_wallet(&self, mut wallet: ::fuels::signers::wallet::WalletUnlocked) -> ::std::result::Result { + let provider = self.wallet.get_provider()?; + wallet.set_provider(provider.clone()); + + ::std::result::Result::Ok(Self { contract_id: self.contract_id.clone(), wallet: wallet, log_decoder: self.log_decoder.clone()}) + } + + pub async fn get_balances(&self) -> ::std::result::Result<::std::collections::HashMap<::std::string::String, u64>, ::fuels::types::errors::Error> { + self.wallet.get_provider()?.get_contract_balances(&self.contract_id).await.map_err(Into::into) + } + + pub fn methods(&self) -> #methods_name { + #methods_name { + contract_id: self.contract_id.clone(), + wallet: self.wallet.clone(), + log_decoder: self.log_decoder.clone() + } + } + } + + // Implement struct that holds the contract methods + pub struct #methods_name { + contract_id: ::fuels::types::bech32::Bech32ContractId, + wallet: ::fuels::signers::wallet::WalletUnlocked, + log_decoder: ::fuels::contract::logs::LogDecoder + } + + impl #methods_name { + #contract_functions + } + + impl ::fuels::contract::contract::SettableContract for #name { + fn id(&self) -> ::fuels::types::bech32::Bech32ContractId { + self.contract_id.clone() + } + fn log_decoder(&self) -> ::fuels::contract::logs::LogDecoder { + self.log_decoder.clone() + } + } + }; + + // All publicly available types generated above should be listed here. + let type_paths = [name, &methods_name] + .map(|type_name| TypePath::new(type_name).expect("We know the given types are not empty")) + .into_iter() + .collect(); + + Ok(GeneratedCode { + code, + usable_types: type_paths, + }) +} + +fn expand_functions( + functions: &[FullABIFunction], + shared_types: &HashSet, +) -> Result { + functions + .iter() + .map(|fun| expand_fn(fun, shared_types)) + .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. +/// +/// The generated function prepares the necessary data and proceeds to call +/// [::fuels_contract::contract::Contract::method_hash] for the actual call. +pub(crate) fn expand_fn( + abi_fun: &FullABIFunction, + shared_types: &HashSet, +) -> Result { + let mut generator = FunctionGenerator::new(abi_fun, shared_types)?; + + generator.set_doc(format!( + "Calls the contract's `{}` function", + abi_fun.name(), + )); + + let original_output = generator.output_type(); + generator.set_output_type( + quote! {::fuels::contract::contract::ContractCallHandler<#original_output> }, + ); + + let fn_selector = generator.fn_selector(); + let arg_tokens = generator.tokenized_args(); + let body = quote! { + let provider = self.wallet.get_provider().expect("Provider not set up"); + ::fuels::contract::contract::Contract::method_hash( + &provider, + self.contract_id.clone(), + &self.wallet, + #fn_selector, + &#arg_tokens, + self.log_decoder.clone() + ) + .expect("method not found (this should never happen)") + }; + generator.set_body(body); + + Ok(generator.into()) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use fuel_abi_types::program_abi::{ABIFunction, ProgramABI, TypeApplication, TypeDeclaration}; + + use super::*; + + #[test] + fn test_expand_fn_simple_abi() -> Result<(), Error> { + let s = r#" + { + "types": [ + { + "typeId": 6, + "type": "u64", + "components": null, + "typeParameters": null + }, + { + "typeId": 8, + "type": "b256", + "components": null, + "typeParameters": null + }, + { + "typeId": 6, + "type": "u64", + "components": null, + "typeParameters": null + }, + { + "typeId": 8, + "type": "b256", + "components": null, + "typeParameters": null + }, + { + "typeId": 10, + "type": "bool", + "components": null, + "typeParameters": null + }, + { + "typeId": 12, + "type": "struct MyStruct1", + "components": [ + { + "name": "x", + "type": 6, + "typeArguments": null + }, + { + "name": "y", + "type": 8, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 6, + "type": "u64", + "components": null, + "typeParameters": null + }, + { + "typeId": 8, + "type": "b256", + "components": null, + "typeParameters": null + }, + { + "typeId": 2, + "type": "struct MyStruct1", + "components": [ + { + "name": "x", + "type": 6, + "typeArguments": null + }, + { + "name": "y", + "type": 8, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 3, + "type": "struct MyStruct2", + "components": [ + { + "name": "x", + "type": 10, + "typeArguments": null + }, + { + "name": "y", + "type": 12, + "typeArguments": [] + } + ], + "typeParameters": null + }, + { + "typeId": 26, + "type": "struct MyStruct1", + "components": [ + { + "name": "x", + "type": 6, + "typeArguments": null + }, + { + "name": "y", + "type": 8, + "typeArguments": null + } + ], + "typeParameters": null + } + ], + "functions": [ + { + "type": "function", + "inputs": [ + { + "name": "s1", + "type": 2, + "typeArguments": [] + }, + { + "name": "s2", + "type": 3, + "typeArguments": [] + } + ], + "name": "some_abi_funct", + "output": { + "name": "", + "type": 26, + "typeArguments": [] + } + } + ] + } + "#; + let parsed_abi: ProgramABI = serde_json::from_str(s)?; + let types = parsed_abi + .types + .into_iter() + .map(|t| (t.type_id, t)) + .collect::>(); + + // Grabbing the one and only function in it. + let result = expand_fn( + &FullABIFunction::from_counterpart(&parsed_abi.functions[0], &types)?, + &HashSet::default(), + )?; + + let expected = quote! { + #[doc = "Calls the contract's `some_abi_funct` function"] + pub fn some_abi_funct( + &self, + s_1: self::MyStruct1, + s_2: self::MyStruct2 + ) -> ::fuels::contract::contract::ContractCallHandler { + let provider = self.wallet.get_provider().expect("Provider not set up"); + ::fuels::contract::contract::Contract::method_hash( + &provider, + self.contract_id.clone(), + &self.wallet, + ::fuels::core::code_gen::function_selector::resolve_fn_selector( + "some_abi_funct", + &[ + ::param_type(), + ::param_type() + ] + ), + &[ + ::fuels::core::Tokenizable::into_token(s_1), + ::fuels::core::Tokenizable::into_token(s_2) + ], + self.log_decoder.clone() + ) + .expect("method not found (this should never happen)") + } + }; + + assert_eq!(result.to_string(), expected.to_string()); + + Ok(()) + } + + #[test] + fn test_expand_fn_simple() -> Result<(), Error> { + let the_function = ABIFunction { + inputs: vec![TypeApplication { + name: String::from("bimbam"), + type_id: 1, + ..Default::default() + }], + name: "HelloWorld".to_string(), + ..Default::default() + }; + let types = [ + ( + 0, + TypeDeclaration { + type_id: 0, + type_field: String::from("()"), + ..Default::default() + }, + ), + ( + 1, + TypeDeclaration { + type_id: 1, + type_field: String::from("bool"), + ..Default::default() + }, + ), + ] + .into_iter() + .collect::>(); + let result = expand_fn( + &FullABIFunction::from_counterpart(&the_function, &types)?, + &HashSet::default(), + ); + + let expected = quote! { + #[doc = "Calls the contract's `HelloWorld` function"] + pub fn HelloWorld(&self, bimbam: bool) -> ::fuels::contract::contract::ContractCallHandler<()> { + let provider = self.wallet.get_provider().expect("Provider not set up"); + ::fuels::contract::contract::Contract::method_hash( + &provider, + self.contract_id.clone(), + &self.wallet, + ::fuels::core::code_gen::function_selector::resolve_fn_selector( + "HelloWorld", + &[::param_type()] + ), + &[::fuels::core::Tokenizable::into_token(bimbam)], + self.log_decoder.clone() + ) + .expect("method not found (this should never happen)") + } + }; + + assert_eq!(result?.to_string(), expected.to_string()); + + Ok(()) + } + + #[test] + fn test_expand_fn_complex() -> Result<(), Error> { + // given + let the_function = ABIFunction { + inputs: vec![TypeApplication { + name: String::from("the_only_allowed_input"), + type_id: 4, + ..Default::default() + }], + name: "hello_world".to_string(), + output: TypeApplication { + name: String::from("stillnotused"), + type_id: 1, + ..Default::default() + }, + ..Default::default() + }; + let types = [ + ( + 1, + TypeDeclaration { + type_id: 1, + type_field: String::from("enum EntropyCirclesEnum"), + components: Some(vec![ + TypeApplication { + name: String::from("Postcard"), + type_id: 2, + ..Default::default() + }, + TypeApplication { + name: String::from("Teacup"), + type_id: 3, + ..Default::default() + }, + ]), + ..Default::default() + }, + ), + ( + 2, + TypeDeclaration { + type_id: 2, + type_field: String::from("bool"), + ..Default::default() + }, + ), + ( + 3, + TypeDeclaration { + type_id: 3, + type_field: String::from("u64"), + ..Default::default() + }, + ), + ( + 4, + TypeDeclaration { + type_id: 4, + type_field: String::from("struct SomeWeirdFrenchCuisine"), + components: Some(vec![ + TypeApplication { + name: String::from("Beef"), + type_id: 2, + ..Default::default() + }, + TypeApplication { + 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)?, + &HashSet::default(), + ); + + //then + + // Some more editing was required because it is not rustfmt-compatible (adding/removing parentheses or commas) + let expected = quote! { + #[doc = "Calls the contract's `hello_world` function"] + pub fn hello_world( + &self, + the_only_allowed_input: self::SomeWeirdFrenchCuisine + ) -> ::fuels::contract::contract::ContractCallHandler { + let provider = self.wallet.get_provider().expect("Provider not set up"); + ::fuels::contract::contract::Contract::method_hash( + &provider, + self.contract_id.clone(), + &self.wallet, + ::fuels::core::code_gen::function_selector::resolve_fn_selector( + "hello_world", + &[::param_type()] + ), + &[::fuels::core::Tokenizable::into_token( + the_only_allowed_input + )], + self.log_decoder.clone() + ) + .expect("method not found (this should never happen)") + } + }; + + assert_eq!(result?.to_string(), expected.to_string()); + + Ok(()) + } +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings/function_generator.rs b/packages/fuels-core/src/code_gen/abigen/bindings/function_generator.rs new file mode 100644 index 0000000000..08235d33a8 --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings/function_generator.rs @@ -0,0 +1,419 @@ +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::quote; + +use fuels_types::errors::Error; +use resolved_type::resolve_type; + +use crate::code_gen::abi_types::{FullABIFunction, FullTypeApplication, FullTypeDeclaration}; +use crate::code_gen::utils::{param_type_calls, Component}; +use crate::code_gen::{resolved_type, resolved_type::ResolvedType}; +use crate::utils::safe_ident; + +#[derive(Debug)] +pub(crate) struct FunctionGenerator { + name: String, + args: Vec, + output_type: TokenStream, + body: TokenStream, + doc: Option, +} + +impl FunctionGenerator { + pub fn new( + fun: &FullABIFunction, + shared_types: &HashSet, + ) -> Result { + let args = function_arguments(fun.inputs(), shared_types)?; + + let output_type = resolve_fn_output_type(fun, shared_types)?; + + Ok(Self { + name: fun.name().to_string(), + args, + output_type: output_type.into(), + body: Default::default(), + doc: None, + }) + } + + 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_doc(&mut self, text: String) -> &mut Self { + self.doc = Some(text); + self + } + + pub fn fn_selector(&self) -> TokenStream { + let param_type_calls = param_type_calls(&self.args); + + let name = &self.name; + quote! {::fuels::core::code_gen::function_selector::resolve_fn_selector(#name, &[#(#param_type_calls),*])} + } + + pub fn tokenized_args(&self) -> TokenStream { + let arg_names = self.args.iter().map(|component| &component.field_name); + quote! {[#(::fuels::core::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 + } +} + +fn function_arguments( + inputs: &[FullTypeApplication], + shared_types: &HashSet, +) -> Result, Error> { + inputs + .iter() + .map(|input| Component::new(input, true, shared_types)) + .collect::>() + .map_err(|e| Error::InvalidType(e.to_string())) +} + +fn resolve_fn_output_type( + function: &FullABIFunction, + shared_types: &HashSet, +) -> Result { + let output_type = resolve_type(function.output(), shared_types)?; + if output_type.uses_vectors() { + Err(Error::CompilationError(format!( + "function '{}' contains a vector in its return type. This currently isn't supported.", + function.name() + ))) + } else { + Ok(output_type) + } +} + +impl From<&FunctionGenerator> for TokenStream { + fn from(fun: &FunctionGenerator) -> Self { + let name = safe_ident(&fun.name); + let doc = fun + .doc + .as_ref() + .map(|text| { + quote! { #[doc = #text] } + }) + .unwrap_or_default(); + + let arg_declarations = fun.args.iter().map(|component| { + let name = &component.field_name; + let field_type: TokenStream = (&component.field_type).into(); + quote! { #name: #field_type } + }); + + let output_type = fun.output_type(); + let body = &fun.body; + + quote! { + #doc + pub fn #name(&self #(,#arg_declarations)*) -> #output_type { + #body + } + } + } +} + +impl From for TokenStream { + fn from(fun: FunctionGenerator) -> Self { + (&fun).into() + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use fuel_abi_types::program_abi::{ABIFunction, TypeApplication, TypeDeclaration}; + + use super::*; + + #[test] + fn test_expand_fn_arguments() -> Result<(), Error> { + let the_argument = TypeApplication { + name: "some_argument".to_string(), + type_id: 0, + ..Default::default() + }; + + // All arguments are here + let the_function = ABIFunction { + inputs: vec![the_argument], + name: "some_fun".to_string(), + ..ABIFunction::default() + }; + + let types = [( + 0, + TypeDeclaration { + type_id: 0, + type_field: String::from("u32"), + ..Default::default() + }, + )] + .into_iter() + .collect::>(); + let result = function_arguments( + FullABIFunction::from_counterpart(&the_function, &types)?.inputs(), + &HashSet::default(), + )?; + let component = &result[0]; + + assert_eq!(&component.field_name.to_string(), "some_argument"); + assert_eq!(&component.field_type.to_string(), "u32"); + + Ok(()) + } + + #[test] + fn test_expand_fn_arguments_primitive() -> Result<(), Error> { + let the_function = ABIFunction { + inputs: vec![TypeApplication { + name: "bim_bam".to_string(), + type_id: 1, + ..Default::default() + }], + name: "pip_pop".to_string(), + ..Default::default() + }; + + let types = [ + ( + 0, + TypeDeclaration { + type_id: 0, + type_field: String::from("()"), + ..Default::default() + }, + ), + ( + 1, + TypeDeclaration { + type_id: 1, + type_field: String::from("u64"), + ..Default::default() + }, + ), + ] + .into_iter() + .collect::>(); + let result = function_arguments( + FullABIFunction::from_counterpart(&the_function, &types)?.inputs(), + &HashSet::default(), + )?; + let component = &result[0]; + + assert_eq!(&component.field_name.to_string(), "bim_bam"); + assert_eq!(&component.field_type.to_string(), "u64"); + + Ok(()) + } + + #[test] + fn test_expand_fn_arguments_composite() -> Result<(), Error> { + let mut function = ABIFunction { + inputs: vec![TypeApplication { + name: "bim_bam".to_string(), + type_id: 0, + ..Default::default() + }], + name: "PipPopFunction".to_string(), + ..Default::default() + }; + + let types = [ + ( + 0, + TypeDeclaration { + type_id: 0, + type_field: "struct CarMaker".to_string(), + components: Some(vec![TypeApplication { + name: "name".to_string(), + type_id: 1, + ..Default::default() + }]), + ..Default::default() + }, + ), + ( + 1, + TypeDeclaration { + type_id: 1, + type_field: "str[5]".to_string(), + ..Default::default() + }, + ), + ( + 2, + TypeDeclaration { + type_id: 2, + type_field: "enum Cocktail".to_string(), + components: Some(vec![TypeApplication { + name: "variant".to_string(), + type_id: 3, + ..Default::default() + }]), + ..Default::default() + }, + ), + ( + 3, + TypeDeclaration { + type_id: 3, + type_field: "u32".to_string(), + ..Default::default() + }, + ), + ] + .into_iter() + .collect::>(); + let result = function_arguments( + FullABIFunction::from_counterpart(&function, &types)?.inputs(), + &HashSet::default(), + )?; + + assert_eq!(&result[0].field_name.to_string(), "bim_bam"); + assert_eq!(&result[0].field_type.to_string(), "self :: CarMaker"); + + function.inputs[0].type_id = 2; + let result = function_arguments( + FullABIFunction::from_counterpart(&function, &types)?.inputs(), + &HashSet::default(), + )?; + + assert_eq!(&result[0].field_name.to_string(), "bim_bam"); + assert_eq!(&result[0].field_type.to_string(), "self :: Cocktail"); + + Ok(()) + } + + #[test] + fn correct_output_type() -> Result<(), Error> { + let function = given_a_fun(); + let sut = FunctionGenerator::new(&function, &HashSet::default())?; + + let output_type = sut.output_type(); + + assert_eq!(output_type.to_string(), "self :: CustomStruct < u64 >"); + + Ok(()) + } + + #[test] + fn correct_fn_selector_resolving_code() -> Result<(), Error> { + let function = given_a_fun(); + let sut = FunctionGenerator::new(&function, &HashSet::default())?; + + let fn_selector_code = sut.fn_selector(); + + assert_eq!( + fn_selector_code.to_string(), + r#":: fuels :: core :: code_gen :: function_selector :: resolve_fn_selector ("test_function" , & [< self :: CustomStruct :: < u8 > as :: fuels :: core :: Parameterize > :: param_type ()])"# + ); + + Ok(()) + } + + #[test] + fn correct_tokenized_args() -> Result<(), Error> { + let function = given_a_fun(); + let sut = FunctionGenerator::new(&function, &HashSet::default())?; + + let tokenized_args = sut.tokenized_args(); + + assert_eq!( + tokenized_args.to_string(), + "[:: fuels :: core :: Tokenizable :: into_token (arg_0)]" + ); + + Ok(()) + } + + #[test] + fn tokenizes_correctly() -> Result<(), Error> { + // given + let function = given_a_fun(); + let mut sut = FunctionGenerator::new(&function, &HashSet::default())?; + + sut.set_doc("This is a doc".to_string()) + .set_body(quote! {this is ze body}); + + // when + let tokenized: TokenStream = sut.into(); + + // then + let expected = quote! { + #[doc = "This is a doc"] + pub fn test_function(&self, arg_0: self::CustomStruct) -> self::CustomStruct { + 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![], + }; + 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![], + }], + type_parameters: vec![generic_type_t], + }; + + 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![], + }, + type_arguments: vec![], + }], + }; + 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![], + }, + type_arguments: vec![], + }], + }]; + + FullABIFunction::new("test_function".to_string(), fn_inputs, fn_output) + .expect("Hand crafted function known to be correct") + } +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings/predicate.rs b/packages/fuels-core/src/code_gen/abigen/bindings/predicate.rs new file mode 100644 index 0000000000..700a3cbabb --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings/predicate.rs @@ -0,0 +1,132 @@ +use std::collections::HashSet; + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::{FullProgramABI, FullTypeDeclaration}; +use crate::code_gen::abigen::bindings::function_generator::FunctionGenerator; +use crate::code_gen::abigen::bindings::utils::extract_main_fn; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::type_path::TypePath; + +pub(crate) fn predicate_bindings( + name: &Ident, + abi: FullProgramABI, + no_std: bool, + shared_types: &HashSet, +) -> Result { + if no_std { + return Ok(GeneratedCode::default()); + } + + let encode_function = expand_fn(&abi, shared_types)?; + + let code = quote! { + #[derive(Debug)] + pub struct #name { + address: ::fuels::types::bech32::Bech32Address, + code: ::std::vec::Vec, + data: ::fuels::core::abi_encoder::UnresolvedBytes + } + + impl #name { + pub fn new(code: ::std::vec::Vec) -> Self { + let address: ::fuels::core::types::Address = (*::fuels::tx::Contract::root_from_code(&code)).into(); + Self { + address: address.into(), + code, + data: ::fuels::core::abi_encoder::UnresolvedBytes::new() + } + } + + pub fn load_from(file_path: &str) -> ::std::result::Result { + ::std::result::Result::Ok(Self::new(::std::fs::read(file_path)?)) + } + + pub fn address(&self) -> &::fuels::types::bech32::Bech32Address { + &self.address + } + + pub fn code(&self) -> ::std::vec::Vec { + self.code.clone() + } + + pub fn data(&self) -> ::fuels::core::abi_encoder::UnresolvedBytes { + self.data.clone() + } + + pub async fn receive(&self, from: &::fuels::signers::wallet::WalletUnlocked, + amount: u64, + asset_id: ::fuels::core::types::AssetId, + tx_parameters: ::std::option::Option<::fuels::core::parameters::TxParameters> + ) -> ::std::result::Result<(::std::string::String, ::std::vec::Vec<::fuels::tx::Receipt>), ::fuels::types::errors::Error> { + let tx_parameters = tx_parameters.unwrap_or_default(); + from + .transfer( + self.address(), + amount, + asset_id, + tx_parameters + ) + .await + } + + pub async fn spend(&self, to: &::fuels::signers::wallet::WalletUnlocked, + amount: u64, + asset_id: ::fuels::core::types::AssetId, + tx_parameters: ::std::option::Option<::fuels::core::parameters::TxParameters> + ) -> ::std::result::Result<::std::vec::Vec<::fuels::tx::Receipt>, ::fuels::types::errors::Error> { + let tx_parameters = tx_parameters.unwrap_or_default(); + to + .receive_from_predicate( + self.address(), + self.code(), + amount, + asset_id, + self.data(), + tx_parameters, + ) + .await + } + + #encode_function + } + }; + + // All publicly available types generated above should be listed here. + let type_paths = [TypePath::new(name).expect("We know name is not empty.")].into(); + + Ok(GeneratedCode { + code, + usable_types: type_paths, + }) +} + +fn expand_fn( + abi: &FullProgramABI, + shared_types: &HashSet, +) -> Result { + let fun = extract_main_fn(&abi.functions)?; + let mut generator = FunctionGenerator::new(fun, shared_types)?; + + let arg_tokens = generator.tokenized_args(); + let body = quote! { + let data = ::fuels::core::abi_encoder::ABIEncoder::encode(&#arg_tokens).expect("Cannot encode predicate data"); + + Self { + address: self.address.clone(), + code: self.code.clone(), + data + } + }; + + generator + .set_doc("Run the predicate's encode function with the provided arguments".to_string()) + .set_name("encode_data".to_string()) + .set_output_type(quote! {Self}) + .set_body(body); + + Ok(generator.into()) +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings/script.rs b/packages/fuels-core/src/code_gen/abigen/bindings/script.rs new file mode 100644 index 0000000000..f9e27e6292 --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings/script.rs @@ -0,0 +1,92 @@ +use std::collections::HashSet; + +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::{FullProgramABI, FullTypeDeclaration}; +use crate::code_gen::abigen::bindings::function_generator::FunctionGenerator; +use crate::code_gen::abigen::bindings::utils::extract_main_fn; +use crate::code_gen::abigen::logs::logs_hashmap_instantiation_code; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::type_path::TypePath; + +pub(crate) fn script_bindings( + name: &Ident, + abi: FullProgramABI, + no_std: bool, + shared_types: &HashSet, +) -> Result { + if no_std { + return Ok(GeneratedCode::default()); + } + + let main_function = expand_fn(&abi, shared_types)?; + + let logs_map = logs_hashmap_instantiation_code(None, &abi.logged_types, shared_types); + + let code = quote! { + #[derive(Debug)] + pub struct #name{ + wallet: ::fuels::signers::wallet::WalletUnlocked, + binary_filepath: ::std::string::String, + log_decoder: ::fuels::contract::logs::LogDecoder + } + + impl #name { + pub fn new(wallet: ::fuels::signers::wallet::WalletUnlocked, binary_filepath: &str) -> Self { + Self { + wallet, + binary_filepath: binary_filepath.to_string(), + log_decoder: ::fuels::contract::logs::LogDecoder {logs_map: #logs_map} + } + } + + #main_function + } + }; + + // All publicly available types generated above should be listed here. + let type_paths = [TypePath::new(name).expect("We know name is not empty.")].into(); + + Ok(GeneratedCode { + code, + usable_types: type_paths, + }) +} + +fn expand_fn( + abi: &FullProgramABI, + shared_types: &HashSet, +) -> Result { + let fun = extract_main_fn(&abi.functions)?; + let mut generator = FunctionGenerator::new(fun, shared_types)?; + + let arg_tokens = generator.tokenized_args(); + let body = quote! { + let script_binary = ::std::fs::read(&self.binary_filepath) + .expect("Could not read from binary filepath"); + let encoded_args = ::fuels::core::abi_encoder::ABIEncoder::encode(&#arg_tokens).expect("Cannot encode script arguments"); + let provider = self.wallet.get_provider().expect("Provider not set up").clone(); + + ::fuels::contract::script_calls::ScriptCallHandler::new( + script_binary, + encoded_args, + self.wallet.clone(), + provider, + self.log_decoder.clone() + ) + }; + + let original_output_type = generator.output_type(); + + generator + .set_output_type( + quote! {::fuels::contract::script_calls::ScriptCallHandler<#original_output_type> }, + ) + .set_doc("Run the script's `main` function with the provided arguments".to_string()) + .set_body(body); + + Ok(generator.into()) +} diff --git a/packages/fuels-core/src/code_gen/abigen/bindings/utils.rs b/packages/fuels-core/src/code_gen/abigen/bindings/utils.rs new file mode 100644 index 0000000000..5c09a8ea6f --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/bindings/utils.rs @@ -0,0 +1,71 @@ +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::FullABIFunction; + +pub(crate) fn extract_main_fn(abi: &[FullABIFunction]) -> Result<&FullABIFunction, Error> { + 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::CompilationError(format!( + "ABI must have one and only one function with the name 'main'. Got: {fn_names:?}" + ))) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::code_gen::abi_types::{FullTypeApplication, FullTypeDeclaration}; + + #[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."); + + if let Error::CompilationError(msg) = err { + assert_eq!( + msg, + r#"ABI must have one and only one function with the name 'main'. Got: ["main", "another", "main"]"# + ); + } else { + panic!("Should have received a CompilationError!"); + } + } + + 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![], + }, + type_arguments: vec![], + }, + ) + .expect("hand-crafted, should not fail!") + } +} diff --git a/packages/fuels-core/src/code_gen/abigen/logs.rs b/packages/fuels-core/src/code_gen/abigen/logs.rs new file mode 100644 index 0000000000..d1a0414a9e --- /dev/null +++ b/packages/fuels-core/src/code_gen/abigen/logs.rs @@ -0,0 +1,61 @@ +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::quote; + +use fuel_abi_types::program_abi::ResolvedLog; + +use crate::code_gen::{ + abi_types::{FullLoggedType, FullTypeDeclaration}, + resolved_type::resolve_type, + utils::single_param_type_call, +}; + +pub(crate) fn logs_hashmap_instantiation_code( + contract_id: Option, + logged_types: &[FullLoggedType], + shared_types: &HashSet, +) -> TokenStream { + let resolved_logs = resolve_logs(logged_types, shared_types); + let log_id_param_type_pairs = generate_log_id_param_type_pairs(&resolved_logs); + let contract_id = contract_id + .map(|id| quote! { ::std::option::Option::Some(#id) }) + .unwrap_or_else(|| quote! {::std::option::Option::None}); + quote! {::fuels::core::get_logs_hashmap(&[#(#log_id_param_type_pairs),*], #contract_id)} +} + +/// Reads the parsed logged types from the ABI and creates ResolvedLogs +fn resolve_logs( + logged_types: &[FullLoggedType], + shared_types: &HashSet, +) -> Vec { + logged_types + .iter() + .map(|l| { + let resolved_type = + resolve_type(&l.application, shared_types).expect("Failed to resolve log type"); + let param_type_call = single_param_type_call(&resolved_type); + let resolved_type_name = TokenStream::from(resolved_type); + + ResolvedLog { + log_id: l.log_id, + param_type_call, + resolved_type_name, + } + }) + .collect() +} + +fn generate_log_id_param_type_pairs(resolved_logs: &[ResolvedLog]) -> Vec { + resolved_logs + .iter() + .map(|r| { + let id = r.log_id; + let param_type_call = &r.param_type_call; + + quote! { + (#id, #param_type_call) + } + }) + .collect() +} diff --git a/packages/fuels-core/src/code_gen/bindings.rs b/packages/fuels-core/src/code_gen/bindings.rs deleted file mode 100644 index 15a0931441..0000000000 --- a/packages/fuels-core/src/code_gen/bindings.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::rustfmt; -use fuels_types::errors::Error; -use proc_macro2::TokenStream; -use std::{fs::File, io::Write, path::Path}; - -/// Type-safe contract bindings generated by a `Builder`. This type can be -/// either written to file or into a token stream for use in a procedural macro. -pub struct ContractBindings { - /// The TokenStream representing the contract bindings. - pub tokens: TokenStream, - /// The output options used for serialization. - pub rustfmt: bool, -} - -impl ContractBindings { - /// Writes the bindings to a given `Write`. - pub fn write(&self, mut w: W) -> Result<(), Error> - where - W: Write, - { - let source = { - let raw = self.tokens.to_string(); - - if self.rustfmt { - rustfmt::format(&raw).unwrap_or(raw) - } else { - raw - } - }; - - w.write_all(source.as_bytes()).unwrap(); - Ok(()) - } - - /// Writes the bindings to the specified file. - pub fn write_to_file

(&self, path: P) -> Result<(), Error> - where - P: AsRef, - { - let file = File::create(path).unwrap(); - self.write(file) - } - - /// Converts the bindings into its underlying token stream. This allows it - /// to be used within a procedural macro. - pub fn into_tokens(self) -> TokenStream { - self.tokens - } -} diff --git a/packages/fuels-core/src/code_gen/custom_types.rs b/packages/fuels-core/src/code_gen/custom_types.rs index 81f7323fd5..c81601bdc2 100644 --- a/packages/fuels-core/src/code_gen/custom_types.rs +++ b/packages/fuels-core/src/code_gen/custom_types.rs @@ -1,11 +1,74 @@ -mod enum_gen; -mod struct_gen; +use std::collections::HashSet; + +use itertools::Itertools; + +use fuels_types::errors::Error; +use fuels_types::utils::custom_type_name; + +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::custom_types::enums::expand_custom_enum; +use crate::code_gen::custom_types::structs::expand_custom_struct; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::utils::get_sdk_provided_types; + +mod enums; +mod structs; mod utils; -pub use enum_gen::expand_custom_enum; -pub use fuels_types::utils::custom_type_name; -pub use struct_gen::expand_custom_struct; -pub use utils::{param_type_calls, single_param_type_call, Component}; +/// 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>( + types: T, + shared_types: &HashSet, +) -> Result { + HashSet::from_iter(types) + .difference(shared_types) + .filter(|ttype| !should_skip_codegen(&ttype.type_field)) + .filter_map(|ttype| { + if ttype.is_struct_type() { + Some(expand_custom_struct(ttype, shared_types)) + } else if ttype.is_enum_type() { + Some(expand_custom_enum(ttype, shared_types)) + } else { + None + } + }) + .fold_ok(GeneratedCode::default(), |acc, generated_code| { + acc.append(generated_code) + }) +} + +// 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 'raw untyped ptr' or 'RawVec' are skipped because they are +// implementation details of the contract's Vec type and are not directly +// used in the SDK. +fn should_skip_codegen(type_field: &str) -> bool { + let name = custom_type_name(type_field).unwrap_or_else(|_| type_field.to_string()); + + is_type_sdk_provided(&name) || is_type_unused(&name) +} + +fn is_type_sdk_provided(name: &str) -> bool { + get_sdk_provided_types() + .iter() + .any(|type_path| type_path.type_name() == name) +} + +fn is_type_unused(name: &str) -> bool { + ["raw untyped ptr", "RawVec"].contains(&name) +} // 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. @@ -16,13 +79,22 @@ pub use utils::{param_type_calls, single_param_type_call, Component}; // TODO(iqdecay): append extra `,` to last enum/struct field so it is aligned with rustfmt #[cfg(test)] mod tests { - use super::*; - use crate::Error; - use fuel_abi_types::program_abi::{ProgramABI, TypeApplication, TypeDeclaration}; - use std::{collections::HashMap, str::FromStr}; + use std::{ + collections::{HashMap, HashSet}, + str::FromStr, + }; use anyhow::anyhow; use proc_macro2::TokenStream; + use quote::quote; + + use crate::code_gen::{ + abi_types::{FullTypeApplication, FullTypeDeclaration}, + type_path::TypePath, + }; + use fuel_abi_types::program_abi::{ProgramABI, TypeApplication, TypeDeclaration}; + + use super::*; #[test] fn test_expand_custom_enum() -> Result<(), Error> { @@ -64,15 +136,106 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_enum(&p, &types)?.to_string(); - let expected = TokenStream::from_str( - r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub enum MatchaTea < > { LongIsland (u64) , MoscowMule (bool) } impl < > Parameterize for MatchaTea < > { fn param_type () -> ParamType { let variants = [("LongIsland" . to_string () , < u64 > :: param_type ()) , ("MoscowMule" . to_string () , < bool > :: param_type ())] . to_vec () ; let variants = EnumVariants :: new (variants) . unwrap_or_else (| _ | panic ! ("{} has no variants which isn't allowed!" , "MatchaTea")) ; ParamType :: Enum { name : "MatchaTea" . to_string () , variants , generics : [] . to_vec () } } } impl < > Tokenizable for MatchaTea < > { fn from_token (token : Token) -> Result < Self , SDKError > where Self : Sized , { let gen_err = | msg | { SDKError :: InvalidData (format ! ("Error while instantiating {} from token! {}" , "MatchaTea" , msg)) } ; match token { Token :: Enum (selector) => { let (discriminant , variant_token , _) = * selector ; match discriminant { 0u8 => Ok (Self :: LongIsland (< u64 > :: from_token (variant_token) ?)) , 1u8 => Ok (Self :: MoscowMule (< bool > :: from_token (variant_token) ?)) , _ => Err (gen_err (format ! ("Discriminant {} doesn't point to any of the enums variants." , discriminant))) , } } _ => Err (gen_err (format ! ("Given token ({}) is not of the type Token::Enum!" , token))) , } } fn into_token (self) -> Token { let (discriminant , token) = match self { Self :: LongIsland (inner) => (0u8 , inner . into_token ()) , Self :: MoscowMule (inner) => (1u8 , inner . into_token ()) } ; let variants = match Self :: param_type () { ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "MatchaTea" , other) } ; Token :: Enum (Box :: new ((discriminant , token , variants))) } } impl < > TryFrom < & [u8] > for MatchaTea < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for MatchaTea < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for MatchaTea < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } - "#, + let actual = expand_custom_enum( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), )? - .to_string(); + .code; - assert_eq!(actual, expected); + let expected = quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum MatchaTea<> { + LongIsland(u64), + MoscowMule(bool) + } + impl<> ::fuels::core::Parameterize for self::MatchaTea<> { + fn param_type() -> ::fuels::types::param_types::ParamType { + let variants = [ + ( + "LongIsland".to_string(), + ::param_type() + ), + ( + "MoscowMule".to_string(), + ::param_type() + ) + ] + .to_vec(); + let variants = ::fuels::types::enum_variants::EnumVariants::new(variants) + .unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", "MatchaTea")); + ::fuels::types::param_types::ParamType::Enum { + name: "MatchaTea".to_string(), + variants, + generics: [].to_vec() + } + } + } + impl<> ::fuels::core::Tokenizable for self::MatchaTea<> { + fn from_token( + token: ::fuels::core::Token + ) -> ::std::result::Result + where + Self: Sized, + { + let gen_err = |msg| { + ::fuels::types::errors::Error::InvalidData(format!( + "Error while instantiating {} from token! {}", + "MatchaTea", msg + )) + }; + match token { + ::fuels::core::Token::Enum(selector) => { + let (discriminant, variant_token, _) = *selector; + match discriminant { + 0u8 => ::std::result::Result::Ok(Self::LongIsland( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + 1u8 => ::std::result::Result::Ok(Self::MoscowMule( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + _ => ::std::result::Result::Err(gen_err(format!( + "Discriminant {} doesn't point to any of the enums variants.", + discriminant + ))), + } + } + _ => ::std::result::Result::Err(gen_err(format!( + "Given token ({}) is not of the type Token::Enum!", + token + ))), + } + } + fn into_token(self) -> ::fuels::core::Token { + let (discriminant, token) = match self { + Self::LongIsland(inner) => (0u8, ::fuels::core::Tokenizable::into_token(inner)), + Self::MoscowMule(inner) => (1u8, ::fuels::core::Tokenizable::into_token(inner)) + }; + let variants = match < Self as :: fuels :: core :: Parameterize > :: param_type () { :: fuels :: types :: param_types :: ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "MatchaTea" , other) } ; + ::fuels::core::Token::Enum(::std::boxed::Box::new((discriminant, token, variants))) + } + } + impl<> TryFrom<&[u8]> for self::MatchaTea<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &[u8]) -> ::std::result::Result { + ::fuels::core::try_from_bytes(bytes) + } + } + impl<> TryFrom<&::std::vec::Vec> for self::MatchaTea<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + impl<> TryFrom<::std::vec::Vec> for self::MatchaTea<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: ::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + }; + + assert_eq!(actual.to_string(), expected.to_string()); Ok(()) } @@ -86,9 +249,12 @@ mod tests { }; let types = [(0, p.clone())].into_iter().collect::>(); - let err = expand_custom_enum(&p, &types) - .err() - .ok_or_else(|| anyhow!("Was able to construct an enum without variants"))?; + let err = expand_custom_enum( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + ) + .err() + .ok_or_else(|| anyhow!("Was able to construct an enum without variants"))?; assert!( matches!(err, Error::InvalidData(_)), @@ -169,14 +335,106 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_enum(&p, &types)?.to_string(); - let expected = TokenStream::from_str( - r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub enum Amsterdam < > { Infrastructure (Building) , Service (u32) } impl < > Parameterize for Amsterdam < > { fn param_type () -> ParamType { let variants = [("Infrastructure" . to_string () , < Building > :: param_type ()) , ("Service" . to_string () , < u32 > :: param_type ())] . to_vec () ; let variants = EnumVariants :: new (variants) . unwrap_or_else (| _ | panic ! ("{} has no variants which isn't allowed!" , "Amsterdam")) ; ParamType :: Enum { name : "Amsterdam" . to_string () , variants , generics : [] . to_vec () } } } impl < > Tokenizable for Amsterdam < > { fn from_token (token : Token) -> Result < Self , SDKError > where Self : Sized , { let gen_err = | msg | { SDKError :: InvalidData (format ! ("Error while instantiating {} from token! {}" , "Amsterdam" , msg)) } ; match token { Token :: Enum (selector) => { let (discriminant , variant_token , _) = * selector ; match discriminant { 0u8 => Ok (Self :: Infrastructure (< Building > :: from_token (variant_token) ?)) , 1u8 => Ok (Self :: Service (< u32 > :: from_token (variant_token) ?)) , _ => Err (gen_err (format ! ("Discriminant {} doesn't point to any of the enums variants." , discriminant))) , } } _ => Err (gen_err (format ! ("Given token ({}) is not of the type Token::Enum!" , token))) , } } fn into_token (self) -> Token { let (discriminant , token) = match self { Self :: Infrastructure (inner) => (0u8 , inner . into_token ()) , Self :: Service (inner) => (1u8 , inner . into_token ()) } ; let variants = match Self :: param_type () { ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "Amsterdam" , other) } ; Token :: Enum (Box :: new ((discriminant , token , variants))) } } impl < > TryFrom < & [u8] > for Amsterdam < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for Amsterdam < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for Amsterdam < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } - "#, - )?.to_string(); + let actual = expand_custom_enum( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code; - assert_eq!(actual, expected); + let expected = quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum Amsterdam<> { + Infrastructure(self::Building), + Service(u32) + } + impl<> ::fuels::core::Parameterize for self::Amsterdam<> { + fn param_type() -> ::fuels::types::param_types::ParamType { + let variants = [ + ( + "Infrastructure".to_string(), + ::param_type() + ), + ( + "Service".to_string(), + ::param_type() + ) + ] + .to_vec(); + let variants = ::fuels::types::enum_variants::EnumVariants::new(variants) + .unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", "Amsterdam")); + ::fuels::types::param_types::ParamType::Enum { + name: "Amsterdam".to_string(), + variants, + generics: [].to_vec() + } + } + } + impl<> ::fuels::core::Tokenizable for self::Amsterdam<> { + fn from_token( + token: ::fuels::core::Token + ) -> ::std::result::Result + where + Self: Sized, + { + let gen_err = |msg| { + ::fuels::types::errors::Error::InvalidData(format!( + "Error while instantiating {} from token! {}", + "Amsterdam", msg + )) + }; + match token { + ::fuels::core::Token::Enum(selector) => { + let (discriminant, variant_token, _) = *selector; + match discriminant { + 0u8 => ::std::result::Result::Ok(Self::Infrastructure( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + 1u8 => ::std::result::Result::Ok(Self::Service( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + _ => ::std::result::Result::Err(gen_err(format!( + "Discriminant {} doesn't point to any of the enums variants.", + discriminant + ))), + } + } + _ => ::std::result::Result::Err(gen_err(format!( + "Given token ({}) is not of the type Token::Enum!", + token + ))), + } + } + fn into_token(self) -> ::fuels::core::Token { + let (discriminant, token) = match self { + Self::Infrastructure(inner) => (0u8, ::fuels::core::Tokenizable::into_token(inner)), + Self::Service(inner) => (1u8, ::fuels::core::Tokenizable::into_token(inner)) + }; + let variants = match < Self as :: fuels :: core :: Parameterize > :: param_type () { :: fuels :: types :: param_types :: ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "Amsterdam" , other) } ; + ::fuels::core::Token::Enum(::std::boxed::Box::new((discriminant, token, variants))) + } + } + impl<> TryFrom<&[u8]> for self::Amsterdam<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &[u8]) -> ::std::result::Result { + ::fuels::core::try_from_bytes(bytes) + } + } + impl<> TryFrom<&::std::vec::Vec> for self::Amsterdam<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + impl<> TryFrom<::std::vec::Vec> for self::Amsterdam<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: ::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + }; + + assert_eq!(actual.to_string(), expected.to_string()); Ok(()) } @@ -218,14 +476,95 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_enum(&p, &types)?.to_string(); - let expected = TokenStream::from_str( - r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub enum SomeEnum < > { SomeArr ([u64 ; 7usize]) } impl < > Parameterize for SomeEnum < > { fn param_type () -> ParamType { let variants = [("SomeArr" . to_string () , < [u64 ; 7usize] > :: param_type ())] . to_vec () ; let variants = EnumVariants :: new (variants) . unwrap_or_else (| _ | panic ! ("{} has no variants which isn't allowed!" , "SomeEnum")) ; ParamType :: Enum { name : "SomeEnum" . to_string () , variants , generics : [] . to_vec () } } } impl < > Tokenizable for SomeEnum < > { fn from_token (token : Token) -> Result < Self , SDKError > where Self : Sized , { let gen_err = | msg | { SDKError :: InvalidData (format ! ("Error while instantiating {} from token! {}" , "SomeEnum" , msg)) } ; match token { Token :: Enum (selector) => { let (discriminant , variant_token , _) = * selector ; match discriminant { 0u8 => Ok (Self :: SomeArr (< [u64 ; 7usize] > :: from_token (variant_token) ?)) , _ => Err (gen_err (format ! ("Discriminant {} doesn't point to any of the enums variants." , discriminant))) , } } _ => Err (gen_err (format ! ("Given token ({}) is not of the type Token::Enum!" , token))) , } } fn into_token (self) -> Token { let (discriminant , token) = match self { Self :: SomeArr (inner) => (0u8 , inner . into_token ()) } ; let variants = match Self :: param_type () { ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "SomeEnum" , other) } ; Token :: Enum (Box :: new ((discriminant , token , variants))) } } impl < > TryFrom < & [u8] > for SomeEnum < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for SomeEnum < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for SomeEnum < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } - "#, - )?.to_string(); + let actual = expand_custom_enum( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code; - assert_eq!(actual, expected); + let expected = quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum SomeEnum < > { + SomeArr([u64; 7usize]) + } + impl < > ::fuels::core::Parameterize for self::SomeEnum < > { + fn param_type() -> ::fuels::types::param_types::ParamType { + let variants = [( + "SomeArr".to_string(), + <[u64; 7usize] as ::fuels::core::Parameterize>::param_type() + )] + .to_vec(); + let variants = ::fuels::types::enum_variants::EnumVariants::new(variants) + .unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", "SomeEnum")); + ::fuels::types::param_types::ParamType::Enum { + name: "SomeEnum".to_string(), + variants, + generics: [].to_vec() + } + } + } + impl < > ::fuels::core::Tokenizable for self::SomeEnum < > { + fn from_token( + token: ::fuels::core::Token + ) -> ::std::result::Result + where + Self: Sized, + { + let gen_err = |msg| { + ::fuels::types::errors::Error::InvalidData(format!( + "Error while instantiating {} from token! {}", + "SomeEnum", msg + )) + }; + match token { + ::fuels::core::Token::Enum(selector) => { + let (discriminant, variant_token, _) = *selector; + match discriminant { + 0u8 => ::std::result::Result::Ok(Self::SomeArr( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + _ => ::std::result::Result::Err(gen_err(format!( + "Discriminant {} doesn't point to any of the enums variants.", + discriminant + ))), + } + } + _ => ::std::result::Result::Err(gen_err(format!( + "Given token ({}) is not of the type Token::Enum!", + token + ))), + } + } + fn into_token(self) -> ::fuels::core::Token { + let (discriminant, token) = match self { + Self::SomeArr(inner) => (0u8, ::fuels::core::Tokenizable::into_token(inner)) + }; + let variants = match < Self as :: fuels :: core :: Parameterize > :: param_type () { :: fuels :: types :: param_types :: ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "SomeEnum" , other) } ; + ::fuels::core::Token::Enum(::std::boxed::Box::new((discriminant, token, variants))) + } + } + impl <> TryFrom<&[u8]> for self::SomeEnum < > { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &[u8]) -> ::std::result::Result { + ::fuels::core::try_from_bytes(bytes) + } + } + impl <> TryFrom<&::std::vec::Vec> for self::SomeEnum <>{ + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + impl <> TryFrom<::std::vec::Vec> for self::SomeEnum <>{ + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: ::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + }; + + assert_eq!(actual.to_string(), expected.to_string()); Ok(()) } @@ -280,14 +619,95 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_enum(&p, &types)?.to_string(); - let expected = TokenStream::from_str( - r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub enum EnumLevel3 < > { El2 (EnumLevel2) } impl < > Parameterize for EnumLevel3 < > { fn param_type () -> ParamType { let variants = [("El2" . to_string () , < EnumLevel2 > :: param_type ())] . to_vec () ; let variants = EnumVariants :: new (variants) . unwrap_or_else (| _ | panic ! ("{} has no variants which isn't allowed!" , "EnumLevel3")) ; ParamType :: Enum { name : "EnumLevel3" . to_string () , variants , generics : [] . to_vec () } } } impl < > Tokenizable for EnumLevel3 < > { fn from_token (token : Token) -> Result < Self , SDKError > where Self : Sized , { let gen_err = | msg | { SDKError :: InvalidData (format ! ("Error while instantiating {} from token! {}" , "EnumLevel3" , msg)) } ; match token { Token :: Enum (selector) => { let (discriminant , variant_token , _) = * selector ; match discriminant { 0u8 => Ok (Self :: El2 (< EnumLevel2 > :: from_token (variant_token) ?)) , _ => Err (gen_err (format ! ("Discriminant {} doesn't point to any of the enums variants." , discriminant))) , } } _ => Err (gen_err (format ! ("Given token ({}) is not of the type Token::Enum!" , token))) , } } fn into_token (self) -> Token { let (discriminant , token) = match self { Self :: El2 (inner) => (0u8 , inner . into_token ()) } ; let variants = match Self :: param_type () { ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "EnumLevel3" , other) } ; Token :: Enum (Box :: new ((discriminant , token , variants))) } } impl < > TryFrom < & [u8] > for EnumLevel3 < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for EnumLevel3 < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for EnumLevel3 < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } - "#, - )?.to_string(); + let actual = expand_custom_enum( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code; - assert_eq!(actual, expected); + let expected = quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum EnumLevel3<> { + El2(self::EnumLevel2) + } + impl<> ::fuels::core::Parameterize for self::EnumLevel3<> { + fn param_type() -> ::fuels::types::param_types::ParamType { + let variants = [( + "El2".to_string(), + ::param_type() + )] + .to_vec(); + let variants = ::fuels::types::enum_variants::EnumVariants::new(variants) + .unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", "EnumLevel3")); + ::fuels::types::param_types::ParamType::Enum { + name: "EnumLevel3".to_string(), + variants, + generics: [].to_vec() + } + } + } + impl<> ::fuels::core::Tokenizable for self::EnumLevel3<> { + fn from_token( + token: ::fuels::core::Token + ) -> ::std::result::Result + where + Self: Sized, + { + let gen_err = |msg| { + ::fuels::types::errors::Error::InvalidData(format!( + "Error while instantiating {} from token! {}", + "EnumLevel3", msg + )) + }; + match token { + ::fuels::core::Token::Enum(selector) => { + let (discriminant, variant_token, _) = *selector; + match discriminant { + 0u8 => ::std::result::Result::Ok(Self::El2( + ::fuels::core::Tokenizable::from_token(variant_token)? + )), + _ => ::std::result::Result::Err(gen_err(format!( + "Discriminant {} doesn't point to any of the enums variants.", + discriminant + ))), + } + } + _ => ::std::result::Result::Err(gen_err(format!( + "Given token ({}) is not of the type Token::Enum!", + token + ))), + } + } + fn into_token(self) -> ::fuels::core::Token { + let (discriminant, token) = match self { + Self::El2(inner) => (0u8, ::fuels::core::Tokenizable::into_token(inner)) + }; + let variants = match < Self as :: fuels :: core :: Parameterize > :: param_type () { :: fuels :: types :: param_types :: ParamType :: Enum { variants , .. } => variants , other => panic ! ("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}" , "EnumLevel3" , other) } ; + ::fuels::core::Token::Enum(::std::boxed::Box::new((discriminant, token, variants))) + } + } + impl<> TryFrom<&[u8]> for self::EnumLevel3<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &[u8]) -> ::std::result::Result { + ::fuels::core::try_from_bytes(bytes) + } + } + impl<> TryFrom<&::std::vec::Vec> for self::EnumLevel3<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: &::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + impl<> TryFrom<::std::vec::Vec> for self::EnumLevel3<> { + type Error = ::fuels::types::errors::Error; + fn try_from(bytes: ::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) + } + } + }; + + assert_eq!(actual.to_string(), expected.to_string()); Ok(()) } @@ -343,10 +763,15 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_struct(&p, &types)?.to_string(); + let actual = expand_custom_struct( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code + .to_string(); let expected = TokenStream::from_str( r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub struct Cocktail < > { pub long_island : bool , pub cosmopolitan : u64 , pub mojito : u32 } impl < > Parameterize for Cocktail < > { fn param_type () -> ParamType { let types = [("long_island" . to_string () , < bool > :: param_type ()) , ("cosmopolitan" . to_string () , < u64 > :: param_type ()) , ("mojito" . to_string () , < u32 > :: param_type ())] . to_vec () ; ParamType :: Struct { name : "Cocktail" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > Tokenizable for Cocktail < > { fn into_token (self) -> Token { let tokens = [self . long_island . into_token () , self . cosmopolitan . into_token () , self . mojito . into_token ()] . to_vec () ; Token :: Struct (tokens) } fn from_token (token : Token) -> Result < Self , SDKError > { match token { Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { SDKError :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "Cocktail")) }) } ; Ok (Self { long_island : < bool > :: from_token (next_token () ?) ? , cosmopolitan : < u64 > :: from_token (next_token () ?) ? , mojito : < u32 > :: from_token (next_token () ?) ? , }) } , other => Err (SDKError :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "Cocktail" , other))) , } } } impl < > TryFrom < & [u8] > for Cocktail < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for Cocktail < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for Cocktail < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } + # [derive (Clone , Debug , Eq , PartialEq)] pub struct Cocktail < > { pub long_island : bool , pub cosmopolitan : u64 , pub mojito : u32 } impl < > :: fuels :: core :: Parameterize for self :: Cocktail < > { fn param_type () -> :: fuels :: types :: param_types :: ParamType { let types = [("long_island" . to_string () , < bool as :: fuels :: core :: Parameterize > :: param_type ()) , ("cosmopolitan" . to_string () , < u64 as :: fuels :: core :: Parameterize > :: param_type ()) , ("mojito" . to_string () , < u32 as :: fuels :: core :: Parameterize > :: param_type ())] . to_vec () ; :: fuels :: types :: param_types :: ParamType :: Struct { name : "Cocktail" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > :: fuels :: core :: Tokenizable for self :: Cocktail < > { fn into_token (self) -> :: fuels :: core :: Token { let tokens = [self . long_island . into_token () , self . cosmopolitan . into_token () , self . mojito . into_token ()] . to_vec () ; :: fuels :: core :: Token :: Struct (tokens) } fn from_token (token : :: fuels :: core :: Token) -> :: std :: result :: Result < Self , :: fuels :: types :: errors :: Error > { match token { :: fuels :: core :: Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { :: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "Cocktail")) }) } ; :: std :: result :: Result :: Ok (Self { long_island : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , cosmopolitan : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , mojito : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , }) } , other => :: std :: result :: Result :: Err (:: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "Cocktail" , other))) , } } } impl < > TryFrom < & [u8] > for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & [u8]) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (bytes) } } impl < > TryFrom < & :: std :: vec :: Vec < u8 >> for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } impl < > TryFrom < :: std :: vec :: Vec < u8 >> for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } "#, )?.to_string(); @@ -364,11 +789,16 @@ mod tests { }; let types = [(0, p.clone())].into_iter().collect::>(); - let actual = expand_custom_struct(&p, &types)?.to_string(); + let actual = expand_custom_struct( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code + .to_string(); let expected = TokenStream::from_str( r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub struct SomeEmptyStruct < > { } impl < > Parameterize for SomeEmptyStruct < > { fn param_type () -> ParamType { let types = [] . to_vec () ; ParamType :: Struct { name : "SomeEmptyStruct" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > Tokenizable for SomeEmptyStruct < > { fn into_token (self) -> Token { let tokens = [] . to_vec () ; Token :: Struct (tokens) } fn from_token (token : Token) -> Result < Self , SDKError > { match token { Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { SDKError :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "SomeEmptyStruct")) }) } ; Ok (Self { }) } , other => Err (SDKError :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "SomeEmptyStruct" , other))) , } } } impl < > TryFrom < & [u8] > for SomeEmptyStruct < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for SomeEmptyStruct < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for SomeEmptyStruct < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } + # [derive (Clone , Debug , Eq , PartialEq)] pub struct SomeEmptyStruct < > { } impl < > :: fuels :: core :: Parameterize for self :: SomeEmptyStruct < > { fn param_type () -> :: fuels :: types :: param_types :: ParamType { let types = [] . to_vec () ; :: fuels :: types :: param_types :: ParamType :: Struct { name : "SomeEmptyStruct" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > :: fuels :: core :: Tokenizable for self :: SomeEmptyStruct < > { fn into_token (self) -> :: fuels :: core :: Token { let tokens = [] . to_vec () ; :: fuels :: core :: Token :: Struct (tokens) } fn from_token (token : :: fuels :: core :: Token) -> :: std :: result :: Result < Self , :: fuels :: types :: errors :: Error > { match token { :: fuels :: core :: Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { :: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "SomeEmptyStruct")) }) } ; :: std :: result :: Result :: Ok (Self { }) } , other => :: std :: result :: Result :: Err (:: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "SomeEmptyStruct" , other))) , } } } impl < > TryFrom < & [u8] > for self :: SomeEmptyStruct < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & [u8]) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (bytes) } } impl < > TryFrom < & :: std :: vec :: Vec < u8 >> for self :: SomeEmptyStruct < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } impl < > TryFrom < :: std :: vec :: Vec < u8 >> for self :: SomeEmptyStruct < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } "#, )?.to_string(); @@ -444,10 +874,15 @@ mod tests { ] .into_iter() .collect::>(); - let actual = expand_custom_struct(&p, &types)?.to_string(); + let actual = expand_custom_struct( + &FullTypeDeclaration::from_counterpart(&p, &types), + &HashSet::default(), + )? + .code + .to_string(); let expected = TokenStream::from_str( r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub struct Cocktail < > { pub long_island : Shaker , pub mojito : u32 } impl < > Parameterize for Cocktail < > { fn param_type () -> ParamType { let types = [("long_island" . to_string () , < Shaker > :: param_type ()) , ("mojito" . to_string () , < u32 > :: param_type ())] . to_vec () ; ParamType :: Struct { name : "Cocktail" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > Tokenizable for Cocktail < > { fn into_token (self) -> Token { let tokens = [self . long_island . into_token () , self . mojito . into_token ()] . to_vec () ; Token :: Struct (tokens) } fn from_token (token : Token) -> Result < Self , SDKError > { match token { Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { SDKError :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "Cocktail")) }) } ; Ok (Self { long_island : < Shaker > :: from_token (next_token () ?) ? , mojito : < u32 > :: from_token (next_token () ?) ? , }) } , other => Err (SDKError :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "Cocktail" , other))) , } } } impl < > TryFrom < & [u8] > for Cocktail < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for Cocktail < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for Cocktail < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } + # [derive (Clone , Debug , Eq , PartialEq)] pub struct Cocktail < > { pub long_island : self :: Shaker , pub mojito : u32 } impl < > :: fuels :: core :: Parameterize for self :: Cocktail < > { fn param_type () -> :: fuels :: types :: param_types :: ParamType { let types = [("long_island" . to_string () , < self :: Shaker as :: fuels :: core :: Parameterize > :: param_type ()) , ("mojito" . to_string () , < u32 as :: fuels :: core :: Parameterize > :: param_type ())] . to_vec () ; :: fuels :: types :: param_types :: ParamType :: Struct { name : "Cocktail" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > :: fuels :: core :: Tokenizable for self :: Cocktail < > { fn into_token (self) -> :: fuels :: core :: Token { let tokens = [self . long_island . into_token () , self . mojito . into_token ()] . to_vec () ; :: fuels :: core :: Token :: Struct (tokens) } fn from_token (token : :: fuels :: core :: Token) -> :: std :: result :: Result < Self , :: fuels :: types :: errors :: Error > { match token { :: fuels :: core :: Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { :: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "Cocktail")) }) } ; :: std :: result :: Result :: Ok (Self { long_island : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , mojito : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , }) } , other => :: std :: result :: Result :: Err (:: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "Cocktail" , other))) , } } } impl < > TryFrom < & [u8] > for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & [u8]) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (bytes) } } impl < > TryFrom < & :: std :: vec :: Vec < u8 >> for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } impl < > TryFrom < :: std :: vec :: Vec < u8 >> for self :: Cocktail < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } "#, )?.to_string(); @@ -597,31 +1032,41 @@ mod tests { } "#; let parsed_abi: ProgramABI = serde_json::from_str(s)?; - let all_types = parsed_abi + let types = parsed_abi .types .into_iter() .map(|t| (t.type_id, t)) .collect::>(); - let s1 = all_types.get(&2).unwrap(); + let s1 = types.get(&2).unwrap(); - let actual = expand_custom_struct(s1, &all_types)?.to_string(); + let actual = expand_custom_struct( + &FullTypeDeclaration::from_counterpart(s1, &types), + &HashSet::default(), + )? + .code + .to_string(); let expected = TokenStream::from_str( r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub struct MyStruct1 < > { pub x : u64 , pub y : Bits256 } impl < > Parameterize for MyStruct1 < > { fn param_type () -> ParamType { let types = [("x" . to_string () , < u64 > :: param_type ()) , ("y" . to_string () , < Bits256 > :: param_type ())] . to_vec () ; ParamType :: Struct { name : "MyStruct1" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > Tokenizable for MyStruct1 < > { fn into_token (self) -> Token { let tokens = [self . x . into_token () , self . y . into_token ()] . to_vec () ; Token :: Struct (tokens) } fn from_token (token : Token) -> Result < Self , SDKError > { match token { Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { SDKError :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "MyStruct1")) }) } ; Ok (Self { x : < u64 > :: from_token (next_token () ?) ? , y : < Bits256 > :: from_token (next_token () ?) ? , }) } , other => Err (SDKError :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "MyStruct1" , other))) , } } } impl < > TryFrom < & [u8] > for MyStruct1 < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for MyStruct1 < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for MyStruct1 < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } + # [derive (Clone , Debug , Eq , PartialEq)] pub struct MyStruct1 < > { pub x : u64 , pub y : :: fuels :: core :: types :: Bits256 } impl < > :: fuels :: core :: Parameterize for self :: MyStruct1 < > { fn param_type () -> :: fuels :: types :: param_types :: ParamType { let types = [("x" . to_string () , < u64 as :: fuels :: core :: Parameterize > :: param_type ()) , ("y" . to_string () , < :: fuels :: core :: types :: Bits256 as :: fuels :: core :: Parameterize > :: param_type ())] . to_vec () ; :: fuels :: types :: param_types :: ParamType :: Struct { name : "MyStruct1" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > :: fuels :: core :: Tokenizable for self :: MyStruct1 < > { fn into_token (self) -> :: fuels :: core :: Token { let tokens = [self . x . into_token () , self . y . into_token ()] . to_vec () ; :: fuels :: core :: Token :: Struct (tokens) } fn from_token (token : :: fuels :: core :: Token) -> :: std :: result :: Result < Self , :: fuels :: types :: errors :: Error > { match token { :: fuels :: core :: Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { :: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "MyStruct1")) }) } ; :: std :: result :: Result :: Ok (Self { x : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , y : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , }) } , other => :: std :: result :: Result :: Err (:: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "MyStruct1" , other))) , } } } impl < > TryFrom < & [u8] > for self :: MyStruct1 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & [u8]) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (bytes) } } impl < > TryFrom < & :: std :: vec :: Vec < u8 >> for self :: MyStruct1 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } impl < > TryFrom < :: std :: vec :: Vec < u8 >> for self :: MyStruct1 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } "#, )?.to_string(); assert_eq!(actual, expected); - let s2 = all_types.get(&3).unwrap(); + let s2 = types.get(&3).unwrap(); - let actual = expand_custom_struct(s2, &all_types)?.to_string(); + let actual = expand_custom_struct( + &FullTypeDeclaration::from_counterpart(s2, &types), + &HashSet::default(), + )? + .code + .to_string(); let expected = TokenStream::from_str( r#" - # [derive (Clone , Debug , Eq , PartialEq)] pub struct MyStruct2 < > { pub x : bool , pub y : MyStruct1 } impl < > Parameterize for MyStruct2 < > { fn param_type () -> ParamType { let types = [("x" . to_string () , < bool > :: param_type ()) , ("y" . to_string () , < MyStruct1 > :: param_type ())] . to_vec () ; ParamType :: Struct { name : "MyStruct2" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > Tokenizable for MyStruct2 < > { fn into_token (self) -> Token { let tokens = [self . x . into_token () , self . y . into_token ()] . to_vec () ; Token :: Struct (tokens) } fn from_token (token : Token) -> Result < Self , SDKError > { match token { Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { SDKError :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "MyStruct2")) }) } ; Ok (Self { x : < bool > :: from_token (next_token () ?) ? , y : < MyStruct1 > :: from_token (next_token () ?) ? , }) } , other => Err (SDKError :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "MyStruct2" , other))) , } } } impl < > TryFrom < & [u8] > for MyStruct2 < > { type Error = SDKError ; fn try_from (bytes : & [u8]) -> Result < Self , Self :: Error > { try_from_bytes (bytes) } } impl < > TryFrom < & Vec < u8 >> for MyStruct2 < > { type Error = SDKError ; fn try_from (bytes : & Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } impl < > TryFrom < Vec < u8 >> for MyStruct2 < > { type Error = SDKError ; fn try_from (bytes : Vec < u8 >) -> Result < Self , Self :: Error > { try_from_bytes (& bytes) } } + # [derive (Clone , Debug , Eq , PartialEq)] pub struct MyStruct2 < > { pub x : bool , pub y : self :: MyStruct1 } impl < > :: fuels :: core :: Parameterize for self :: MyStruct2 < > { fn param_type () -> :: fuels :: types :: param_types :: ParamType { let types = [("x" . to_string () , < bool as :: fuels :: core :: Parameterize > :: param_type ()) , ("y" . to_string () , < self :: MyStruct1 as :: fuels :: core :: Parameterize > :: param_type ())] . to_vec () ; :: fuels :: types :: param_types :: ParamType :: Struct { name : "MyStruct2" . to_string () , fields : types , generics : [] . to_vec () } } } impl < > :: fuels :: core :: Tokenizable for self :: MyStruct2 < > { fn into_token (self) -> :: fuels :: core :: Token { let tokens = [self . x . into_token () , self . y . into_token ()] . to_vec () ; :: fuels :: core :: Token :: Struct (tokens) } fn from_token (token : :: fuels :: core :: Token) -> :: std :: result :: Result < Self , :: fuels :: types :: errors :: Error > { match token { :: fuels :: core :: Token :: Struct (tokens) => { let mut tokens_iter = tokens . into_iter () ; let mut next_token = move || { tokens_iter . next () . ok_or_else (|| { :: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Ran out of tokens before '{}' has finished construction!" , "MyStruct2")) }) } ; :: std :: result :: Result :: Ok (Self { x : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , y : :: fuels :: core :: Tokenizable :: from_token (next_token () ?) ? , }) } , other => :: std :: result :: Result :: Err (:: fuels :: types :: errors :: Error :: InstantiationError (format ! ("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}" , "MyStruct2" , other))) , } } } impl < > TryFrom < & [u8] > for self :: MyStruct2 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & [u8]) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (bytes) } } impl < > TryFrom < & :: std :: vec :: Vec < u8 >> for self :: MyStruct2 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : & :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } impl < > TryFrom < :: std :: vec :: Vec < u8 >> for self :: MyStruct2 < > { type Error = :: fuels :: types :: errors :: Error ; fn try_from (bytes : :: std :: vec :: Vec < u8 >) -> :: std :: result :: Result < Self , Self :: Error > { :: fuels :: core :: try_from_bytes (& bytes) } } "#, )?.to_string(); @@ -629,4 +1074,36 @@ mod tests { Ok(()) } + + #[test] + fn will_skip_shared_types() { + // given + let types = ["struct SomeStruct", "enum SomeEnum"].map(given_a_custom_type); + let shared_types = HashSet::from_iter(types.iter().take(1).cloned()); + + // when + let generated_code = generate_types(types, &shared_types).expect("Should have succeeded."); + + // then + assert_eq!( + generated_code.usable_types, + HashSet::from([TypePath::new("SomeEnum").expect("Hand crafted, should not fail")]) + ); + } + + fn given_a_custom_type(type_field: &str) -> FullTypeDeclaration { + FullTypeDeclaration { + type_field: type_field.to_string(), + components: vec![FullTypeApplication { + name: "a".to_string(), + type_decl: FullTypeDeclaration { + type_field: "u8".to_string(), + components: vec![], + type_parameters: vec![], + }, + type_arguments: vec![], + }], + type_parameters: vec![], + } + } } diff --git a/packages/fuels-core/src/code_gen/custom_types/enum_gen.rs b/packages/fuels-core/src/code_gen/custom_types/enums.rs similarity index 63% rename from packages/fuels-core/src/code_gen/custom_types/enum_gen.rs rename to packages/fuels-core/src/code_gen/custom_types/enums.rs index 5842a62344..c937ce60c6 100644 --- a/packages/fuels-core/src/code_gen/custom_types/enum_gen.rs +++ b/packages/fuels-core/src/code_gen/custom_types/enums.rs @@ -1,37 +1,42 @@ -use super::utils::{ - extract_components, extract_generic_parameters, impl_try_from, param_type_calls, Component, -}; -use crate::utils::ident; -use fuel_abi_types::program_abi::TypeDeclaration; -use fuels_types::{errors::Error, utils::custom_type_name}; +use std::collections::HashSet; + use proc_macro2::{Ident, TokenStream}; use quote::quote; -use std::collections::HashMap; + +use fuels_types::{errors::Error, utils::custom_type_name}; + +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::type_path::TypePath; +use crate::code_gen::utils::{param_type_calls, Component}; +use crate::utils::ident; + +use super::utils::{extract_components, extract_generic_parameters, impl_try_from}; /// Returns a TokenStream containing the declaration, `Parameterize`, /// `Tokenizable` and `TryFrom` implementations for the enum described by the /// given TypeDeclaration. -pub fn expand_custom_enum( - type_decl: &TypeDeclaration, - types: &HashMap, -) -> Result { - let enum_ident = ident(&custom_type_name(&type_decl.type_field)?); - - let components = extract_components(type_decl, types, false)?; +pub(crate) fn expand_custom_enum( + type_decl: &FullTypeDeclaration, + shared_types: &HashSet, +) -> Result { + let enum_name = custom_type_name(&type_decl.type_field)?; + let enum_ident = ident(&enum_name); + + let components = extract_components(type_decl, false, shared_types)?; if components.is_empty() { return Err(Error::InvalidData( "Enum must have at least one component!".into(), )); } - - let generics = extract_generic_parameters(type_decl, types)?; + let generics = extract_generic_parameters(type_decl)?; let enum_def = enum_decl(&enum_ident, &components, &generics); let parameterize_impl = enum_parameterize_impl(&enum_ident, &components, &generics); let tokenize_impl = enum_tokenizable_impl(&enum_ident, &components, &generics); let try_from = impl_try_from(&enum_ident, &generics); - Ok(quote! { + let code = quote! { #enum_def #parameterize_impl @@ -39,6 +44,10 @@ pub fn expand_custom_enum( #tokenize_impl #try_from + }; + Ok(GeneratedCode { + code, + usable_types: HashSet::from([TypePath::new(&enum_name).expect("Enum name is not empty!")]), }) } @@ -65,8 +74,9 @@ fn enum_decl( ); quote! { + #[allow(clippy::enum_variant_names)] #[derive(Clone, Debug, Eq, PartialEq)] - pub enum #enum_ident <#(#generics: Tokenizable + Parameterize),*> { + pub enum #enum_ident <#(#generics: ::fuels::core::Tokenizable + ::fuels::core::Parameterize),*> { #(#enum_variants),* } } @@ -90,12 +100,11 @@ fn enum_tokenizable_impl( let value = if field_type.is_unit() { quote! {} } else { - let field_type: TokenStream = field_type.into(); - quote! { <#field_type>::from_token(variant_token)? } + quote! { ::fuels::core::Tokenizable::from_token(variant_token)? } }; let u8_discriminant = discriminant as u8; - quote! { #u8_discriminant => Ok(Self::#field_name(#value))} + quote! { #u8_discriminant => ::std::result::Result::Ok(Self::#field_name(#value))} }, ); @@ -111,49 +120,49 @@ fn enum_tokenizable_impl( if field_type.is_unit() { quote! { Self::#field_name() => (#u8_discriminant, ().into_token())} } else { - quote! { Self::#field_name(inner) => (#u8_discriminant, inner.into_token())} + quote! { Self::#field_name(inner) => (#u8_discriminant, ::fuels::core::Tokenizable::into_token(inner))} } }, ); quote! { - impl<#(#generics: Tokenizable + Parameterize),*> Tokenizable for #enum_ident <#(#generics),*> { - fn from_token(token: Token) -> Result + impl<#(#generics: ::fuels::core::Tokenizable + ::fuels::core::Parameterize),*> ::fuels::core::Tokenizable for self::#enum_ident <#(#generics),*> { + fn from_token(token: ::fuels::core::Token) -> ::std::result::Result where Self: Sized, { let gen_err = |msg| { - SDKError::InvalidData(format!( + ::fuels::types::errors::Error::InvalidData(format!( "Error while instantiating {} from token! {}", #enum_ident_stringified, msg )) }; match token { - Token::Enum(selector) => { + ::fuels::core::Token::Enum(selector) => { let (discriminant, variant_token, _) = *selector; match discriminant { #(#match_discriminant_from_token,)* - _ => Err(gen_err(format!( + _ => ::std::result::Result::Err(gen_err(format!( "Discriminant {} doesn't point to any of the enums variants.", discriminant ))), } } - _ => Err(gen_err(format!( + _ => ::std::result::Result::Err(gen_err(format!( "Given token ({}) is not of the type Token::Enum!", token ))), } } - fn into_token(self) -> Token { + fn into_token(self) -> ::fuels::core::Token { let (discriminant, token) = match self { #(#match_discriminant_into_token),* }; - let variants = match Self::param_type() { - ParamType::Enum{variants, ..} => variants, + let variants = match ::param_type() { + ::fuels::types::param_types::ParamType::Enum{variants, ..} => variants, other => panic!("Calling {}::param_type() must return a ParamType::Enum but instead it returned: {:?}", #enum_ident_stringified, other) }; - Token::Enum(Box::new((discriminant, token, variants))) + ::fuels::core::Token::Enum(::std::boxed::Box::new((discriminant, token, variants))) } } } @@ -177,11 +186,12 @@ fn enum_parameterize_impl( }); let enum_ident_stringified = enum_ident.to_string(); quote! { - impl<#(#generics: Parameterize + Tokenizable),*> Parameterize for #enum_ident <#(#generics),*> { - fn param_type() -> ParamType { + impl<#(#generics: ::fuels::core::Parameterize + ::fuels::core::Tokenizable),*> ::fuels::core::Parameterize for self::#enum_ident <#(#generics),*> { + fn param_type() -> ::fuels::types::param_types::ParamType { let variants = [#(#variants),*].to_vec(); - let variants = EnumVariants::new(variants).unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", #enum_ident_stringified)); - ParamType::Enum{ + + let variants = ::fuels::types::enum_variants::EnumVariants::new(variants).unwrap_or_else(|_| panic!("{} has no variants which isn't allowed!", #enum_ident_stringified)); + ::fuels::types::param_types::ParamType::Enum{ name: #enum_ident_stringified.to_string(), variants, generics: [#(#generics::param_type()),*].to_vec() diff --git a/packages/fuels-core/src/code_gen/custom_types/struct_gen.rs b/packages/fuels-core/src/code_gen/custom_types/structs.rs similarity index 55% rename from packages/fuels-core/src/code_gen/custom_types/struct_gen.rs rename to packages/fuels-core/src/code_gen/custom_types/structs.rs index 4346fcc19b..d3322c2db2 100644 --- a/packages/fuels-core/src/code_gen/custom_types/struct_gen.rs +++ b/packages/fuels-core/src/code_gen/custom_types/structs.rs @@ -1,24 +1,30 @@ -use super::utils::{ - extract_components, extract_generic_parameters, impl_try_from, param_type_calls, Component, -}; -use crate::utils::ident; -use fuel_abi_types::program_abi::TypeDeclaration; -use fuels_types::{errors::Error, utils::custom_type_name}; +use std::collections::HashSet; + use proc_macro2::{Ident, TokenStream}; use quote::quote; -use std::collections::HashMap; + +use fuels_types::{errors::Error, utils::custom_type_name}; + +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::generated_code::GeneratedCode; +use crate::code_gen::type_path::TypePath; +use crate::code_gen::utils::{param_type_calls, Component}; +use crate::utils::ident; + +use super::utils::{extract_components, extract_generic_parameters, impl_try_from}; /// Returns a TokenStream containing the declaration, `Parameterize`, /// `Tokenizable` and `TryFrom` implementations for the struct described by the /// given TypeDeclaration. -pub fn expand_custom_struct( - type_decl: &TypeDeclaration, - types: &HashMap, -) -> Result { - let struct_ident = ident(&custom_type_name(&type_decl.type_field)?); +pub(crate) fn expand_custom_struct( + type_decl: &FullTypeDeclaration, + shared_types: &HashSet, +) -> Result { + let struct_name = custom_type_name(&type_decl.type_field)?; + let struct_ident = ident(&struct_name); - let components = extract_components(type_decl, types, true)?; - let generic_parameters = extract_generic_parameters(type_decl, types)?; + let components = extract_components(type_decl, true, shared_types)?; + let generic_parameters = extract_generic_parameters(type_decl)?; let struct_decl = struct_decl(&struct_ident, &components, &generic_parameters); @@ -29,7 +35,7 @@ pub fn expand_custom_struct( let try_from_impl = impl_try_from(&struct_ident, &generic_parameters); - Ok(quote! { + let code = quote! { #struct_decl #parameterized_impl @@ -37,6 +43,12 @@ pub fn expand_custom_struct( #tokenizable_impl #try_from_impl + }; + Ok(GeneratedCode { + code, + usable_types: HashSet::from([ + TypePath::new(&struct_name).expect("Struct name is not empty!") + ]), }) } @@ -57,7 +69,7 @@ fn struct_decl( quote! { #[derive(Clone, Debug, Eq, PartialEq)] - pub struct #struct_ident <#(#generic_parameters: Tokenizable + Parameterize, )*> { + pub struct #struct_ident <#(#generic_parameters: ::fuels::core::Tokenizable + ::fuels::core::Parameterize, )*> { #(#fields),* } } @@ -71,17 +83,11 @@ fn struct_tokenizable_impl( let struct_name_str = struct_ident.to_string(); let from_token_calls = components .iter() - .map( - |Component { - field_name, - field_type, - }| { - let resolved: TokenStream = field_type.into(); - quote! { - #field_name: <#resolved>::from_token(next_token()?)? - } - }, - ) + .map(|Component { field_name, .. }| { + quote! { + #field_name: ::fuels::core::Tokenizable::from_token(next_token()?)? + } + }) .collect::>(); let into_token_calls = components @@ -92,23 +98,23 @@ fn struct_tokenizable_impl( .collect::>(); quote! { - impl <#(#generic_parameters: Tokenizable + Parameterize, )*> Tokenizable for #struct_ident <#(#generic_parameters, )*> { - fn into_token(self) -> Token { + impl <#(#generic_parameters: ::fuels::core::Tokenizable + ::fuels::core::Parameterize, )*> ::fuels::core::Tokenizable for self::#struct_ident <#(#generic_parameters, )*> { + fn into_token(self) -> ::fuels::core::Token { let tokens = [#(#into_token_calls),*].to_vec(); - Token::Struct(tokens) + ::fuels::core::Token::Struct(tokens) } - fn from_token(token: Token) -> Result { + fn from_token(token: ::fuels::core::Token) -> ::std::result::Result { match token { - Token::Struct(tokens) => { + ::fuels::core::Token::Struct(tokens) => { let mut tokens_iter = tokens.into_iter(); let mut next_token = move || { tokens_iter .next() - .ok_or_else(|| { SDKError::InstantiationError(format!("Ran out of tokens before '{}' has finished construction!", #struct_name_str)) }) + .ok_or_else(|| { ::fuels::types::errors::Error::InstantiationError(format!("Ran out of tokens before '{}' has finished construction!", #struct_name_str)) }) }; - Ok(Self { #( #from_token_calls, )* }) + ::std::result::Result::Ok(Self { #( #from_token_calls, )* }) }, - other => Err(SDKError::InstantiationError(format!("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}", #struct_name_str, other))), + other => ::std::result::Result::Err(::fuels::types::errors::Error::InstantiationError(format!("Error while constructing '{}'. Expected token of type Token::Struct, got {:?}", #struct_name_str, other))), } } } @@ -132,10 +138,10 @@ fn struct_parameterized_impl( }); let struct_name_str = struct_ident.to_string(); quote! { - impl <#(#generic_parameters: Parameterize + Tokenizable),*> Parameterize for #struct_ident <#(#generic_parameters),*> { - fn param_type() -> ParamType { + impl <#(#generic_parameters: ::fuels::core::Parameterize + ::fuels::core::Tokenizable),*> ::fuels::core::Parameterize for self::#struct_ident <#(#generic_parameters),*> { + fn param_type() -> ::fuels::types::param_types::ParamType { let types = [#(#field_name_param_type),*].to_vec(); - ParamType::Struct{ + ::fuels::types::param_types::ParamType::Struct{ name: #struct_name_str.to_string(), fields: types, generics: [#(#generic_parameters::param_type()),*].to_vec() diff --git a/packages/fuels-core/src/code_gen/custom_types/utils.rs b/packages/fuels-core/src/code_gen/custom_types/utils.rs index e5d97927a7..79773bd8b0 100644 --- a/packages/fuels-core/src/code_gen/custom_types/utils.rs +++ b/packages/fuels-core/src/code_gen/custom_types/utils.rs @@ -1,39 +1,14 @@ -use crate::code_gen::resolved_type::{resolve_type, ResolvedType}; -use crate::utils::{ident, safe_ident}; -use fuel_abi_types::program_abi::{TypeApplication, TypeDeclaration}; -use fuels_types::errors::Error; -use fuels_types::utils::extract_generic_name; -use inflector::Inflector; +use std::collections::HashSet; + use proc_macro2::{Ident, TokenStream}; use quote::quote; -use std::collections::HashMap; -// Represents a component of either a struct(field name) or an enum(variant -// name). -#[derive(Debug)] -pub struct Component { - pub field_name: Ident, - pub field_type: ResolvedType, -} +use fuels_types::errors::Error; +use fuels_types::utils::extract_generic_name; -impl Component { - pub fn new( - component: &TypeApplication, - types: &HashMap, - snake_case: bool, - ) -> Result { - let field_name = if snake_case { - component.name.to_snake_case() - } else { - component.name.to_owned() - }; - - Ok(Component { - field_name: safe_ident(&field_name), - field_type: resolve_type(component, types)?, - }) - } -} +use crate::code_gen::abi_types::FullTypeDeclaration; +use crate::code_gen::utils::Component; +use crate::utils::ident; /// These TryFrom implementations improve devx by enabling users to easily /// construct contract types from bytes. These are generated due to the orphan @@ -50,58 +25,53 @@ impl Component { /// &[u8], &Vec and a Vec pub(crate) fn impl_try_from(ident: &Ident, generics: &[TokenStream]) -> TokenStream { quote! { - impl<#(#generics: Tokenizable + Parameterize),*> TryFrom<&[u8]> for #ident<#(#generics),*> { - type Error = SDKError; + impl<#(#generics: ::fuels::core::Tokenizable + ::fuels::core::Parameterize),*> TryFrom<&[u8]> for self::#ident<#(#generics),*> { + type Error = ::fuels::types::errors::Error; - fn try_from(bytes: &[u8]) -> Result { - try_from_bytes(bytes) + fn try_from(bytes: &[u8]) -> ::std::result::Result { + ::fuels::core::try_from_bytes(bytes) } } - impl<#(#generics: Tokenizable + Parameterize),*> TryFrom<&Vec> for #ident<#(#generics),*> { - type Error = SDKError; + impl<#(#generics: ::fuels::core::Tokenizable + ::fuels::core::Parameterize),*> TryFrom<&::std::vec::Vec> for self::#ident<#(#generics),*> { + type Error = ::fuels::types::errors::Error; - fn try_from(bytes: &Vec) -> Result { - try_from_bytes(&bytes) + fn try_from(bytes: &::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) } } - impl<#(#generics: Tokenizable + Parameterize),*> TryFrom> for #ident<#(#generics),*> { - type Error = SDKError; + impl<#(#generics: ::fuels::core::Tokenizable + ::fuels::core::Parameterize),*> TryFrom<::std::vec::Vec> for self::#ident<#(#generics),*> { + type Error = ::fuels::types::errors::Error; - fn try_from(bytes: Vec) -> Result { - try_from_bytes(&bytes) + fn try_from(bytes: ::std::vec::Vec) -> ::std::result::Result { + ::fuels::core::try_from_bytes(&bytes) } } } } -/// Transforms components from inside the given `TypeDeclaration` into a vector +/// Transforms components from inside the given `FullTypeDeclaration` into a vector /// of `Components`. Will fail if there are no components. pub(crate) fn extract_components( - type_decl: &TypeDeclaration, - types: &HashMap, + type_decl: &FullTypeDeclaration, snake_case: bool, + shared_types: &HashSet, ) -> Result, Error> { type_decl .components - .as_ref() - .unwrap_or(&vec![]) .iter() - .map(|component| Component::new(component, types, snake_case)) + .map(|component| Component::new(component, snake_case, shared_types)) .collect() } /// Returns a vector of TokenStreams, one for each of the generic parameters /// used by the given type. -pub fn extract_generic_parameters( - type_decl: &TypeDeclaration, - types: &HashMap, +pub(crate) fn extract_generic_parameters( + type_decl: &FullTypeDeclaration, ) -> Result, Error> { type_decl .type_parameters .iter() - .flatten() - .map(|id| types.get(id).unwrap()) .map(|decl| { let name = extract_generic_name(&decl.type_field).unwrap_or_else(|| { panic!("Type parameters should only contain ids of generic types!") @@ -112,64 +82,19 @@ pub fn extract_generic_parameters( .collect() } -/// Returns TokenStreams representing calls to `Parameterize::param_type` for -/// all given Components. Makes sure to properly handle calls when generics are -/// involved. -pub fn param_type_calls(field_entries: &[Component]) -> Vec { - field_entries - .iter() - .map(|Component { field_type, .. }| single_param_type_call(field_type)) - .collect() -} - -/// Returns a TokenStream representing the call to `Parameterize::param_type` for -/// the given ResolvedType. Makes sure to properly handle calls when generics are -/// involved. -pub fn single_param_type_call(field_type: &ResolvedType) -> TokenStream { - let type_name = &field_type.type_name; - let parameters = field_type - .generic_params - .iter() - .map(TokenStream::from) - .collect::>(); - if parameters.is_empty() { - quote! { <#type_name>::param_type() } - } else { - quote! { #type_name::<#(#parameters),*>::param_type() } - } -} - #[cfg(test)] mod tests { - use super::*; + use fuel_abi_types::program_abi::TypeDeclaration; use fuels_types::utils::custom_type_name; - #[test] - fn component_name_is_snake_case_when_requested() -> anyhow::Result<()> { - let type_application = TypeApplication { - name: "SomeNameHere".to_string(), - type_id: 0, - type_arguments: None, - }; - - let types = HashMap::from([( - 0, - TypeDeclaration { - type_id: 0, - type_field: "()".to_string(), - components: None, - type_parameters: None, - }, - )]); - - let component = Component::new(&type_application, &types, true)?; + use crate::code_gen::resolved_type::ResolvedType; + use crate::code_gen::utils::param_type_calls; - assert_eq!(component.field_name, ident("some_name_here")); + use super::*; - Ok(()) - } #[test] fn extracts_generic_types() -> anyhow::Result<()> { + // given let declaration = TypeDeclaration { type_id: 0, type_field: "".to_string(), @@ -195,8 +120,13 @@ mod tests { .into_iter() .collect(); - let generics = extract_generic_parameters(&declaration, &types)?; + // when + let generics = extract_generic_parameters(&FullTypeDeclaration::from_counterpart( + &declaration, + &types, + ))?; + // then let stringified_generics = generics .into_iter() .map(|generic| generic.to_string()) @@ -247,8 +177,8 @@ mod tests { assert_eq!( stringified_result, vec![ - "< u8 > :: param_type ()", - "SomeStruct :: < T , K > :: param_type ()" + "< u8 as :: fuels :: core :: Parameterize > :: param_type ()", + "< SomeStruct :: < T , K > as :: fuels :: core :: Parameterize > :: param_type ()" ] ) } diff --git a/packages/fuels-core/src/code_gen/docs_gen.rs b/packages/fuels-core/src/code_gen/docs_gen.rs deleted file mode 100644 index bb44c40c82..0000000000 --- a/packages/fuels-core/src/code_gen/docs_gen.rs +++ /dev/null @@ -1,12 +0,0 @@ -use proc_macro2::{Literal, TokenStream}; -use quote::quote; - -/// Functions used by the Abigen to expand functions defined in an ABI spec. - -/// Expands a doc string into an attribute token stream. -pub fn expand_doc(s: &str) -> TokenStream { - let doc = Literal::string(s); - quote! { - #[doc = #doc] - } -} diff --git a/packages/fuels-core/src/code_gen/function_selector.rs b/packages/fuels-core/src/code_gen/function_selector.rs index fb15bbf53b..048a3fa28e 100644 --- a/packages/fuels-core/src/code_gen/function_selector.rs +++ b/packages/fuels-core/src/code_gen/function_selector.rs @@ -1,6 +1,7 @@ +use fuels_types::param_types::ParamType; + use crate::utils::first_four_bytes_of_sha256_hash; use crate::{unzip_param_types, ByteArray}; -use fuels_types::param_types::ParamType; /// Given a function name and its inputs will return a ByteArray representing /// the function selector as specified in the Fuel specs. @@ -77,10 +78,11 @@ fn resolve_arg(arg: &ParamType) -> String { #[cfg(test)] mod tests { - use super::*; use fuels_test_helpers::generate_unused_field_names; use fuels_types::enum_variants::EnumVariants; + use super::*; + #[test] fn handles_primitive_types() { let check_selector_for_type = |primitive_type: ParamType, expected_selector: &str| { diff --git a/packages/fuels-core/src/code_gen/generated_code.rs b/packages/fuels-core/src/code_gen/generated_code.rs new file mode 100644 index 0000000000..8c8e9cde25 --- /dev/null +++ b/packages/fuels-core/src/code_gen/generated_code.rs @@ -0,0 +1,163 @@ +use std::collections::HashSet; + +use itertools::Itertools; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use crate::code_gen::type_path::TypePath; + +#[derive(Default)] +pub(crate) struct GeneratedCode { + pub code: TokenStream, + pub usable_types: HashSet, +} + +impl GeneratedCode { + pub fn is_empty(&self) -> bool { + self.code.is_empty() + } + + pub fn append(mut self, another: GeneratedCode) -> Self { + self.code.extend(another.code); + self.usable_types.extend(another.usable_types); + self + } + + pub fn wrap_in_mod(self, mod_name: &Ident) -> Self { + let mod_path = TypePath::new(mod_name).unwrap(); + let type_paths = self + .usable_types + .into_iter() + .map(|type_path| type_path.prepend(mod_path.clone())) + .collect(); + + let inner_code = self.code; + let code = quote! { + #[allow(clippy::too_many_arguments)] + #[no_implicit_prelude] + pub mod #mod_name { + #inner_code + } + }; + + Self { + code, + usable_types: type_paths, + } + } + + pub fn use_statements_for_uniquely_named_types(&self) -> TokenStream { + let type_paths = self + .types_with_unique_names() + .into_iter() + .map(TokenStream::from); + + quote! { + #(pub use #type_paths;)* + } + } + + fn types_with_unique_names(&self) -> Vec<&TypePath> { + self.usable_types + .iter() + .sorted_by(|&lhs, &rhs| lhs.type_name().cmp(rhs.type_name())) + .group_by(|&e| e.type_name()) + .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 will_wrap_code_in_mod() { + let generated_code = GeneratedCode { + code: quote! {some code}, + usable_types: HashSet::from([ + TypePath::new("SomeType").expect("Hand crafted, should be valid.") + ]), + }; + + let generated_code = generated_code.wrap_in_mod(&ident("a_mod")); + + let expected_code = quote! { + #[allow(clippy::too_many_arguments)] + #[no_implicit_prelude] + pub mod a_mod { + some code + } + }; + + assert_eq!(generated_code.code.to_string(), expected_code.to_string()); + } + + #[test] + fn wrapping_in_mod_prepends_mod_to_usable_types() { + let generated_code = GeneratedCode { + code: quote! {some code}, + usable_types: HashSet::from([given_type_path("SomeType")]), + }; + + let generated_code = generated_code.wrap_in_mod(&ident("a_mod")); + + assert_eq!( + generated_code.usable_types, + HashSet::from([ + TypePath::new("a_mod::SomeType").expect("Hand crafted, should be valid!") + ]) + ); + } + + #[test] + fn appending_appends_both_code_and_usable_types() { + // given + let type_path_1 = given_type_path("SomeType1"); + let code_1 = GeneratedCode { + code: quote! {some code 1}, + usable_types: HashSet::from([type_path_1.clone()]), + }; + + let type_path_2 = given_type_path("SomeType2"); + let code_2 = GeneratedCode { + code: quote! {some code 2}, + usable_types: HashSet::from([type_path_2.clone()]), + }; + + // when + let joined = code_1.append(code_2); + + // then + assert_eq!(joined.code.to_string(), "some code 1 some code 2"); + assert_eq!( + joined.usable_types, + HashSet::from([type_path_1, type_path_2]) + ) + } + + #[test] + fn use_statements_only_for_uniquely_named_types() { + let generated_code = GeneratedCode { + code: Default::default(), + usable_types: HashSet::from([ + given_type_path("NotUnique"), + given_type_path("some_mod::Unique"), + given_type_path("even_though::the_duplicate_is::in_another_mod::NotUnique"), + ]), + }; + + let use_statements = generated_code.use_statements_for_uniquely_named_types(); + + assert_eq!(use_statements.to_string(), "pub use some_mod :: Unique ;"); + } + + fn given_type_path(path: &str) -> TypePath { + TypePath::new(path).expect("Hand crafted, should be valid.") + } +} diff --git a/packages/fuels-core/src/code_gen/resolved_type.rs b/packages/fuels-core/src/code_gen/resolved_type.rs index cd2de33a39..8552aa4849 100644 --- a/packages/fuels-core/src/code_gen/resolved_type.rs +++ b/packages/fuels-core/src/code_gen/resolved_type.rs @@ -1,16 +1,25 @@ -use crate::utils::{ident, safe_ident}; -use fuel_abi_types::program_abi::{TypeApplication, TypeDeclaration}; -use fuels_types::errors::Error; -use fuels_types::utils::custom_type_name; -use fuels_types::utils::{ - extract_array_len, extract_generic_name, extract_str_len, has_tuple_format, -}; +use std::collections::HashSet; +use std::fmt::{Display, Formatter}; + use lazy_static::lazy_static; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use regex::Regex; -use std::collections::HashMap; -use std::fmt::{Display, Formatter}; + +use fuels_types::{ + errors::Error, + utils::custom_type_name, + utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, +}; + +use crate::{ + code_gen::{ + abi_types::{FullTypeApplication, FullTypeDeclaration}, + type_path::TypePath, + utils::get_sdk_provided_types, + }, + utils::{ident, safe_ident}, +}; // Represents a type alongside its generic parameters. Can be converted into a // `TokenStream` via `.into()`. @@ -66,19 +75,18 @@ impl From for TokenStream { /// it can be used whenever you need the Rust type of the given /// `TypeApplication`. pub(crate) fn resolve_type( - type_application: &TypeApplication, - types: &HashMap, + type_application: &FullTypeApplication, + shared_types: &HashSet, ) -> Result { - let recursively_resolve = |type_applications: &Option>| { + let recursively_resolve = |type_applications: &Vec| { type_applications .iter() - .flatten() - .map(|array_type| resolve_type(array_type, types)) + .map(|type_application| resolve_type(type_application, shared_types)) .collect::, _>>() .expect("Failed to resolve types") }; - let base_type = types.get(&type_application.type_id).unwrap(); + let base_type = &type_application.type_decl; let type_field = base_type.type_field.as_str(); @@ -90,17 +98,18 @@ pub(crate) fn resolve_type( to_array, to_sized_ascii_string, to_tuple, - to_struct, + to_custom_type, ] .into_iter() - .filter_map(|fun| { + .find_map(|fun| { + let is_shared = shared_types.contains(base_type); fun( type_field, move || recursively_resolve(&base_type.components), move || recursively_resolve(&type_application.type_arguments), + is_shared, ) }) - .next() .ok_or_else(|| Error::InvalidType(format!("Could not resolve {type_field} to any known type"))) } @@ -108,6 +117,7 @@ fn to_generic( type_field: &str, _: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { let name = extract_generic_name(type_field)?; @@ -122,6 +132,7 @@ fn to_array( type_field: &str, components_supplier: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { let len = extract_array_len(type_field)?; @@ -143,6 +154,7 @@ fn to_sized_ascii_string( type_field: &str, _: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { let len = extract_str_len(type_field)?; @@ -152,7 +164,7 @@ fn to_sized_ascii_string( }]; Some(ResolvedType { - type_name: quote! { SizedAsciiString }, + type_name: quote! { ::fuels::core::types::SizedAsciiString }, generic_params, }) } @@ -161,6 +173,7 @@ fn to_tuple( type_field: &str, components_supplier: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { if has_tuple_format(type_field) { let inner_types = components_supplier().into_iter().map(TokenStream::from); @@ -181,6 +194,7 @@ fn to_simple_type( type_field: &str, _: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { match type_field { "u8" | "u16" | "u32" | "u64" | "bool" | "()" => { @@ -201,11 +215,11 @@ fn to_byte( type_field: &str, _: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { if type_field == "byte" { - let type_name = quote! {Byte}; Some(ResolvedType { - type_name, + type_name: quote! {::fuels::core::types::Byte}, generic_params: vec![], }) } else { @@ -216,11 +230,11 @@ fn to_bits256( type_field: &str, _: impl Fn() -> Vec, _: impl Fn() -> Vec, + _: bool, ) -> Option { if type_field == "b256" { - let type_name = quote! {Bits256}; Some(ResolvedType { - type_name, + type_name: quote! {::fuels::core::types::Bits256}, generic_params: vec![], }) } else { @@ -228,26 +242,43 @@ fn to_bits256( } } -fn to_struct( +fn to_custom_type( type_field: &str, _: impl Fn() -> Vec, type_arguments_supplier: impl Fn() -> Vec, + is_shared: bool, ) -> Option { - custom_type_name(type_field) - .ok() - .map(|type_name| ident(&type_name)) - .map(|type_name| ResolvedType { - type_name: type_name.into_token_stream(), - generic_params: type_arguments_supplier(), - }) + let type_name = custom_type_name(type_field).ok()?; + + let type_path = get_sdk_provided_types() + .into_iter() + .find(|provided_type| provided_type.type_name() == type_name) + .unwrap_or_else(|| { + let custom_type_name = ident(&type_name); + let path_str = if is_shared { + format!("super::shared_types::{custom_type_name}") + } else { + format!("self::{custom_type_name}") + }; + TypePath::new(&path_str).expect("Known to be well formed") + }); + + Some(ResolvedType { + type_name: type_path.into(), + generic_params: type_arguments_supplier(), + }) } #[cfg(test)] mod tests { - use super::*; + use std::collections::HashMap; use anyhow::Context; + use fuel_abi_types::program_abi::{TypeApplication, TypeDeclaration}; + + use super::*; + fn test_resolve_first_type( expected: &str, type_declarations: &[TypeDeclaration], @@ -260,10 +291,14 @@ mod tests { type_id: type_declarations[0].type_id, ..Default::default() }; - let resolved_type = resolve_type(&type_application, &types) + + let application = FullTypeApplication::from_counterpart(&type_application, &types); + let resolved_type = resolve_type(&application, &HashSet::default()) .with_context(|| format!("failed to resolve {:?}", &type_application))?; let actual = TokenStream::from(&resolved_type).to_string(); + assert_eq!(actual, expected); + Ok(()) } @@ -305,12 +340,12 @@ mod tests { #[test] fn test_resolve_byte() -> anyhow::Result<()> { - test_resolve_primitive_type("byte", "Byte") + test_resolve_primitive_type("byte", ":: fuels :: core :: types :: Byte") } #[test] fn test_resolve_b256() -> anyhow::Result<()> { - test_resolve_primitive_type("b256", "Bits256") + test_resolve_primitive_type("b256", ":: fuels :: core :: types :: Bits256") } #[test] @@ -344,7 +379,7 @@ mod tests { #[test] fn test_resolve_vector() -> anyhow::Result<()> { test_resolve_first_type( - "Vec", + ":: std :: vec :: Vec", &[ TypeDeclaration { type_id: 0, @@ -409,13 +444,16 @@ mod tests { #[test] fn test_resolve_string() -> anyhow::Result<()> { - test_resolve_primitive_type("str[3]", "SizedAsciiString < 3usize >") + test_resolve_primitive_type( + "str[3]", + ":: fuels :: core :: types :: SizedAsciiString < 3usize >", + ) } #[test] fn test_resolve_struct() -> anyhow::Result<()> { test_resolve_first_type( - "SomeStruct", + "self :: SomeStruct", &[ TypeDeclaration { type_id: 0, @@ -451,7 +489,7 @@ mod tests { #[test] fn test_resolve_enum() -> anyhow::Result<()> { test_resolve_first_type( - "SomeEnum", + "self :: SomeEnum", &[ TypeDeclaration { type_id: 0, @@ -535,4 +573,34 @@ mod tests { ], ) } + + #[test] + fn custom_types_uses_correct_path_for_sdk_provided_types() { + let provided_type_names = get_sdk_provided_types() + .into_iter() + .map(|type_path| (type_path.type_name().to_string(), type_path)) + .collect::>(); + + for (type_name, expected_path) in provided_type_names { + let resolved_type = + to_custom_type(&format!("struct {type_name}"), Vec::new, Vec::new, false) + .expect("Should have succeeded."); + + let expected_type_name: TokenStream = expected_path.into(); + assert_eq!( + resolved_type.type_name.to_string(), + expected_type_name.to_string() + ); + } + } + #[test] + fn handles_shared_types() { + let resolved_type = + to_custom_type("struct SomeStruct", Vec::new, Vec::new, true).expect("should succeed"); + + assert_eq!( + resolved_type.type_name.to_string(), + "super :: shared_types :: SomeStruct" + ) + } } diff --git a/packages/fuels-core/src/code_gen/type_path.rs b/packages/fuels-core/src/code_gen/type_path.rs new file mode 100644 index 0000000000..4642432701 --- /dev/null +++ b/packages/fuels-core/src/code_gen/type_path.rs @@ -0,0 +1,134 @@ +use std::str::FromStr; + +use proc_macro2::TokenStream; +use quote::quote; + +use fuels_types::errors::Error; + +#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub(crate) struct TypePath { + parts: Vec, +} + +impl TypePath { + pub fn new(path: T) -> Result { + let path_str = path.to_string().trim().to_string(); + + if path_str.is_empty() { + return Err(Error::InvalidType(format!( + "TypePath cannot be constructed from '{path_str}' because it's empty!" + ))); + } + + let parts = path_str + .split("::") + .map(|part| part.trim().to_string()) + .collect::>(); + + let type_name = parts + .last() + .expect("Cannot be empty, since we started off with a non-empty string"); + + if type_name.is_empty() { + Err(Error::InvalidType(format!( + "TypePath cannot be constructed from '{path_str}'! Missing ident at the end." + ))) + } else { + Ok(Self { parts }) + } + } + + pub fn prepend(self, mut another: TypePath) -> Self { + another.parts.extend(self.parts); + another + } + + pub fn type_name(&self) -> &str { + self.parts + .last() + .expect("Must have at least one element") + .as_str() + } +} + +impl From<&TypePath> for TokenStream { + fn from(type_path: &TypePath) -> Self { + let parts = type_path + .parts + .iter() + .map(|part| TokenStream::from_str(part).unwrap()); + quote! { + #(#parts)::* + } + } +} +impl From for TokenStream { + fn from(type_path: TypePath) -> Self { + (&type_path).into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cannot_be_empty() { + let empty_path = " "; + + let err = TypePath::new(empty_path).expect_err("Should have failed!"); + + if let Error::InvalidType(msg) = err { + assert_eq!( + msg, + "TypePath cannot be constructed from '' because it's empty!" + ); + } else { + panic!("Expected an error of the type Error::InvalidType, got: {err}"); + } + } + + #[test] + fn must_have_ident_at_end() { + let no_ident = " ::missing_ident:: "; + + let err = TypePath::new(no_ident).expect_err("Should have failed!"); + + if let Error::InvalidType(msg) = err { + assert_eq!( + msg, + "TypePath cannot be constructed from '::missing_ident::'! Missing ident at the end." + ); + } else { + panic!("Expected an error of the type Error::InvalidType, got: {err}"); + } + } + + #[test] + fn trims_whitespace() { + let path = " some_mod :: ident "; + + let path = TypePath::new(path).expect("Should have passed."); + + assert_eq!(path.parts, vec!["some_mod", "ident"]) + } + + #[test] + fn can_be_prepended_to() { + let path = TypePath::new(" some_mod :: ident ").expect("Should have passed."); + let another_path = TypePath::new(" something :: else ").expect("the type path is valid"); + + let joined = path.prepend(another_path); + + assert_eq!(joined.parts, vec!["something", "else", "some_mod", "ident"]) + } + + #[test] + fn can_get_type_name() { + let path = TypePath::new(" some_mod :: ident ").expect("Should have passed."); + + let type_name = path.type_name(); + + assert_eq!(type_name, "ident"); + } +} diff --git a/packages/fuels-core/src/code_gen/utils.rs b/packages/fuels-core/src/code_gen/utils.rs new file mode 100644 index 0000000000..cae839dc1a --- /dev/null +++ b/packages/fuels-core/src/code_gen/utils.rs @@ -0,0 +1,133 @@ +use std::collections::HashSet; + +use inflector::Inflector; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +use fuels_types::errors::Error; + +use crate::code_gen::abi_types::{FullTypeApplication, FullTypeDeclaration}; +use crate::code_gen::resolved_type::{resolve_type, ResolvedType}; +use crate::code_gen::type_path::TypePath; +use crate::utils::safe_ident; + +// Represents a component of either a struct(field name) or an enum(variant +// name). +#[derive(Debug)] +pub(crate) struct Component { + pub field_name: Ident, + pub field_type: ResolvedType, +} + +impl Component { + pub fn new( + component: &FullTypeApplication, + snake_case: bool, + shared_types: &HashSet, + ) -> Result { + let field_name = if snake_case { + component.name.to_snake_case() + } else { + component.name.to_owned() + }; + + Ok(Component { + field_name: safe_ident(&field_name), + field_type: resolve_type(component, shared_types)?, + }) + } +} + +/// Returns TokenStreams representing calls to `Parameterize::param_type` for +/// all given Components. Makes sure to properly handle calls when generics are +/// involved. +pub(crate) fn param_type_calls(field_entries: &[Component]) -> Vec { + field_entries + .iter() + .map(|Component { field_type, .. }| single_param_type_call(field_type)) + .collect() +} + +/// Returns a TokenStream representing the call to `Parameterize::param_type` for +/// the given ResolvedType. Makes sure to properly handle calls when generics are +/// involved. +pub(crate) fn single_param_type_call(field_type: &ResolvedType) -> TokenStream { + let type_name = &field_type.type_name; + let parameters = field_type + .generic_params + .iter() + .map(TokenStream::from) + .collect::>(); + if parameters.is_empty() { + quote! { <#type_name as ::fuels::core::Parameterize>::param_type() } + } else { + quote! { <#type_name::<#(#parameters),*> as ::fuels::core::Parameterize>::param_type() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn respects_snake_case_flag() -> Result<(), Error> { + let type_application = type_application_named("WasNotSnakeCased"); + + let sut = Component::new(&type_application, true, &Default::default())?; + + assert_eq!(sut.field_name, "was_not_snake_cased"); + + Ok(()) + } + + #[test] + fn avoids_collisions_with_reserved_keywords() -> Result<(), Error> { + { + let type_application = type_application_named("if"); + + let sut = Component::new(&type_application, false, &Default::default())?; + + assert_eq!(sut.field_name, "if_"); + } + + { + let type_application = type_application_named("let"); + + let sut = Component::new(&type_application, false, &Default::default())?; + + assert_eq!(sut.field_name, "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![], + }, + type_arguments: vec![], + } + } +} + +pub(crate) fn get_sdk_provided_types() -> Vec { + [ + "::fuels::core::types::ContractId", + "::fuels::core::types::AssetId", + "::fuels::core::types::Address", + "::fuels::core::types::Identity", + "::fuels::core::types::EvmAddress", + "::fuels::core::types::B512", + "::std::vec::Vec", + "::std::result::Result", + "::std::option::Option", + ] + .map(|type_path_str| { + TypePath::new(type_path_str).expect("known at compile time to be correctly formed") + }) + .to_vec() +} diff --git a/packages/fuels-core/src/lib.rs b/packages/fuels-core/src/lib.rs index b461a9f6e6..3008b5ec3f 100644 --- a/packages/fuels-core/src/lib.rs +++ b/packages/fuels-core/src/lib.rs @@ -1,13 +1,18 @@ -use crate::{abi_decoder::ABIDecoder, types::Bits256}; +use std::collections::HashMap; +use std::{fmt, iter::zip}; + use fuel_types::bytes::padded_len; +use fuel_types::ContractId; +use strum_macros::EnumString; + +use fuels_types::bech32::Bech32ContractId; use fuels_types::{ enum_variants::EnumVariants, errors::{CodecError, Error}, param_types::ParamType, }; -use serde::{Deserialize, Serialize}; -use std::{fmt, iter::zip}; -use strum_macros::EnumString; + +use crate::{abi_decoder::ABIDecoder, types::Bits256}; pub mod abi_decoder; pub mod abi_encoder; @@ -15,7 +20,6 @@ pub mod code_gen; pub mod constants; pub mod offsets; pub mod parameters; -pub mod rustfmt; pub mod source; pub mod tokenizer; pub mod types; @@ -34,12 +38,6 @@ pub type ByteArray = [u8; 8]; pub type Selector = ByteArray; pub type EnumSelector = (u8, Token, EnumVariants); -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Identity { - Address(fuel_tx::Address), - ContractId(fuel_tx::ContractId), -} - #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct StringToken { data: String, @@ -263,16 +261,30 @@ fn paramtype_decode_log(param_type: &ParamType, token: &Token) -> Result, +) -> HashMap<(Bech32ContractId, u64), ParamType> { + let contract_id = contract_id.unwrap_or_else(|| Bech32ContractId::from(ContractId::zeroed())); + id_param_pairs + .iter() + .map(|(id, param_type)| ((contract_id.clone(), *id), param_type.to_owned())) + .collect() +} + #[cfg(test)] mod tests { - use super::DecodableLog; + use fuel_types::{Address, AssetId, ContractId}; + + use fuels_types::{constants::WORD_SIZE, errors::Error}; + use crate::{ try_from_bytes, types::{Bits256, EvmAddress, SizedAsciiString}, Parameterize, }; - use fuel_types::{Address, AssetId, ContractId}; - use fuels_types::{constants::WORD_SIZE, errors::Error}; + + use super::DecodableLog; #[test] fn can_convert_bytes_into_tuple() -> Result<(), Error> { diff --git a/packages/fuels-core/src/offsets.rs b/packages/fuels-core/src/offsets.rs index 94fe311b7b..5fbb29d9c8 100644 --- a/packages/fuels-core/src/offsets.rs +++ b/packages/fuels-core/src/offsets.rs @@ -1,7 +1,8 @@ -use crate::tx::{field::Script, ConsensusParameters, InputRepr}; use fuel_types::bytes::padded_len_usize; use fuel_vm::prelude::Opcode; +use crate::tx::{field::Script, ConsensusParameters, InputRepr}; + /// Gets the base offset for a script or a predicate. The offset depends on the `max_inputs` /// field of the `ConsensusParameters` and the static offset. pub fn base_offset(consensus_parameters: &ConsensusParameters) -> usize { diff --git a/packages/fuels-core/src/parameters.rs b/packages/fuels-core/src/parameters.rs index 9b87b5f320..075c2fdc11 100644 --- a/packages/fuels-core/src/parameters.rs +++ b/packages/fuels-core/src/parameters.rs @@ -1,8 +1,9 @@ +use fuel_tx::{AssetId, StorageSlot}; + use crate::constants::{ BASE_ASSET_ID, DEFAULT_CALL_PARAMS_AMOUNT, DEFAULT_GAS_LIMIT, DEFAULT_GAS_PRICE, DEFAULT_MATURITY, }; -use fuel_tx::{AssetId, StorageSlot}; #[derive(Debug, Copy, Clone)] pub struct TxParameters { diff --git a/packages/fuels-core/src/rustfmt.rs b/packages/fuels-core/src/rustfmt.rs deleted file mode 100644 index 390f20ae60..0000000000 --- a/packages/fuels-core/src/rustfmt.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This module implements basic `rustfmt` code formatting. - -use anyhow::{anyhow, Result}; -use std::io::Write; -use std::process::{Command, Stdio}; - -/// Format the raw input source string and return formatted output. -pub fn format(source: S) -> Result -where - S: AsRef, -{ - let mut rustfmt = Command::new("rustfmt") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn()?; - - { - let stdin = rustfmt - .stdin - .as_mut() - .ok_or_else(|| anyhow!("stdin was not created for `rustfmt` child process"))?; - stdin.write_all(source.as_ref().as_bytes())?; - } - - let output = rustfmt.wait_with_output()?; - if !output.status.success() { - return Err(anyhow!( - "`rustfmt` exited with code {}:\n{}", - output.status, - String::from_utf8_lossy(&output.stderr), - )); - } - - let stdout = String::from_utf8(output.stdout)?; - Ok(stdout) -} diff --git a/packages/fuels-core/src/tokenizer.rs b/packages/fuels-core/src/tokenizer.rs index 0c31cd6526..1a536696f7 100644 --- a/packages/fuels-core/src/tokenizer.rs +++ b/packages/fuels-core/src/tokenizer.rs @@ -1,7 +1,9 @@ -use crate::{unzip_param_types, StringToken, Token}; -use fuels_types::{errors::Error, param_types::ParamType, utils::has_array_format}; use hex::FromHex; +use fuels_types::{errors::Error, param_types::ParamType, utils::has_array_format}; + +use crate::{unzip_param_types, StringToken, Token}; + #[derive(Default)] pub struct Tokenizer; @@ -404,9 +406,10 @@ fn get_array_length_from_string(ele: &str) -> usize { } #[cfg(test)] mod tests { - use super::*; use crate::Tokenizable; + use super::*; + #[test] fn tokenize_struct_excess_value_elements_expected_error() -> Result<(), Error> { let struct_params = [ diff --git a/packages/fuels-core/src/types.rs b/packages/fuels-core/src/types.rs index 6d86c9db23..a7a0cef6b8 100644 --- a/packages/fuels-core/src/types.rs +++ b/packages/fuels-core/src/types.rs @@ -1,9 +1,9 @@ -mod bits; -mod byte; -mod native; -mod sized_ascii_string; - pub use crate::types::bits::*; pub use crate::types::byte::*; pub use crate::types::native::*; pub use crate::types::sized_ascii_string::*; + +mod bits; +mod byte; +mod native; +mod sized_ascii_string; diff --git a/packages/fuels-core/src/types/bits.rs b/packages/fuels-core/src/types/bits.rs index 6f498c0555..0d3384a6aa 100644 --- a/packages/fuels-core/src/types/bits.rs +++ b/packages/fuels-core/src/types/bits.rs @@ -1,6 +1,7 @@ -use crate::{Parameterize, Token, Tokenizable}; use fuels_types::{errors::Error, param_types::ParamType}; +use crate::{Parameterize, Token, Tokenizable}; + // 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. @@ -178,10 +179,12 @@ impl Tokenizable for EvmAddress { #[cfg(test)] mod tests { - use super::*; - use crate::Tokenizable; use fuels_types::param_types::ParamType; + use crate::Tokenizable; + + use super::*; + #[test] fn test_param_type_b256() { assert_eq!(Bits256::param_type(), ParamType::B256); diff --git a/packages/fuels-core/src/types/byte.rs b/packages/fuels-core/src/types/byte.rs index f2291988e6..34a94bfffa 100644 --- a/packages/fuels-core/src/types/byte.rs +++ b/packages/fuels-core/src/types/byte.rs @@ -1,7 +1,8 @@ -use crate::{Parameterize, Token, Tokenizable}; use fuels_types::errors::Error; use fuels_types::param_types::ParamType; +use crate::{Parameterize, Token, Tokenizable}; + pub struct Byte(pub u8); impl Parameterize for Byte { diff --git a/packages/fuels-core/src/types/native.rs b/packages/fuels-core/src/types/native.rs index 423acbac0b..757dd3e6f6 100644 --- a/packages/fuels-core/src/types/native.rs +++ b/packages/fuels-core/src/types/native.rs @@ -1,8 +1,18 @@ -use crate::{Bits256, Identity, Parameterize, Token, Tokenizable}; -use fuel_tx::{Address, AssetId, ContractId}; -use fuels_types::{enum_variants::EnumVariants, errors::Error, param_types::ParamType}; use std::iter::zip; +pub use fuel_tx::{Address, AssetId, ContractId}; +use serde::{Deserialize, Serialize}; + +use fuels_types::{enum_variants::EnumVariants, errors::Error, param_types::ParamType}; + +use crate::{Bits256, Parameterize, Token, Tokenizable}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Identity { + Address(Address), + ContractId(ContractId), +} + impl Parameterize for [T; SIZE] { fn param_type() -> ParamType { ParamType::Array(Box::new(T::param_type()), SIZE) diff --git a/packages/fuels-core/src/types/sized_ascii_string.rs b/packages/fuels-core/src/types/sized_ascii_string.rs index 36d733f3d2..375d8171ec 100644 --- a/packages/fuels-core/src/types/sized_ascii_string.rs +++ b/packages/fuels-core/src/types/sized_ascii_string.rs @@ -1,7 +1,9 @@ -use crate::{Parameterize, StringToken, Token, Tokenizable}; +use std::fmt::{Debug, Display, Formatter}; + use fuels_types::errors::Error; use fuels_types::param_types::ParamType; -use std::fmt::{Debug, Display, Formatter}; + +use crate::{Parameterize, StringToken, Token, Tokenizable}; // 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 diff --git a/packages/fuels-core/src/utils.rs b/packages/fuels-core/src/utils.rs index f56726083c..172876fbc6 100644 --- a/packages/fuels-core/src/utils.rs +++ b/packages/fuels-core/src/utils.rs @@ -1,8 +1,9 @@ -use crate::ByteArray; use proc_macro2::{Ident, Span}; use sha2::{Digest, Sha256}; use syn::Ident as SynIdent; +use crate::ByteArray; + /// Hashes an encoded function selector using SHA256 and returns the first 4 bytes. /// The function selector has to have been already encoded following the ABI specs defined /// [here](https://github.com/FuelLabs/fuel-specs/blob/1be31f70c757d8390f74b9e1b3beb096620553eb/specs/protocol/abi.md) diff --git a/packages/fuels-types/src/bech32.rs b/packages/fuels-types/src/bech32.rs index 7066b1fe47..7759d544fc 100644 --- a/packages/fuels-types/src/bech32.rs +++ b/packages/fuels-types/src/bech32.rs @@ -1,11 +1,13 @@ -use crate::errors::Error; -use bech32::{FromBase32, ToBase32, Variant::Bech32m}; -use fuel_tx::{Address, Bytes32, ContractId}; use std::{ fmt::{Display, Formatter}, str::FromStr, }; +use bech32::{FromBase32, ToBase32, Variant::Bech32m}; +use fuel_tx::{Address, Bytes32, ContractId}; + +use crate::errors::Error; + // Fuel Network human-readable part for bech32 encoding pub const FUEL_BECH32_HRP: &str = "fuel"; diff --git a/packages/fuels-types/src/coin.rs b/packages/fuels-types/src/coin.rs index bc26a36a2f..fe74ca2689 100644 --- a/packages/fuels-types/src/coin.rs +++ b/packages/fuels-types/src/coin.rs @@ -1,10 +1,8 @@ +use fuel_chain_config::CoinConfig; #[cfg(feature = "fuel-core-lib")] use fuel_core::model::{Coin as ClientCoin, CoinStatus as ClientCoinStatus}; - #[cfg(not(feature = "fuel-core-lib"))] use fuel_gql_client::client::schema::coin::{Coin as ClientCoin, CoinStatus as ClientCoinStatus}; - -use fuel_chain_config::CoinConfig; use fuel_tx::{AssetId, UtxoId}; use crate::bech32::Bech32Address; diff --git a/packages/fuels-types/src/errors.rs b/packages/fuels-types/src/errors.rs index 2bf241b159..ec6d1180da 100644 --- a/packages/fuels-types/src/errors.rs +++ b/packages/fuels-types/src/errors.rs @@ -1,5 +1,6 @@ -use fuel_tx::{CheckError, Receipt}; use std::{array::TryFromSliceError, fmt, str::Utf8Error}; + +use fuel_tx::{CheckError, Receipt}; use strum::ParseError; use thiserror::Error; diff --git a/packages/fuels-types/src/message.rs b/packages/fuels-types/src/message.rs index dcba028700..213d9ac703 100644 --- a/packages/fuels-types/src/message.rs +++ b/packages/fuels-types/src/message.rs @@ -1,10 +1,8 @@ use fuel_chain_config::MessageConfig; #[cfg(feature = "fuel-core-lib")] use fuel_core::model::Message as ClientMessage; - #[cfg(not(feature = "fuel-core-lib"))] use fuel_gql_client::client::schema::message::Message as ClientMessage; - use fuel_tx::{Input, MessageId}; use crate::bech32::Bech32Address; diff --git a/packages/fuels-types/src/param_types.rs b/packages/fuels-types/src/param_types.rs index ff45c19f02..dbedfa025f 100644 --- a/packages/fuels-types/src/param_types.rs +++ b/packages/fuels-types/src/param_types.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, iter::zip}; + use crate::{ constants::WORD_SIZE, enum_variants::EnumVariants, @@ -9,7 +11,6 @@ use crate::{ }; use fuel_abi_types::program_abi::{TypeApplication, TypeDeclaration}; use itertools::Itertools; -use std::{collections::HashMap, iter::zip}; use strum_macros::EnumString; #[derive(Debug, Clone, EnumString, PartialEq, Eq)] @@ -381,13 +382,14 @@ fn try_primitive(the_type: &Type) -> Result, Error> { #[cfg(test)] mod tests { + use crate::param_types::ParamType; + + use super::*; + const WIDTH_OF_B256: usize = 4; const WIDTH_OF_U32: usize = 1; const WIDTH_OF_BOOL: usize = 1; - use super::*; - use crate::param_types::ParamType; - // The same function from fuels-test-helpers cannot // be used here because of a circular dependency fn generate_unused_field_names(types: Vec) -> Vec<(String, ParamType)> { diff --git a/packages/fuels-types/src/resource.rs b/packages/fuels-types/src/resource.rs index 9a2a1bc79b..544d1188a6 100644 --- a/packages/fuels-types/src/resource.rs +++ b/packages/fuels-types/src/resource.rs @@ -1,6 +1,7 @@ -use crate::{coin::Coin, message::Message}; use fuel_gql_client::client::schema::resource::Resource as ClientResource; +use crate::{coin::Coin, message::Message}; + #[derive(Debug)] pub enum Resource { Coin(Coin), diff --git a/packages/fuels-types/src/utils.rs b/packages/fuels-types/src/utils.rs index b2f70ef530..42168977a2 100644 --- a/packages/fuels-types/src/utils.rs +++ b/packages/fuels-types/src/utils.rs @@ -1,7 +1,8 @@ -use crate::errors::Error; use lazy_static::lazy_static; use regex::Regex; +use crate::errors::Error; + pub fn has_array_format(element: &str) -> bool { element.starts_with('[') && element.ends_with(']') } diff --git a/packages/fuels/Cargo.toml b/packages/fuels/Cargo.toml index 1898c4f638..c0261252a7 100644 --- a/packages/fuels/Cargo.toml +++ b/packages/fuels/Cargo.toml @@ -25,7 +25,6 @@ ctor = " 0.1" chrono = "0.4.2" fuel-core = { version = "0.15", default-features = false } fuel-core-interfaces = { version = "0.15", default-features = false } -fuel-gql-client = { version = "0.15", default-features = false } hex = { version = "0.4.3", default-features = false } sha2 = "0.9.5" tokio = "1.15.0" diff --git a/packages/fuels/src/lib.rs b/packages/fuels/src/lib.rs index c51ca0c2ce..059a9af489 100644 --- a/packages/fuels/src/lib.rs +++ b/packages/fuels/src/lib.rs @@ -70,10 +70,9 @@ pub mod prelude { pub use super::core::parameters::*; pub use super::core::tx::{Address, AssetId, ContractId}; pub use super::core::types::*; - pub use super::core::Identity; pub use super::core::{Token, Tokenizable}; pub use super::fuel_node::*; - pub use super::fuels_abigen::{abigen, predicate_abigen, script_abigen, setup_contract_test}; + pub use super::fuels_abigen::{abigen, setup_contract_test}; pub use super::signers::provider::*; pub use super::signers::{wallet::generate_mnemonic_phrase, Signer, Wallet, WalletUnlocked}; pub use super::test_helpers::Config; diff --git a/packages/fuels/tests/bindings.rs b/packages/fuels/tests/bindings.rs index 8ecea023d2..3946e60c64 100644 --- a/packages/fuels/tests/bindings.rs +++ b/packages/fuels/tests/bindings.rs @@ -13,17 +13,22 @@ pub fn null_contract_id() -> Bech32ContractId { async fn compile_bindings_from_contract_file() { // Generates the bindings from an ABI definition in a JSON file // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - "packages/fuels/tests/bindings/takes_ints_returns_bool-abi.json", + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "SimpleContract", + abi = "packages/fuels/tests/bindings/simple_contract" + ), + Deploy( + name = "simple_contract_instance", + contract = "SimpleContract", + wallet = "wallet" + ), ); - let wallet = launch_provider_and_get_wallet().await; - - // `SimpleContract` is the name of the contract - let contract_instance = SimpleContract::new(null_contract_id(), wallet); - - let call_handler = contract_instance.methods().takes_ints_returns_bool(42); + let call_handler = simple_contract_instance + .methods() + .takes_ints_returns_bool(42); let encoded_args = call_handler.contract_call.encoded_args.resolve(0); let encoded = format!( @@ -39,9 +44,9 @@ async fn compile_bindings_from_contract_file() { async fn compile_bindings_from_inline_contract() -> Result<(), Error> { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -76,7 +81,7 @@ async fn compile_bindings_from_inline_contract() -> Result<(), Error> { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -99,9 +104,9 @@ async fn compile_bindings_from_inline_contract() -> Result<(), Error> { async fn compile_bindings_array_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -148,7 +153,7 @@ async fn compile_bindings_array_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -174,9 +179,9 @@ async fn compile_bindings_array_input() { async fn compile_bindings_bool_array_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -223,7 +228,7 @@ async fn compile_bindings_bool_array_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -249,9 +254,9 @@ async fn compile_bindings_bool_array_input() { async fn compile_bindings_byte_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -286,7 +291,7 @@ async fn compile_bindings_byte_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -308,9 +313,9 @@ async fn compile_bindings_byte_input() { async fn compile_bindings_string_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -345,7 +350,7 @@ async fn compile_bindings_string_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -376,9 +381,9 @@ async fn compile_bindings_string_input() { async fn compile_bindings_b256_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -413,7 +418,7 @@ async fn compile_bindings_b256_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -443,9 +448,9 @@ async fn compile_bindings_b256_input() { #[tokio::test] async fn compile_bindings_evm_address_input() { - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -480,7 +485,7 @@ async fn compile_bindings_evm_address_input() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; @@ -513,9 +518,9 @@ async fn compile_bindings_evm_address_input() { async fn compile_bindings_struct_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -585,7 +590,7 @@ async fn compile_bindings_struct_input() { ] } "#, - ); + )); // Because of the abigen! macro, `MyStruct` is now in scope // and can be used! let input = MyStruct { @@ -616,9 +621,9 @@ async fn compile_bindings_struct_input() { async fn compile_bindings_nested_struct_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -688,7 +693,7 @@ async fn compile_bindings_nested_struct_input() { ] } "#, - ); + )); let inner_struct = InnerStruct { a: true }; @@ -721,9 +726,9 @@ async fn compile_bindings_nested_struct_input() { async fn compile_bindings_enum_input() { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -781,7 +786,7 @@ async fn compile_bindings_enum_input() { ] } "#, - ); + )); let variant = MyEnum::X(42); @@ -801,3 +806,119 @@ async fn compile_bindings_enum_input() { let expected = "0000000021b2784f0000000000000000000000000000002a"; assert_eq!(encoded, expected); } + +#[tokio::test] +async fn shared_types() -> Result<(), Error> { + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "ContractA", + abi = "packages/fuels/tests/bindings/sharing_types/contract_a" + ), + Abigen( + name = "ContractB", + abi = "packages/fuels/tests/bindings/sharing_types/contract_b" + ), + Deploy( + name = "contract_a", + contract = "ContractA", + wallet = "wallet" + ), + Deploy( + name = "contract_b", + contract = "ContractB", + wallet = "wallet" + ), + ); + + { + 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(()) +} diff --git a/packages/fuels/tests/bindings/sharing_types/contract_a/Forc.toml b/packages/fuels/tests/bindings/sharing_types/contract_a/Forc.toml new file mode 100644 index 0000000000..3044ac0e3a --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/contract_a/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "contract_a" + +[dependencies] +shared_lib = { path = "../shared_lib" } diff --git a/packages/fuels/tests/bindings/sharing_types/contract_a/src/main.sw b/packages/fuels/tests/bindings/sharing_types/contract_a/src/main.sw new file mode 100644 index 0000000000..d514f1744b --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/contract_a/src/main.sw @@ -0,0 +1,40 @@ +contract; +use shared_lib::*; + +struct UniqueStructToContractA { + a: T, +} + +struct StructSameNameButDifferentInternals { + a: u32, +} + +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 + } +} diff --git a/packages/fuels/tests/bindings/sharing_types/contract_b/Forc.toml b/packages/fuels/tests/bindings/sharing_types/contract_b/Forc.toml new file mode 100644 index 0000000000..99f9fbfd08 --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/contract_b/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "contract_b" + +[dependencies] +shared_lib = { path = "../shared_lib" } diff --git a/packages/fuels/tests/bindings/sharing_types/contract_b/src/main.sw b/packages/fuels/tests/bindings/sharing_types/contract_b/src/main.sw new file mode 100644 index 0000000000..57d6b82271 --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/contract_b/src/main.sw @@ -0,0 +1,40 @@ +contract; +use shared_lib::*; + +struct UniqueStructToContractB { + a: T, +} + +struct StructSameNameButDifferentInternals { + a: [u64; 1], +} + +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 + } +} diff --git a/packages/fuels/tests/bindings/sharing_types/shared_lib/Forc.toml b/packages/fuels/tests/bindings/sharing_types/shared_lib/Forc.toml new file mode 100644 index 0000000000..a5107fb5f8 --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/shared_lib/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "lib.sw" +license = "Apache-2.0" +name = "shared_lib" + +[dependencies] diff --git a/packages/fuels/tests/bindings/sharing_types/shared_lib/src/lib.sw b/packages/fuels/tests/bindings/sharing_types/shared_lib/src/lib.sw new file mode 100644 index 0000000000..ee9f3ef522 --- /dev/null +++ b/packages/fuels/tests/bindings/sharing_types/shared_lib/src/lib.sw @@ -0,0 +1,15 @@ +library shared_lib; + +pub struct SharedStruct1 { + a: T, +} + +pub struct SharedStruct2 { + a: u32, + b: SharedStruct1, +} + +pub enum SharedEnum { + a: u64, + b: SharedStruct2, +} diff --git a/packages/fuels/tests/bindings/simple_contract/Forc.toml b/packages/fuels/tests/bindings/simple_contract/Forc.toml new file mode 100644 index 0000000000..c0657fd854 --- /dev/null +++ b/packages/fuels/tests/bindings/simple_contract/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "simple_contract" + +[dependencies] diff --git a/packages/fuels/tests/bindings/simple_contract/src/main.sw b/packages/fuels/tests/bindings/simple_contract/src/main.sw new file mode 100644 index 0000000000..df81ca63d2 --- /dev/null +++ b/packages/fuels/tests/bindings/simple_contract/src/main.sw @@ -0,0 +1,11 @@ +contract; + +abi MyContract { + fn takes_ints_returns_bool(only_argument: u32) -> bool; +} + +impl MyContract for Contract { + fn takes_ints_returns_bool(only_argument: u32) -> bool { + true + } +} diff --git a/packages/fuels/tests/bindings/takes_ints_returns_bool-abi.json b/packages/fuels/tests/bindings/takes_ints_returns_bool-abi.json deleted file mode 100644 index ab652b03a7..0000000000 --- a/packages/fuels/tests/bindings/takes_ints_returns_bool-abi.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "types": [ - { - "typeId": 0, - "type": "bool", - "components": null, - "typeParameters": null - }, - { - "typeId": 1, - "type": "u32", - "components": null, - "typeParameters": null - } - ], - "functions": [ - { - "inputs": [ - { - "name": "only_argument", - "type": 1, - "typeArguments": null - } - ], - "name": "takes_ints_returns_bool", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ] -} \ No newline at end of file diff --git a/packages/fuels/tests/contracts.rs b/packages/fuels/tests/contracts.rs index 6b86f412a8..0b6d1eaf27 100644 --- a/packages/fuels/tests/contracts.rs +++ b/packages/fuels/tests/contracts.rs @@ -4,9 +4,16 @@ use std::future::Future; #[tokio::test] async fn test_multiple_args() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); // Make sure we can call the contract with multiple arguments @@ -25,22 +32,35 @@ async fn test_multiple_args() -> Result<(), Error> { } #[tokio::test] -#[allow(unused_variables)] async fn test_contract_calling_contract() -> Result<(), Error> { // Tests a contract call that calls another contract (FooCaller calls FooContract underneath) - // Load and deploy the first compiled contract setup_contract_test!( - lib_contract_instance, - wallet, - "packages/fuels/tests/contracts/lib_contract" + Wallets("wallet"), + Abigen( + name = "LibContract", + abi = "packages/fuels/tests/contracts/lib_contract" + ), + Abigen( + name = "LibContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller" + ), + Deploy( + name = "lib_contract_instance", + contract = "LibContract", + wallet = "wallet" + ), + Deploy( + name = "lib_contract_instance2", + contract = "LibContract", + wallet = "wallet" + ), + Deploy( + name = "contract_caller_instance", + contract = "LibContractCaller", + wallet = "wallet" + ), ); let lib_contract_id = lib_contract_instance.get_contract_id(); - - setup_contract_test!( - lib_contract_instance2, - None, - "packages/fuels/tests/contracts/lib_contract" - ); let lib_contract_id2 = lib_contract_instance2.get_contract_id(); // Call the contract directly. It increments the given value. @@ -48,13 +68,6 @@ async fn test_contract_calling_contract() -> Result<(), Error> { assert_eq!(43, response.value); - // Load and deploy the second compiled contract - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); - let response = contract_caller_instance .methods() .increment_from_contracts(lib_contract_id.into(), lib_contract_id2.into(), 42) @@ -92,9 +105,16 @@ async fn test_contract_calling_contract() -> Result<(), Error> { #[tokio::test] async fn test_reverting_transaction() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/revert_transaction_error" + Wallets("wallet"), + Abigen( + name = "RevertContract", + abi = "packages/fuels/tests/contracts/revert_transaction_error" + ), + Deploy( + name = "contract_instance", + contract = "RevertContract", + wallet = "wallet" + ), ); let response = contract_instance @@ -110,9 +130,16 @@ async fn test_reverting_transaction() -> Result<(), Error> { #[tokio::test] async fn test_multiple_read_calls() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/multiple_read_calls" + Wallets("wallet"), + Abigen( + name = "MultiReadContract", + abi = "packages/fuels/tests/contracts/multiple_read_calls" + ), + Deploy( + name = "contract_instance", + contract = "MultiReadContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -134,9 +161,16 @@ async fn test_multiple_read_calls() -> Result<(), Error> { #[tokio::test] async fn test_multi_call() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -159,9 +193,16 @@ async fn test_multi_call() -> Result<(), Error> { #[tokio::test] async fn test_contract_call_fee_estimation() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let gas_price = 100_000_000; @@ -197,9 +238,16 @@ async fn test_contract_call_fee_estimation() -> Result<(), Error> { #[tokio::test] async fn contract_call_has_same_estimated_and_used_gas() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let tolerance = 0.0; @@ -223,9 +271,16 @@ async fn contract_call_has_same_estimated_and_used_gas() -> Result<(), Error> { #[tokio::test] async fn mutl_call_has_same_estimated_and_used_gas() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -253,9 +308,16 @@ async fn mutl_call_has_same_estimated_and_used_gas() -> Result<(), Error> { #[tokio::test] async fn contract_method_call_respects_maturity() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/transaction_block_height" + Wallets("wallet"), + Abigen( + name = "BlockHeightContract", + abi = "packages/fuels/tests/contracts/transaction_block_height" + ), + Deploy( + name = "contract_instance", + contract = "BlockHeightContract", + wallet = "wallet" + ), ); let call_w_maturity = |call_maturity| { @@ -275,9 +337,16 @@ async fn contract_method_call_respects_maturity() -> Result<(), Error> { #[tokio::test] async fn test_auth_msg_sender_from_sdk() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/auth_testing_contract" + Wallets("wallet"), + Abigen( + name = "AuthContract", + abi = "packages/fuels/tests/contracts/auth_testing_contract" + ), + Deploy( + name = "contract_instance", + contract = "AuthContract", + wallet = "wallet" + ), ); // Contract returns true if `msg_sender()` matches `wallet.address()`. @@ -294,9 +363,16 @@ async fn test_auth_msg_sender_from_sdk() -> Result<(), Error> { #[tokio::test] async fn test_large_return_data() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/large_return_data" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/large_return_data" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -344,9 +420,16 @@ async fn test_large_return_data() -> Result<(), Error> { #[tokio::test] async fn can_handle_function_called_new() -> anyhow::Result<()> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let response = contract_instance.methods().new().call().await?.value; @@ -358,28 +441,36 @@ async fn can_handle_function_called_new() -> anyhow::Result<()> { #[tokio::test] async fn test_contract_setup_macro_deploy_with_salt() -> Result<(), Error> { // ANCHOR: contract_setup_macro_multi - // The first wallet name must be `wallet` setup_contract_test!( - lib_contract_instance, - wallet, - "packages/fuels/tests/contracts/lib_contract" + Wallets("wallet"), + Abigen( + name = "LibContract", + abi = "packages/fuels/tests/contracts/lib_contract" + ), + Abigen( + name = "LibContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller" + ), + Deploy( + name = "lib_contract_instance", + contract = "LibContract", + wallet = "wallet" + ), + 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.get_contract_id(); - // The macros that want to use the `wallet` have to set - // the wallet name to `None` - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); let contract_caller_id = contract_caller_instance.get_contract_id(); - setup_contract_test!( - contract_caller_instance2, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); let contract_caller_id2 = contract_caller_instance2.get_contract_id(); // Because we deploy with salt, we can deploy the same contract multiple times @@ -411,9 +502,16 @@ async fn test_contract_setup_macro_deploy_with_salt() -> Result<(), Error> { #[tokio::test] async fn test_wallet_getter() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); assert_eq!(contract_instance.get_wallet().address(), wallet.address()); @@ -432,9 +530,15 @@ async fn test_connect_wallet() -> anyhow::Result<()> { let wallet_2 = wallets.pop().unwrap(); setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/contracts/contract_test" + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); // ANCHOR_END: contract_setup_macro_manual_wallet @@ -494,10 +598,10 @@ async fn setup_output_variable_estimation_test( #[tokio::test] async fn test_output_variable_estimation() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" + )); let (wallets, addresses, mint_asset_id, contract_id) = setup_output_variable_estimation_test().await?; @@ -546,10 +650,10 @@ async fn test_output_variable_estimation() -> Result<(), Error> { #[tokio::test] async fn test_output_variable_estimation_default_attempts() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" + )); let (wallets, addresses, mint_asset_id, contract_id) = setup_output_variable_estimation_test().await?; @@ -575,10 +679,10 @@ async fn test_output_variable_estimation_default_attempts() -> Result<(), Error> #[tokio::test] async fn test_output_variable_estimation_multicall() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/token_ops/out/debug/token_ops-abi.json" + )); let (wallets, addresses, mint_asset_id, contract_id) = setup_output_variable_estimation_test().await?; @@ -616,9 +720,16 @@ async fn test_contract_instance_get_balances() -> Result<(), Error> { wallet.set_provider(provider.clone()); setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_id = contract_instance.get_contract_id(); @@ -657,9 +768,16 @@ async fn contract_call_futures_implement_send() -> Result<(), Error> { } setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); tokio_spawn_imitation(async move { @@ -676,21 +794,31 @@ async fn contract_call_futures_implement_send() -> Result<(), Error> { #[tokio::test] async fn test_contract_set_estimation() -> Result<(), Error> { setup_contract_test!( - lib_contract_instance, - wallet, - "packages/fuels/tests/contracts/lib_contract" + Wallets("wallet"), + Abigen( + name = "LibContract", + abi = "packages/fuels/tests/contracts/lib_contract" + ), + Abigen( + name = "LibContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller" + ), + Deploy( + name = "lib_contract_instance", + contract = "LibContract", + wallet = "wallet" + ), + Deploy( + name = "contract_caller_instance", + contract = "LibContractCaller", + wallet = "wallet" + ), ); let lib_contract_id = lib_contract_instance.get_contract_id(); let res = lib_contract_instance.methods().increment(42).call().await?; assert_eq!(43, res.value); - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); - { // Should fail due to missing external contracts let res = contract_caller_instance @@ -716,25 +844,38 @@ async fn test_contract_set_estimation() -> Result<(), Error> { #[tokio::test] async fn test_output_variable_contract_id_estimation_multicall() -> Result<(), Error> { setup_contract_test!( - lib_contract_instance, - wallet, - "packages/fuels/tests/contracts/lib_contract" + Wallets("wallet"), + Abigen( + name = "LibContract", + abi = "packages/fuels/tests/contracts/lib_contract" + ), + Abigen( + name = "LibContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller" + ), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "lib_contract_instance", + contract = "LibContract", + wallet = "wallet" + ), + Deploy( + name = "contract_caller_instance", + contract = "LibContractCaller", + wallet = "wallet" + ), + Deploy( + name = "contract_test_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let lib_contract_id = lib_contract_instance.get_contract_id(); - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); - - setup_contract_test!( - contract_test_instance, - None, - "packages/fuels/tests/contracts/contract_test" - ); - let contract_methods = contract_caller_instance.methods(); let mut multi_call_handler = MultiContractCallHandler::new(wallet.clone()); @@ -784,9 +925,16 @@ async fn test_contract_call_with_non_default_max_input() -> Result<(), Error> { wallet.set_provider(provider.clone()); setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let response = contract_instance.methods().get(5, 6).call().await?; diff --git a/packages/fuels/tests/from_token.rs b/packages/fuels/tests/from_token.rs index 21e57692ac..4dcaaab147 100644 --- a/packages/fuels/tests/from_token.rs +++ b/packages/fuels/tests/from_token.rs @@ -12,9 +12,9 @@ pub fn null_contract_id() -> Bech32ContractId { async fn create_struct_from_decoded_tokens() -> Result<(), Error> { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -72,7 +72,7 @@ async fn create_struct_from_decoded_tokens() -> Result<(), Error> { ] } "#, - ); + )); // Decoded tokens let u8_token = Token::U8(10); @@ -106,9 +106,9 @@ async fn create_struct_from_decoded_tokens() -> Result<(), Error> { async fn create_nested_struct_from_decoded_tokens() -> Result<(), Error> { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `SimpleContract`. - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -178,7 +178,7 @@ async fn create_nested_struct_from_decoded_tokens() -> Result<(), Error> { ] } "#, - ); + )); // Creating just the InnerStruct is possible let a = Token::Bool(true); diff --git a/packages/fuels/tests/logs.rs b/packages/fuels/tests/logs.rs index 75d92691cc..9f4ed39ee0 100644 --- a/packages/fuels/tests/logs.rs +++ b/packages/fuels/tests/logs.rs @@ -4,9 +4,16 @@ use fuels::tx::Receipt; #[tokio::test] async fn test_parse_logged_varibles() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); // ANCHOR: produce_logs @@ -35,9 +42,16 @@ async fn test_parse_logged_varibles() -> Result<(), Error> { #[tokio::test] async fn test_parse_logs_values() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -62,9 +76,16 @@ async fn test_parse_logs_values() -> Result<(), Error> { #[tokio::test] async fn test_parse_logs_custom_types() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -93,9 +114,16 @@ async fn test_parse_logs_custom_types() -> Result<(), Error> { #[tokio::test] async fn test_parse_logs_generic_types() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -138,9 +166,16 @@ async fn test_parse_logs_generic_types() -> Result<(), Error> { #[tokio::test] async fn test_get_logs() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); // ANCHOR: get_logs @@ -185,9 +220,16 @@ async fn test_get_logs() -> Result<(), Error> { #[tokio::test] async fn test_get_logs_with_no_logs() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -205,9 +247,16 @@ async fn test_get_logs_with_no_logs() -> Result<(), Error> { #[tokio::test] async fn test_multi_call_log_single_contract() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -248,15 +297,21 @@ async fn test_multi_call_log_single_contract() -> Result<(), Error> { #[tokio::test] async fn test_multi_call_log_multiple_contracts() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/logs/contract_logs" - ); - - setup_contract_test!( - contract_instance2, - None, - "packages/fuels/tests/logs/contract_logs" + Wallets("wallet"), + Abigen( + name = "LogContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Deploy( + name = "contract_instance", + contract = "LogContract", + wallet = "wallet" + ), + Deploy( + name = "contract_instance2", + contract = "LogContract", + wallet = "wallet" + ), ); let call_handler_1 = contract_instance.methods().produce_logs_values(); @@ -296,9 +351,25 @@ async fn test_multi_call_log_multiple_contracts() -> Result<(), Error> { async fn test_multi_call_contract_with_contract_logs() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; - abigen!( - MyContract, - "packages/fuels/tests/logs/contract_logs/out/debug/contract_logs-abi.json", + setup_contract_test!( + Abigen( + name = "MyContract", + abi = "packages/fuels/tests/logs/contract_logs" + ), + Abigen( + name = "ContractCaller", + abi = "packages/fuels/tests/logs/contract_with_contract_logs" + ), + Deploy( + name = "contract_caller_instance", + contract = "ContractCaller", + wallet = "wallet" + ), + Deploy( + name = "contract_caller_instance2", + contract = "ContractCaller", + wallet = "wallet" + ), ); let contract_id: ContractId = Contract::deploy( @@ -312,18 +383,6 @@ async fn test_multi_call_contract_with_contract_logs() -> Result<(), Error> { let contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/logs/contract_with_contract_logs" - ); - - setup_contract_test!( - contract_caller_instance2, - None, - "packages/fuels/tests/logs/contract_with_contract_logs" - ); - let call_handler_1 = contract_caller_instance .methods() .logs_from_external_contract(contract_id) @@ -368,9 +427,16 @@ fn assert_is_revert_containing_msg(msg: &str, error: Error) { #[tokio::test] async fn test_require_log() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/require" + Wallets("wallet"), + Abigen( + name = "RequireContract", + abi = "packages/fuels/tests/contracts/require" + ), + Deploy( + name = "contract_instance", + contract = "RequireContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -417,9 +483,16 @@ async fn test_require_log() -> Result<(), Error> { #[tokio::test] async fn test_multi_call_require_log_single_contract() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/require" + Wallets("wallet"), + Abigen( + name = "RequireContract", + abi = "packages/fuels/tests/contracts/require" + ), + Deploy( + name = "contract_instance", + contract = "RequireContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -467,15 +540,21 @@ async fn test_multi_call_require_log_single_contract() -> Result<(), Error> { #[tokio::test] async fn test_multi_call_require_log_multi_contract() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/require" - ); - - setup_contract_test!( - contract_instance2, - None, - "packages/fuels/tests/contracts/require" + Wallets("wallet"), + Abigen( + name = "RequireContract", + abi = "packages/fuels/tests/contracts/require" + ), + Deploy( + name = "contract_instance", + contract = "RequireContract", + wallet = "wallet" + ), + Deploy( + name = "contract_instance2", + contract = "RequireContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -525,10 +604,10 @@ async fn test_multi_call_require_log_multi_contract() -> Result<(), Error> { #[allow(unused_variables)] async fn test_script_get_logs() -> Result<(), Error> { // ANCHOR: script_logs - script_abigen!( - log_script, - "packages/fuels/tests/logs/script_logs/out/debug/script_logs-abi.json" - ); + abigen!(Script( + name = "log_script", + abi = "packages/fuels/tests/logs/script_logs/out/debug/script_logs-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/logs/script_logs/out/debug/script_logs.bin"; @@ -591,9 +670,20 @@ async fn test_script_get_logs() -> Result<(), Error> { async fn test_contract_with_contract_logs() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; - abigen!( - MyContract, - "packages/fuels/tests/logs/contract_logs/out/debug/contract_logs-abi.json", + setup_contract_test!( + Abigen( + name = "MyContract", + abi = "packages/fuels/tests/logs/contract_logs", + ), + Abigen( + name = "ContractCaller", + abi = "packages/fuels/tests/logs/contract_with_contract_logs", + ), + Deploy( + name = "contract_caller_instance", + contract = "ContractCaller", + wallet = "wallet" + ) ); let contract_id: ContractId = Contract::deploy( @@ -607,12 +697,6 @@ async fn test_contract_with_contract_logs() -> Result<(), Error> { let contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/logs/contract_with_contract_logs" - ); - let expected_logs: Vec = vec![ format!("{:?}", 64), format!("{:?}", 32), @@ -639,8 +723,13 @@ async fn test_script_logs_with_contract_logs() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; abigen!( - MyContract, - "packages/fuels/tests/logs/contract_logs/out/debug/contract_logs-abi.json", + Contract( + name="MyContract", + abi="packages/fuels/tests/logs/contract_logs/out/debug/contract_logs-abi.json", + ),Script( + name="log_script", + abi="packages/fuels/tests/logs/script_with_contract_logs/out/debug/script_with_contract_logs-abi.json" + ) ); let contract_id: ContractId = Contract::deploy( @@ -654,11 +743,6 @@ async fn test_script_logs_with_contract_logs() -> Result<(), Error> { let contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - script_abigen!( - log_script, - "packages/fuels/tests/logs/script_with_contract_logs/out/debug/script_with_contract_logs-abi.json" - ); - let bin_path = "../fuels/tests/logs/script_with_contract_logs/out/debug/script_with_contract_logs.bin"; let instance = log_script::new(wallet.clone(), bin_path); @@ -714,10 +798,10 @@ async fn test_script_logs_with_contract_logs() -> Result<(), Error> { #[tokio::test] async fn test_script_get_logs_with_type() -> Result<(), Error> { - script_abigen!( - log_script, - "packages/fuels/tests/logs/script_logs/out/debug/script_logs-abi.json" - ); + abigen!(Script( + name = "log_script", + abi = "packages/fuels/tests/logs/script_logs/out/debug/script_logs-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/logs/script_logs/out/debug/script_logs.bin"; @@ -787,10 +871,10 @@ async fn test_script_get_logs_with_type() -> Result<(), Error> { #[tokio::test] async fn test_script_require_log() -> Result<(), Error> { - script_abigen!( - log_script, - "packages/fuels/tests/scripts/script_require/out/debug/script_require-abi.json" - ); + abigen!(Script( + name = "log_script", + abi = "packages/fuels/tests/scripts/script_require/out/debug/script_require-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_require/out/debug/script_require.bin"; @@ -842,9 +926,20 @@ async fn test_script_require_log() -> Result<(), Error> { async fn test_contract_require_from_contract() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; - abigen!( - MyContract, - "packages/fuels/tests/contracts/lib_contract/out/debug/lib_contract-abi.json", + setup_contract_test!( + Abigen( + name = "MyContract", + abi = "packages/fuels/tests/contracts/lib_contract", + ), + Abigen( + name = "ContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller", + ), + Deploy( + name = "contract_caller_instance", + contract = "ContractCaller", + wallet = "wallet" + ) ); let contract_id: ContractId = Contract::deploy( @@ -858,12 +953,6 @@ async fn test_contract_require_from_contract() -> Result<(), Error> { let contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); - let error = contract_caller_instance .methods() .require_from_contract(contract_id) @@ -881,9 +970,29 @@ async fn test_contract_require_from_contract() -> Result<(), Error> { async fn test_multi_call_contract_require_from_contract() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; - abigen!( - MyContract, - "packages/fuels/tests/contracts/lib_contract/out/debug/lib_contract-abi.json", + setup_contract_test!( + Abigen( + name = "MyContract", + abi = "packages/fuels/tests/contracts/lib_contract", + ), + Abigen( + name = "ContractLogs", + abi = "packages/fuels/tests/logs/contract_logs", + ), + Abigen( + name = "ContractCaller", + abi = "packages/fuels/tests/contracts/lib_contract_caller", + ), + Deploy( + name = "contract_instance", + contract = "ContractLogs", + wallet = "wallet" + ), + Deploy( + name = "contract_caller_instance", + contract = "ContractCaller", + wallet = "wallet" + ), ); let contract_id: ContractId = Contract::deploy( @@ -897,18 +1006,6 @@ async fn test_multi_call_contract_require_from_contract() -> Result<(), Error> { let lib_contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/logs/contract_logs" - ); - - setup_contract_test!( - contract_caller_instance, - None, - "packages/fuels/tests/contracts/lib_contract_caller" - ); - let call_handler_1 = contract_instance.methods().produce_logs_values(); let call_handler_2 = contract_caller_instance @@ -936,10 +1033,8 @@ async fn test_multi_call_contract_require_from_contract() -> Result<(), Error> { async fn test_script_require_from_contract() -> Result<(), Error> { let wallet = launch_provider_and_get_wallet().await; - abigen!( - MyContract, - "packages/fuels/tests/contracts/lib_contract/out/debug/lib_contract-abi.json", - ); + abigen!(Contract(name = "MyContract", abi = "packages/fuels/tests/contracts/lib_contract/out/debug/lib_contract-abi.json"), + Script(name = "log_script", abi = "packages/fuels/tests/scripts/script_require_from_contract/out/debug/script_require_from_contract-abi.json")); let contract_id: ContractId = Contract::deploy( "../../packages/fuels/tests/contracts/lib_contract/out/debug/lib_contract.bin", @@ -952,11 +1047,6 @@ async fn test_script_require_from_contract() -> Result<(), Error> { let contract_instance = MyContract::new(contract_id.into(), wallet.clone()); - script_abigen!( - log_script, - "packages/fuels/tests/scripts/script_require_from_contract/out/debug/script_require_from_contract-abi.json" - ); - let bin_path = "../fuels/tests/scripts/script_require_from_contract/out/debug/script_require_from_contract.bin"; let instance = log_script::new(wallet.clone(), bin_path); diff --git a/packages/fuels/tests/predicates.rs b/packages/fuels/tests/predicates.rs index a8af5a2747..7d82677a37 100644 --- a/packages/fuels/tests/predicates.rs +++ b/packages/fuels/tests/predicates.rs @@ -93,10 +93,10 @@ async fn transfer_coins_and_messages_to_predicate() -> Result<(), Error> { wallet.set_provider(provider.clone()); - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_basic/out/debug/predicate_basic.bin")?; @@ -113,10 +113,10 @@ async fn transfer_coins_and_messages_to_predicate() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_single_u64() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_u64/out/debug/predicate_u64-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/predicate_u64/out/debug/predicate_u64-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_u64/out/debug/predicate_u64.bin")?; @@ -159,10 +159,10 @@ async fn spend_predicate_coins_messages_single_u64() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_basic() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_basic/out/debug/predicate_basic.bin")?; @@ -205,10 +205,7 @@ async fn spend_predicate_coins_messages_basic() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_address() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_address/out/debug/predicate_address-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_address/out/debug/predicate_address-abi.json")); let predicate = MyPredicate::load_from( "tests/predicates/predicate_address/out/debug/predicate_address.bin", @@ -260,10 +257,10 @@ async fn spend_predicate_coins_messages_address() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_enums() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_enums/out/debug/predicate_enums-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/predicate_enums/out/debug/predicate_enums-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_enums/out/debug/predicate_enums.bin")?; @@ -306,10 +303,7 @@ async fn spend_predicate_coins_messages_enums() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_structs() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_structs/out/debug/predicate_structs-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_structs/out/debug/predicate_structs-abi.json")); let predicate = MyPredicate::load_from( "tests/predicates/predicate_structs/out/debug/predicate_structs.bin", @@ -365,10 +359,10 @@ async fn spend_predicate_coins_messages_structs() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_tuple() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_tuple/out/debug/predicate_tuple-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = "packages/fuels/tests/predicates/predicate_tuple/out/debug/predicate_tuple-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_tuple/out/debug/predicate_tuple.bin")?; @@ -411,10 +405,11 @@ async fn spend_predicate_coins_messages_tuple() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_vector() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_vector/out/debug/predicate_vector-abi.json" - ); + abigen!(Predicate( + name = "MyPredicate", + abi = + "packages/fuels/tests/predicates/predicate_vector/out/debug/predicate_vector-abi.json" + )); let predicate = MyPredicate::load_from("tests/predicates/predicate_vector/out/debug/predicate_vector.bin")?; @@ -457,10 +452,7 @@ async fn spend_predicate_coins_messages_vector() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_vectors() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_vectors/out/debug/predicate_vectors-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_vectors/out/debug/predicate_vectors-abi.json")); let predicate = MyPredicate::load_from( "tests/predicates/predicate_vectors/out/debug/predicate_vectors.bin", @@ -549,10 +541,7 @@ async fn spend_predicate_coins_messages_vectors() -> Result<(), Error> { #[tokio::test] async fn spend_predicate_coins_messages_generics() -> Result<(), Error> { - predicate_abigen!( - MyPredicate, - "packages/fuels/tests/predicates/predicate_generics/out/debug/predicate_generics-abi.json" - ); + abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_generics/out/debug/predicate_generics-abi.json")); let predicate = MyPredicate::load_from( "tests/predicates/predicate_generics/out/debug/predicate_generics.bin", diff --git a/packages/fuels/tests/providers.rs b/packages/fuels/tests/providers.rs index 8f26fd9b34..ada05e6373 100644 --- a/packages/fuels/tests/providers.rs +++ b/packages/fuels/tests/providers.rs @@ -1,21 +1,20 @@ use chrono::Duration; use fuel_core::service::{Config as CoreConfig, FuelService}; -use fuel_gql_client::fuel_tx::Receipt; use fuels::{ client::{PageDirection, PaginationRequest}, prelude::*, + signers::fuel_crypto::SecretKey, + tx::Receipt, + types::{block::Block, message::Message}, }; -use fuels_signers::fuel_crypto::SecretKey; -use fuels_types::block::Block; -use fuels_types::message::Message; use std::{iter, str::FromStr}; #[tokio::test] async fn test_provider_launch_and_connect() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let mut wallet = WalletUnlocked::new_random(None); @@ -61,10 +60,10 @@ async fn test_provider_launch_and_connect() -> Result<(), Error> { #[tokio::test] async fn test_network_error() -> Result<(), anyhow::Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let mut wallet = WalletUnlocked::new_random(None); @@ -118,9 +117,15 @@ async fn test_input_message() -> Result<(), Error> { wallet.set_provider(provider); setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/contracts/contract_test" + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let spendable_messages = wallet.get_messages().await?; @@ -155,10 +160,10 @@ async fn test_input_message_pays_fee() -> Result<(), Error> { let (provider, _) = setup_test_provider(vec![], messages, None, None).await; wallet.set_provider(provider); - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); let contract_id = Contract::deploy( "tests/contracts/contract_test/out/debug/contract_test.bin", @@ -246,10 +251,7 @@ async fn can_set_custom_block_time() -> Result<(), Error> { #[tokio::test] async fn contract_deployment_respects_maturity() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/contracts/transaction_block_height/out/debug/transaction_block_height-abi.json" - ); + abigen!(Contract(name="MyContract", abi="packages/fuels/tests/contracts/transaction_block_height/out/debug/transaction_block_height-abi.json")); let config = Config { manual_blocks_enabled: true, @@ -289,9 +291,16 @@ async fn contract_deployment_respects_maturity() -> Result<(), Error> { #[tokio::test] async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let gas_limit = 225883; @@ -318,9 +327,16 @@ async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<(), Error> { #[tokio::test] async fn test_amount_and_asset_forwarding() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/token_ops" + Wallets("wallet"), + Abigen( + name = "TokenContract", + abi = "packages/fuels/tests/contracts/token_ops" + ), + Deploy( + name = "contract_instance", + contract = "TokenContract", + wallet = "wallet" + ), ); let contract_id = contract_instance.get_contract_id(); let contract_methods = contract_instance.methods(); @@ -416,9 +432,15 @@ async fn test_gas_errors() -> Result<(), Error> { wallet.set_provider(provider); setup_contract_test!( - contract_instance, - None, - "packages/fuels/tests/contracts/contract_test" + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); // Test running out of gas. Gas price as `None` will be 0. @@ -460,9 +482,16 @@ async fn test_gas_errors() -> Result<(), Error> { #[tokio::test] async fn test_call_param_gas_errors() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); // Transaction gas_limit is sufficient, call gas_forwarded is too small @@ -495,9 +524,16 @@ async fn test_call_param_gas_errors() -> Result<(), Error> { #[tokio::test] async fn test_get_gas_used() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let gas_used = contract_instance @@ -524,10 +560,10 @@ async fn testnet_hello_world() -> Result<(), Error> { // 3. The hardcoded wallet having enough funds to pay for the transaction. // This is a nice test to showcase the SDK interaction with // the testnet. But, if it becomes too problematic, we should remove it. - abigen!( - MyContract, - "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" - ); + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/contracts/contract_test/out/debug/contract_test-abi.json" + )); // Create a provider pointing to the testnet. let provider = Provider::connect("node-beta-1.fuel.network").await.unwrap(); diff --git a/packages/fuels/tests/scripts.rs b/packages/fuels/tests/scripts.rs index 1ef435b618..ba5ff5eef0 100644 --- a/packages/fuels/tests/scripts.rs +++ b/packages/fuels/tests/scripts.rs @@ -3,9 +3,16 @@ use fuels::prelude::*; #[tokio::test] async fn test_transaction_script_workflow() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let call_handler = contract_instance.methods().initialize_counter(42); @@ -23,9 +30,16 @@ async fn test_transaction_script_workflow() -> Result<(), Error> { #[tokio::test] async fn test_multi_call_script_workflow() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -54,10 +68,7 @@ async fn test_multi_call_script_workflow() -> Result<(), Error> { async fn main_function_arguments() -> Result<(), Error> { // ANCHOR: script_with_arguments // The abigen is used for the same purpose as with contracts (Rust bindings) - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments-abi.json" - ); + abigen!(Script(name="MyScript", abi="packages/fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments-abi.json")); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments.bin"; @@ -77,10 +88,7 @@ async fn main_function_arguments() -> Result<(), Error> { #[tokio::test] async fn main_function_generic_arguments() -> Result<(), Error> { - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_generic_types/out/debug/script_generic_types-abi.json" - ); + abigen!(Script(name="MyScript", abi="packages/fuels/tests/scripts/script_generic_types/out/debug/script_generic_types-abi.json")); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_generic_types/out/debug/script_generic_types.bin"; let instance = MyScript::new(wallet, bin_path); @@ -107,11 +115,11 @@ async fn main_function_generic_arguments() -> Result<(), Error> { #[tokio::test] async fn main_function_option_result() -> Result<(), Error> { - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_option_result_types/out/debug\ + abigen!(Script( + name = "MyScript", + abi = "packages/fuels/tests/scripts/script_option_result_types/out/debug\ /script_option_result_types-abi.json" - ); + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_option_result_types/out/debug/script_option_result_types.bin"; @@ -129,10 +137,11 @@ async fn main_function_option_result() -> Result<(), Error> { #[tokio::test] async fn main_function_tuple_types() -> Result<(), Error> { - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_tuple_types/out/debug/script_tuple_types-abi.json" - ); + abigen!(Script( + name = "MyScript", + abi = + "packages/fuels/tests/scripts/script_tuple_types/out/debug/script_tuple_types-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_tuple_types/out/debug/script_tuple_types.bin"; let instance = MyScript::new(wallet, bin_path); @@ -168,10 +177,10 @@ async fn main_function_tuple_types() -> Result<(), Error> { #[tokio::test] async fn main_function_vector_arguments() -> Result<(), Error> { - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_vectors/out/debug/script_vectors-abi.json" - ); + abigen!(Script( + name = "MyScript", + abi = "packages/fuels/tests/scripts/script_vectors/out/debug/script_vectors-abi.json" + )); let wallet = launch_provider_and_get_wallet().await; let bin_path = "../fuels/tests/scripts/script_vectors/out/debug/script_vectors.bin"; let instance = MyScript::new(wallet, bin_path); @@ -220,10 +229,10 @@ async fn main_function_vector_arguments() -> Result<(), Error> { #[tokio::test] async fn test_basic_script_with_tx_parameters() -> Result<(), Error> { - script_abigen!( - bimbam_script, - "packages/fuels/tests/scripts/basic_script/out/debug/basic_script-abi.json" - ); + abigen!(Script( + name = "bimbam_script", + abi = "packages/fuels/tests/scripts/basic_script/out/debug/basic_script-abi.json" + )); let num_wallets = 1; let num_coins = 1; let amount = 1000; @@ -272,10 +281,10 @@ async fn test_script_call_with_non_default_max_input() -> Result<(), Error> { let provider = Provider::new(fuel_client); wallet.set_provider(provider.clone()); - script_abigen!( - MyScript, - "packages/fuels/tests/scripts/script_vector/out/debug/script_vector-abi.json" - ); + abigen!(Script( + name = "MyScript", + abi = "packages/fuels/tests/scripts/script_vector/out/debug/script_vector-abi.json" + )); let bin_path = "../fuels/tests/scripts/script_vector/out/debug/script_vector.bin"; let instance = MyScript::new(wallet, bin_path); diff --git a/packages/fuels/tests/storage.rs b/packages/fuels/tests/storage.rs index 656b5cb08b..4c11b02825 100644 --- a/packages/fuels/tests/storage.rs +++ b/packages/fuels/tests/storage.rs @@ -1,13 +1,10 @@ +use fuels::core::tx::{Bytes32, StorageSlot}; use fuels::prelude::*; -use fuels_core::tx::{Bytes32, StorageSlot}; use std::str::FromStr; #[tokio::test] async fn test_storage_initialization() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json" - ); + abigen!(Contract(name="MyContract", abi="packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json")); let wallet = launch_provider_and_get_wallet().await; @@ -40,10 +37,7 @@ async fn test_storage_initialization() -> Result<(), Error> { #[tokio::test] async fn test_init_storage_automatically() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json" - ); + abigen!(Contract(name="MyContract", abi="packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json")); let wallet = launch_provider_and_get_wallet().await; @@ -84,10 +78,7 @@ async fn test_init_storage_automatically() -> Result<(), Error> { #[tokio::test] async fn test_init_storage_automatically_bad_json_path() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json" - ); + abigen!(Contract(name="MyContract", abi="packages/fuels/tests/storage/contract_storage_test/out/debug/contract_storage_test-abi.json")); let wallet = launch_provider_and_get_wallet().await; diff --git a/packages/fuels/tests/types.rs b/packages/fuels/tests/types.rs index bd0db03d76..0ec90cdd72 100644 --- a/packages/fuels/tests/types.rs +++ b/packages/fuels/tests/types.rs @@ -10,9 +10,16 @@ pub fn null_contract_id() -> Bech32ContractId { #[tokio::test] async fn test_methods_typeless_argument() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/empty_arguments" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/empty_arguments" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let response = contract_instance @@ -28,9 +35,16 @@ async fn test_methods_typeless_argument() -> Result<(), Error> { #[tokio::test] async fn call_with_empty_return() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/call_empty_return" + Wallets("wallet"), + Abigen( + name = "TestContract", + abi = "packages/fuels/tests/types/call_empty_return" + ), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet" + ), ); let _response = contract_instance @@ -44,9 +58,16 @@ async fn call_with_empty_return() -> Result<(), Error> { #[tokio::test] async fn type_safe_output_values() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/contract_output_test" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/contract_output_test" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); // `response`'s type matches the return type of `is_event()` @@ -76,10 +97,7 @@ async fn call_with_structs() -> Result<(), Error> { // Generates the bindings from the an ABI definition inline. // The generated bindings can be accessed through `MyContract`. // ANCHOR: struct_generation - abigen!( - MyContract, - "packages/fuels/tests/types/complex_types_contract/out/debug/complex_types_contract-abi.json" - ); + abigen!(Contract(name="MyContract", abi="packages/fuels/tests/types/complex_types_contract/out/debug/complex_types_contract-abi.json")); // Here we can use `CounterConfig`, a struct originally // defined in the contract. @@ -117,9 +135,16 @@ async fn call_with_structs() -> Result<(), Error> { #[tokio::test] async fn abigen_different_structs_same_arg_name() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/two_structs" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/two_structs" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let param_one = StructOne { foo: 42 }; @@ -139,9 +164,16 @@ async fn abigen_different_structs_same_arg_name() -> Result<(), Error> { #[tokio::test] async fn nested_structs() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/nested_structs" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/nested_structs" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let expected = AllStruct { @@ -189,9 +221,16 @@ async fn nested_structs() -> Result<(), Error> { #[tokio::test] async fn calls_with_empty_struct() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/complex_types_contract" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/complex_types_contract" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -214,11 +253,11 @@ async fn calls_with_empty_struct() -> Result<(), Error> { #[tokio::test] async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/types/enum_inside_struct/out/debug\ + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/types/enum_inside_struct/out/debug\ /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, ]; @@ -245,9 +284,16 @@ async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<(), Error> #[tokio::test] async fn test_tuples() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/tuples" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/tuples" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -312,9 +358,16 @@ async fn test_tuples() -> Result<(), Error> { #[tokio::test] async fn test_evm_address() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/evm_address" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/evm_address" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); { @@ -373,9 +426,16 @@ async fn test_evm_address() -> Result<(), Error> { #[tokio::test] async fn test_array() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); assert_eq!( @@ -393,9 +453,16 @@ async fn test_array() -> Result<(), Error> { #[tokio::test] async fn test_arrays_with_custom_types() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/contracts/contract_test" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/contracts/contract_test" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let persons = [ @@ -428,9 +495,16 @@ async fn test_arrays_with_custom_types() -> Result<(), Error> { #[tokio::test] async fn str_in_array() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/str_in_array" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/str_in_array" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap()); @@ -461,9 +535,16 @@ async fn str_in_array() -> Result<(), Error> { #[tokio::test] async fn test_enum_inside_struct() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/enum_inside_struct" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/enum_inside_struct" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let expected = Cocktail { @@ -496,9 +577,16 @@ async fn test_enum_inside_struct() -> Result<(), Error> { #[tokio::test] async fn native_types_support() -> Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/native_types" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/native_types" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let user = User { @@ -526,9 +614,16 @@ async fn native_types_support() -> Result<(), Box> { #[tokio::test] async fn enum_coding_w_variable_width_variants() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/enum_encoding" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/enum_encoding" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); // If we had a regression on the issue of enum encoding width, then we'll @@ -560,9 +655,16 @@ async fn enum_coding_w_variable_width_variants() -> Result<(), Error> { #[tokio::test] async fn enum_coding_w_unit_enums() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/enum_encoding" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/enum_encoding" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); // If we had a regression on the issue of unit enum encoding width, then @@ -592,9 +694,16 @@ async fn enum_coding_w_unit_enums() -> Result<(), Error> { #[tokio::test] async fn enum_as_input() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/enum_as_input" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/enum_as_input" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let expected = StandardEnum::Two(12345); @@ -630,11 +739,11 @@ async fn enum_as_input() -> Result<(), Error> { #[tokio::test] async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<(), Error> { - abigen!( - MyContract, - "packages/fuels/tests/types/enum_inside_struct/out/debug\ + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/types/enum_inside_struct/out/debug\ /enum_inside_struct-abi.json" - ); + )); // ANCHOR: manual_decode let shaker_in_bytes: Vec = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; @@ -658,9 +767,16 @@ async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<(), Error> { #[tokio::test] async fn type_inside_enum() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/type_inside_enum" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/type_inside_enum" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); // String inside enum @@ -715,9 +831,9 @@ async fn type_inside_enum() -> Result<(), Error> { expected = "SizedAsciiString<4> can only be constructed from a String of length 4. Got: fuell" )] async fn strings_must_have_correct_length() { - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -752,7 +868,7 @@ async fn strings_must_have_correct_length() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; let contract_instance = SimpleContract::new(null_contract_id(), wallet); @@ -766,9 +882,9 @@ async fn strings_must_have_correct_length() { expected = "SizedAsciiString must be constructed from a string containing only ascii encodable characters. Got: fueŁ" )] async fn strings_must_have_all_ascii_chars() { - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -803,7 +919,7 @@ async fn strings_must_have_all_ascii_chars() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; let contract_instance = SimpleContract::new(null_contract_id(), wallet); @@ -817,9 +933,9 @@ async fn strings_must_have_all_ascii_chars() { expected = "SizedAsciiString<4> can only be constructed from a String of length 4. Got: fuell" )] async fn strings_must_have_correct_length_custom_types() { - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -889,7 +1005,7 @@ async fn strings_must_have_correct_length_custom_types() { ] } "#, - ); + )); let wallet = launch_provider_and_get_wallet().await; let contract_instance = SimpleContract::new(null_contract_id(), wallet); @@ -903,9 +1019,9 @@ async fn strings_must_have_correct_length_custom_types() { expected = "SizedAsciiString must be constructed from a string containing only ascii encodable characters. Got: fueŁ" )] async fn strings_must_have_all_ascii_chars_custom_types() { - abigen!( - SimpleContract, - r#" + abigen!(Contract( + name = "SimpleContract", + abi = r#" { "types": [ { @@ -975,7 +1091,7 @@ async fn strings_must_have_all_ascii_chars_custom_types() { ] } "#, - ); + )); let inner_struct = InnerStruct { bar: "fueŁ".try_into().unwrap(), @@ -993,9 +1109,16 @@ async fn strings_must_have_all_ascii_chars_custom_types() { #[tokio::test] async fn test_rust_option_can_be_decoded() -> Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/options" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/options" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1038,9 +1161,16 @@ async fn test_rust_option_can_be_decoded() -> Result<(), Box Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/options" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/options" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1085,9 +1215,16 @@ async fn test_rust_option_can_be_encoded() -> Result<(), Box Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/results" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/results" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1130,9 +1267,16 @@ async fn test_rust_result_can_be_decoded() -> Result<(), Box Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/results" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/results" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1158,9 +1302,16 @@ async fn test_rust_result_can_be_encoded() -> Result<(), Box Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/identity" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/identity" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1196,9 +1347,16 @@ async fn test_identity_can_be_decoded() -> Result<(), Box #[tokio::test] async fn test_identity_can_be_encoded() -> Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/identity" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/identity" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); @@ -1237,35 +1395,44 @@ async fn test_identity_can_be_encoded() -> Result<(), Box #[tokio::test] async fn test_identity_with_two_contracts() -> Result<(), Box> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/identity" - ); - - setup_contract_test!( - contract_instance2, - None, - "packages/fuels/tests/types/identity" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/identity" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), + Deploy( + name = "contract_instance2", + contract = "TypesContract", + wallet = "wallet" + ), ); 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_instance + .methods() + .input_identity(Identity::Address(expected_address)) + .call() + .await?; - let response = contract_instance2 - .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); + assert!(response.value); + } Ok(()) } @@ -1273,11 +1440,19 @@ async fn test_identity_with_two_contracts() -> Result<(), Box anyhow::Result<()> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/generics" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/generics" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let contract_methods = contract_instance.methods(); + { // ANCHOR: generic // simple struct with a single generic param @@ -1375,9 +1550,16 @@ async fn generics_test() -> anyhow::Result<()> { #[tokio::test] async fn test_vector() -> Result<(), Error> { setup_contract_test!( - contract_instance, - wallet, - "packages/fuels/tests/types/vectors" + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/vectors" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), ); let methods = contract_instance.methods(); @@ -1454,7 +1636,18 @@ async fn test_vector() -> Result<(), Error> { #[tokio::test] async fn test_b512() -> Result<(), Error> { - setup_contract_test!(contract_instance, wallet, "packages/fuels/tests/types/b512"); + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "TypesContract", + abi = "packages/fuels/tests/types/b512" + ), + Deploy( + name = "contract_instance", + contract = "TypesContract", + wallet = "wallet" + ), + ); let contract_methods = contract_instance.methods(); // ANCHOR: b512_example diff --git a/packages/wasm-tests/Cargo.toml b/packages/wasm-tests/Cargo.toml index 88f8f32faa..0a1e5a16be 100644 --- a/packages/wasm-tests/Cargo.toml +++ b/packages/wasm-tests/Cargo.toml @@ -8,9 +8,7 @@ publish = false crate-type = ['cdylib'] [dependencies] -fuels-abigen-macro = { path = "../fuels-abigen-macro" } -fuels-core = { path = "../fuels-core" } -fuels-types = { path = "../fuels-types" } +fuels = { path = "../fuels" } getrandom = { version = "0.2", features = ["js"] } [dev-dependencies] diff --git a/packages/wasm-tests/src/lib.rs b/packages/wasm-tests/src/lib.rs index 6fe59a52ca..2f14978180 100644 --- a/packages/wasm-tests/src/lib.rs +++ b/packages/wasm-tests/src/lib.rs @@ -1,10 +1,13 @@ extern crate alloc; -use fuels_abigen_macro::wasm_abigen; +use fuels::core::abi_decoder::ABIDecoder; +use fuels::core::Tokenizable; +use fuels::fuels_abigen::wasm_abigen; +use fuels::types::param_types::ParamType; -wasm_abigen!( - no_name, - r#" +wasm_abigen!(Contract( + name = "no_name", + abi = r#" { "types": [ { @@ -96,11 +99,9 @@ wasm_abigen!( "loggedTypes": [] } "# -); +)); pub fn the_fn() { - use fuels_core::{abi_decoder::ABIDecoder, Tokenizable}; - use fuels_types::param_types::ParamType; let data = vec![ 0, 0, 0, 0, 0, 0, 3, 252, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175, 175,