diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1d67ee5..c194f418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,9 +15,9 @@ concurrency: env: CARGO_TERM_COLOR: always REGISTRY: ghcr.io - RUST_VERSION: 1.80.1 - FORC_VERSION: 0.63.3 - CORE_VERSION: 0.34.0 + RUST_VERSION: 1.83.0 + FORC_VERSION: 0.66.2 + CORE_VERSION: 0.40.0 jobs: build-sway-lib: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2b5fe245..42681a5d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,13 +2,21 @@ name: Docs on: pull_request: - paths: - - docs/** jobs: - test: + spell-check: + name: Spell Check uses: FuelLabs/github-actions/.github/workflows/mdbook-docs.yml@master with: - docs-src-path: 'docs/book/src' - spellcheck-config-path: 'docs/book/.spellcheck.yml' - \ No newline at end of file + docs-src-path: "docs/book/src" + spellcheck-config-path: "docs/book/.spellcheck.yml" + + link-check: + name: Link Check + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - uses: gaurav-nelson/github-action-markdown-link-check@1.0.15 + with: + configuration-path: "docs/book/.markdown-link-check.json" diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 1ad288bd..0b594e78 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -8,9 +8,9 @@ on: - v* env: - RUST_VERSION: 1.80.1 - FORC_VERSION: 0.63.3 - CORE_VERSION: 0.34.0 + RUST_VERSION: 1.83.0 + FORC_VERSION: 0.66.2 + CORE_VERSION: 0.40.0 jobs: deploy-contributing: diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc60b46..c107c137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -Description of the upcoming release here. - ### Added - Something new here 1 @@ -24,11 +22,38 @@ Description of the upcoming release here. - Some fix here 1 - Some fix here 2 -#### Breaking +### Breaking - Some breaking change here 1 - Some breaking change here 2 +## [Version 0.24.1] + +### Added v0.24.1 + +- [#309](https://github.com/FuelLabs/sway-libs/pull/309) Adds fallback function test cases to the Reentrancy Guard Library. +- [#310](https://github.com/FuelLabs/sway-libs/pull/310) Adds proxy tests cases to the Reentrancy Guard Library. + +### Changed v0.24.1 + +- [#305](https://github.com/FuelLabs/sway-libs/pull/305) Updates to forc `v0.66.2`, fuel-core `v0.40.0`, and fuels-rs `v0.66.9`. +- [#306](https://github.com/FuelLabs/sway-libs/pull/306) Updates the SRC-7 naming to Onchain Native Asset Metadata Standard. +- [#308](https://github.com/FuelLabs/sway-libs/pull/308) Removes comments on Cross-Contract Reentrancy vulnerability. +- [#314](https://github.com/FuelLabs/sway-libs/pull/314) Prepares for the v0.24.1 release. +- [#317](https://github.com/FuelLabs/sway-libs/pull/317) Updates the CI rust version to v1.83.0. + +### Fixed v0.24.1 + +- [#297](https://github.com/FuelLabs/sway-libs/pull/297) Fixes docs anchor in basic SRC-7 example. +- [#298](https://github.com/FuelLabs/sway-libs/pull/298) Fixes the README headers on Upgradability Libraries from an `h2` to an `h4`. +- [#302](https://github.com/FuelLabs/sway-libs/pull/302) Fixes typos in documentation. +- [#303](https://github.com/FuelLabs/sway-libs/pull/304) Fixes links in the Upgradability Library documenation. +- [#311](https://github.com/FuelLabs/sway-libs/pull/311) Fixes links in README. + +#### Breaking v0.24.1 + +- None + ## [Version 0.24.0] ### Added v0.24.0 diff --git a/Cargo.toml b/Cargo.toml index 65004cf8..84defed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [package] name = "sway-libs" -version = "0.24.0" +version = "0.24.1" edition = "2021" diff --git a/README.md b/README.md index 00c0285f..26802f06 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ For implementation details on the libraries please see the [Sway Libs Docs](http #### Math -- [Signed Integers](https://docs.fuel.network/docs/sway-libs/queue/) is an interface to implement signed integers. +- [Signed Integers](https://docs.fuel.network/docs/sway-libs/signed_integers/) is an interface to implement signed integers. > **NOTE:** > The Fixed Point Number library has been deprecated pending a re-write. @@ -66,7 +66,7 @@ For implementation details on the libraries please see the [Sway Libs Docs](http - [Queue](https://docs.fuel.network/docs/sway-libs/queue/) is a linear data structure that provides First-In-First-Out (FIFO) operations. -## Upgradability Libraries +#### Upgradability Libraries - [Upgradability](https://docs.fuel.network/docs/sway-libs/upgradability/) provides functions that can be used to implement contract upgrades via simple upgradable proxies. @@ -75,7 +75,7 @@ For implementation details on the libraries please see the [Sway Libs Docs](http To import a library, the following dependency should be added to the project's `Forc.toml` file under `[dependencies]`. ```rust -sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.0" } +sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.1" } ``` > **NOTE:** @@ -97,7 +97,7 @@ For more information about implementation please refer to the [Sway Libs Docs Hu ## Running Tests -There are two sets of tests that should be run: inline tests and sdk-harness tests. Please make sure you are using `forc v0.63.3` and `fuel-core v0.34.0`. You can check what version you are using by running the `fuelup show` command. +There are two sets of tests that should be run: inline tests and sdk-harness tests. Please make sure you are using `forc v0.66.2` and `fuel-core v0.40.0`. You can check what version you are using by running the `fuelup show` command. Make sure you are in the source directory of this repository `sway-libs/`. @@ -119,7 +119,7 @@ forc test --path tests --release --locked && cargo test --manifest-path tests/Ca Any instructions related to using a specific library should be found within the README.md of that library. > **NOTE:** -> All projects currently use `forc v0.63.3`, `fuels-rs v0.66.2` and `fuel-core v0.34.0`. +> All projects currently use `forc v0.66.2`, `fuels-rs v0.66.9` and `fuel-core v0.40.0`. ## Contributing diff --git a/docs/book/spell-check-custom-words.txt b/docs/book/spell-check-custom-words.txt index d066d8e8..1044e280 100644 --- a/docs/book/spell-check-custom-words.txt +++ b/docs/book/spell-check-custom-words.txt @@ -209,4 +209,5 @@ StorageMetadata functionly verifiably upgradable -upgradability \ No newline at end of file +upgradability +Onchain \ No newline at end of file diff --git a/docs/book/src/asset/base.md b/docs/book/src/asset/base.md index 054e51f9..c4d3aa0f 100644 --- a/docs/book/src/asset/base.md +++ b/docs/book/src/asset/base.md @@ -42,7 +42,7 @@ The following ABI and functions are also provided to set your [SRC-20](https://d ## Setting Up Storage -Once imported, the Asset Library's base functionality should be available. To use them, be sure to add the storage block bellow to your contract which enables the [SRC-20](https://docs.fuel.network/docs/sway-standards/src-20-native-asset/) standard. +Once imported, the Asset Library's base functionality should be available. To use them, be sure to add the storage block below to your contract which enables the [SRC-20](https://docs.fuel.network/docs/sway-standards/src-20-native-asset/) standard. ```sway {{#include ../../../../examples/asset/base_docs/src/main.sw:src20_storage}} diff --git a/docs/book/src/asset/index.md b/docs/book/src/asset/index.md index c9c1f6d2..ee8fe64e 100644 --- a/docs/book/src/asset/index.md +++ b/docs/book/src/asset/index.md @@ -14,4 +14,4 @@ The [SRC-3; Mint and Burn Standard](https://docs.fuel.network/docs/sway-standard ## [SRC-7 Functionality](./metadata.md) -The [SRC-7; Arbitrary Asset Metadata Standard](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) prescribes an ABI for metadata associated with Native Assets on the Fuel Network. The Asset Library's [metadata](./metadata.md) section supports the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/)'s implementation. +The [SRC-7; Onchain Asset Metadata Standard](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) prescribes an ABI for stateful metadata associated with Native Assets on the Fuel Network. The Asset Library's [metadata](./metadata.md) section supports the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/)'s implementation. diff --git a/docs/book/src/asset/metadata.md b/docs/book/src/asset/metadata.md index 792c284e..9b90ff6e 100644 --- a/docs/book/src/asset/metadata.md +++ b/docs/book/src/asset/metadata.md @@ -14,7 +14,7 @@ To import the Asset Library Base Functionality and [SRC-7](https://docs.fuel.net ## Integration with the SRC-7 Standard -The [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) definition states that the following abi implementation is required for any Native Asset on Fuel: +The [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) definition states that the following abi implementation is required for any Native Asset on Fuel which uses stateful metadata: ```sway {{#include ../../../../examples/asset/metadata_docs/src/main.sw:src7_abi}} @@ -24,20 +24,9 @@ The Asset Library has the following complimentary data type for the [SRC-7](http - `StorageMetadata` -The following additional functionality for the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/)'s `Metadata` type is provided: - -- `as_string()` -- `is_string()` -- `as_u64()` -- `is_u64()` -- `as_bytes()` -- `is_bytes()` -- `as_b256()` -- `is_b256()` - ## Setting Up Storage -Once imported, the Asset Library's metadata functionality should be available. To use them, be sure to add the storage block bellow to your contract which enables the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) standard. +Once imported, the Asset Library's metadata functionality should be available. To use them, be sure to add the storage block below to your contract which enables the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) standard. ```sway {{#include ../../../../examples/asset/metadata_docs/src/main.sw:src7_storage}} @@ -59,59 +48,8 @@ The `_set_metadata()` function follows the SRC-7 standard for logging and will e ### Implementing the SRC-7 Standard with StorageMetadata -To use the `StorageMetadata` type, simply get the stored metadata with the associated `key` and `AssetId`. The example below shows the implementation of the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) standard in combination with the Asset Library's `StorageMetadata` type with no user defined restrictions or custom functionality. +To use the `StorageMetadata` type, simply get the stored metadata with the associated `key` and `AssetId` using the provided `_metadata()` convenience function. The example below shows the implementation of the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) standard in combination with the Asset Library's `StorageMetadata` type and the `_metadata()` function with no user defined restrictions or custom functionality. ```sway {{#include ../../../../examples/asset/basic_src7/src/main.sw:basic_src7}} ``` - -## Using the `Metadata` Extensions - -The `Metadata` type defined by the [SRC-7](https://docs.fuel.network/docs/sway-standards/src-7-asset-metadata/) standard can be one of 4 states: - -```sway -pub enum Metadata { - B256: b256, - Bytes: Bytes, - Int: u64, - String: String, -} -``` - -The Asset Library enables the following functionality for the `Metadata` type: - -### `is_b256()` and `as_b256()` - -The `is_b256()` check enables checking whether the `Metadata` type is a `b256`. -The `as_b256()` returns the `b256` of the `Metadata` type. - -```sway -{{#include ../../../../examples/asset/metadata_docs/src/main.sw:as_b256}} -``` - -### `is_bytes()` and `as_bytes()` - -The `is_bytes()` check enables checking whether the `Metadata` type is a `Bytes`. -The `as_bytes()` returns the `Bytes` of the `Metadata` type. - -```sway -{{#include ../../../../examples/asset/metadata_docs/src/main.sw:as_bytes}} -``` - -### `is_u64()` and `as_u64()` - -The `is_u64()` check enables checking whether the `Metadata` type is a `u64`. -The `as_u64()` returns the `u64` of the `Metadata` type. - -```sway -{{#include ../../../../examples/asset/metadata_docs/src/main.sw:as_u64}} -``` - -### `is_string()` and `as_string()` - -The `is_string()` check enables checking whether the `Metadata` type is a `String`. -The `as_string()` returns the `String` of the `Metadata` type. - -```sway -{{#include ../../../../examples/asset/basic_src7/src/main.sw:src7_metadata_convenience_function}} -``` diff --git a/docs/book/src/asset/supply.md b/docs/book/src/asset/supply.md index 9057f994..cb3fcf82 100644 --- a/docs/book/src/asset/supply.md +++ b/docs/book/src/asset/supply.md @@ -29,7 +29,7 @@ The Asset Library has the following complimentary functions for each function in ## Setting Up Storage -Once imported, the Asset Library's supply functionality should be available. To use them, be sure to add the storage block bellow to your contract which enables the [SRC-3](https://docs.fuel.network/docs/sway-standards/src-3-minting-and-burning/) standard. +Once imported, the Asset Library's supply functionality should be available. To use them, be sure to add the storage block below to your contract which enables the [SRC-3](https://docs.fuel.network/docs/sway-standards/src-3-minting-and-burning/) standard. ```sway {{#include ../../../../examples/asset/supply_docs/src/main.sw:src3_storage}} @@ -37,7 +37,7 @@ Once imported, the Asset Library's supply functionality should be available. To ## Implementing the SRC-3 Standard with the Asset Library -To use either function, simply pass the `StorageKey` from the prescribed storage block. The example below shows the implementation of the [SRC-3](https://docs.fuel.network/docs/sway-standards/src-3-minting-and-burning/) standard in combination with the Asset Library with no user defined restrictions or custom functionality. It is recommended that the [Ownership Library](../ownership/index.md) is used in conjunction with the Asset Library;s supply functionality to ensure only a single user has permissions to mint an Asset. +To use either function, simply pass the `StorageKey` from the prescribed storage block. The example below shows the implementation of the [SRC-3](https://docs.fuel.network/docs/sway-standards/src-3-minting-and-burning/) standard in combination with the Asset Library with no user defined restrictions or custom functionality. It is recommended that the [Ownership Library](../ownership/index.md) is used in conjunction with the Asset Library's supply functionality to ensure only a single user has permissions to mint an Asset. The `_mint()` and `_burn()` functions follows the SRC-20 standard for logging and will emit the `TotalSupplyEvent` when called. diff --git a/docs/book/src/bytecode/index.md b/docs/book/src/bytecode/index.md index 0bebfc3c..e6ff36a1 100644 --- a/docs/book/src/bytecode/index.md +++ b/docs/book/src/bytecode/index.md @@ -73,7 +73,7 @@ To verify a contract's bytecode root you may call `verify_bytecode_root()` or `v ### Computing the Address from Bytecode -To compute a predicates's address you may call the `compute_predicate_address()` or `compute_predicate_address_with_configurables()` functions. +To compute a predicate's address you may call the `compute_predicate_address()` or `compute_predicate_address_with_configurables()` functions. ```sway {{#include ../../../../examples/bytecode/src/main.sw:compute_predicate_address}} diff --git a/docs/book/src/getting_started/index.md b/docs/book/src/getting_started/index.md index e57c5e38..7e6c306f 100644 --- a/docs/book/src/getting_started/index.md +++ b/docs/book/src/getting_started/index.md @@ -5,7 +5,7 @@ To import any library, the following dependency should be added to the project's `Forc.toml` file under `[dependencies]`. ```sway -sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.0" } +sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.1" } ``` For reference, here is a complete `Forc.toml` file: @@ -18,7 +18,7 @@ license = "Apache-2.0" name = "MyProject" [dependencies] -sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.0" } +sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.1" } ``` > **NOTE:** Be sure to set the tag to the latest release. diff --git a/docs/book/src/index.md b/docs/book/src/index.md index b5fa21ae..27e68d9b 100644 --- a/docs/book/src/index.md +++ b/docs/book/src/index.md @@ -2,7 +2,7 @@ The purpose of Sway Libraries is to contain libraries which users can import and use that are not part of the standard library. -There are several types of libraries that Sway Libs encompases. These include libraries that provide convenience functions, [Sway-Standards](https://github.com/FuelLabs/sway-standards) supporting libraries, data type libraries, security functionality libraries, and other tools valuable to blockchain development. +There are several types of libraries that Sway Libs encompasses. These include libraries that provide convenience functions, [Sway-Standards](https://github.com/FuelLabs/sway-standards) supporting libraries, data type libraries, security functionality libraries, and other tools valuable to blockchain development. For implementation details on the libraries please see the [Sway Libs Docs](https://fuellabs.github.io/sway-libs/master/sway_libs/). diff --git a/docs/book/src/pausable/index.md b/docs/book/src/pausable/index.md index ee72d18b..f0a56569 100644 --- a/docs/book/src/pausable/index.md +++ b/docs/book/src/pausable/index.md @@ -47,7 +47,7 @@ When developing a contract, you may want to lock functions down to a specific st It is highly recommended to integrate the [Ownership Library](../ownership/index.md) with the Pausable Library and apply restrictions the `pause()` and `unpause()` functions. This will ensure that only a single user may pause and unpause a contract in cause of emergency. Failure to apply this restriction will allow any user to obstruct a contract's functionality. -The follow example implements the `Pausable` abi and applies restrictions to it's pause/unpause functions. The owner of the contract must be set in an constructor defined by `MyConstructor` in this example. +The follow example implements the `Pausable` abi and applies restrictions to it's pause/unpause functions. The owner of the contract must be set in a constructor defined by `MyConstructor` in this example. ```sway {{#include ../../../../examples/pausable/pausable_with_ownership/src/main.sw:impl_with_ownership}} diff --git a/docs/book/src/reentrancy/index.md b/docs/book/src/reentrancy/index.md index 9d47d3a3..a7ad9d6f 100644 --- a/docs/book/src/reentrancy/index.md +++ b/docs/book/src/reentrancy/index.md @@ -2,17 +2,12 @@ The Reentrancy Guard Library provides an API to check for and disallow reentrancy on a contract. A reentrancy attack happens when a function is externally invoked during its execution, allowing it to be run multiple times in a single transaction. -The reentrancy check is used to check if a contract ID has been called more than -once in the current call stack. +The reentrancy check is used to check if a contract ID has been called more than once in the current call stack. A reentrancy, or "recursive call" attack can cause some functions to behave in unexpected ways. This can be prevented by asserting a contract has not yet been called in the current transaction. An example can be found [here](https://swcregistry.io/docs/SWC-107). For implementation details on the Reentrancy Guard Library please see the [Sway Libs Docs](https://fuellabs.github.io/sway-libs/master/sway_libs/reentrancy/index.html). -## Known Issues - -While this can protect against both single-function reentrancy and cross-function reentrancy attacks, it WILL NOT PREVENT a cross-contract reentrancy attack. - ## Importing the Reentrancy Guard Library In order to use the Reentrancy Guard library, Sway Libs must be added to the `Forc.toml` file and then imported into your Sway project. To add Sway Libs as a dependency to the `Forc.toml` file in your project please see the [Getting Started](../getting_started/index.md). @@ -45,3 +40,7 @@ To check if the current caller is a reentrant, you may call the `is_reentrant()` ```sway {{#include ../../../../examples/reentrancy/src/main.sw:is_reentrant}} ``` + +## Cross Contract Reentrancy + +Cross-Contract Reentrancy is not possible on Fuel due to the use of Native Assets. As such, no contract calls are performed when assets are transferred. However standard security practices when relying on other contracts for state should still be applied, especially when making external calls. diff --git a/docs/book/src/signed_integers/index.md b/docs/book/src/signed_integers/index.md index e15e290b..4a601401 100644 --- a/docs/book/src/signed_integers/index.md +++ b/docs/book/src/signed_integers/index.md @@ -16,7 +16,7 @@ To import the Signed Integer Number Library to your Sway Smart Contract, add the {{#include ../../../../examples/signed_integers/src/main.sw:import}} ``` -In order to use the any of the Signed Integer types, import them into your Sway project like so: +In order to use any of the Signed Integer types, import them into your Sway project like so: ```sway {{#include ../../../../examples/signed_integers/src/main.sw:import_8}} diff --git a/docs/book/src/upgradability/index.md b/docs/book/src/upgradability/index.md index bb070909..11f20c00 100644 --- a/docs/book/src/upgradability/index.md +++ b/docs/book/src/upgradability/index.md @@ -1,6 +1,6 @@ # Upgradability Library -The Upgradability Library provides functions that can be used to implement contract upgrades via simple upgradable proxies. The Upgradability Library implements the required and optional functionality from [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradable-proxies/) as well as additional functionality for ownership of the proxy contract. +The Upgradability Library provides functions that can be used to implement contract upgrades via simple upgradable proxies. The Upgradability Library implements the required and optional functionality from [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradeable-proxies/) as well as additional functionality for ownership of the proxy contract. For implementation details on the Upgradability Library please see the [Sway Libs Docs](https://fuellabs.github.io/sway-libs/master/sway_libs/upgradability/index.html). @@ -8,7 +8,7 @@ For implementation details on the Upgradability Library please see the [Sway Lib In order to use the Upgradability library, Sway Libs and [Sway Standards](https://docs.fuel.network/docs/sway-standards/) must be added to the `Forc.toml` file and then imported into your Sway project. To add Sway Libs as a dependency to the `Forc.toml` file in your project please see the [Getting Started](../getting_started/index.md). To add Sway Standards as a dependency please see the [Sway Standards Book](https://docs.fuel.network/docs/sway-standards/#using-a-standard). -To import the Upgradability Library and [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradable-proxies/) Standard to your Sway Smart Contract, add the following to your Sway file: +To import the Upgradability Library and [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradeable-proxies/) Standard to your Sway Smart Contract, add the following to your Sway file: ```sway {{#include ../../../../examples/upgradability/src/main.sw:import}} @@ -16,7 +16,7 @@ To import the Upgradability Library and [SRC-14](https://docs.fuel.network/docs/ ## Integrating the Upgradability Library into the SRC-14 Standard -To implement the [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradable-proxies/) standard with the Upgradability library, be sure to add the Sway Standards dependency to your contract. The following demonstrates the integration of the Ownership library with the SRC-14 standard. +To implement the [SRC-14](https://docs.fuel.network/docs/sway-standards/src-14-simple-upgradeable-proxies/) standard with the Upgradability library, be sure to add the Sway Standards dependency to your contract. The following demonstrates the integration of the Ownership library with the SRC-14 standard. ```sway {{#include ../../../../examples/upgradability/src/main.sw:integrate_with_src14}} diff --git a/examples/Forc.lock b/examples/Forc.lock index f859c0f1..b02db3b9 100644 --- a/examples/Forc.lock +++ b/examples/Forc.lock @@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "core" -source = "path+from-root-EF1196EF955CE54B" +source = "path+from-root-7053AAA90CC5E690" [[package]] name = "merkle_examples" @@ -146,7 +146,7 @@ dependencies = ["std"] [[package]] name = "std" -source = "git+https://github.com/fuellabs/sway?tag=v0.63.3#f55c81cce61aac31913ac0e87306cbaed7da679a" +source = "git+https://github.com/fuellabs/sway?tag=v0.66.2#31486c0b47669612acb7c64d66ecb50aea281282" dependencies = ["core"] [[package]] diff --git a/examples/asset/metadata_docs/src/main.sw b/examples/asset/metadata_docs/src/main.sw index cf0e13b1..e09ed4d0 100644 --- a/examples/asset/metadata_docs/src/main.sw +++ b/examples/asset/metadata_docs/src/main.sw @@ -20,14 +20,14 @@ storage { } // ANCHOR_END: src7_storage -// ANCHOR src7_metadata_convenience_function +// ANCHOR: src7_metadata_convenience_function impl SRC7 for Contract { #[storage(read)] fn metadata(asset: AssetId, key: String) -> Option { _metadata(storage.metadata, asset, key) } } -// ANCHOR src7_metadata_convenience_function +// ANCHOR_END: src7_metadata_convenience_function // ANCHOR: src7_set_metadata impl SetAssetMetadata for Contract { diff --git a/libs/Forc.lock b/libs/Forc.lock index 3eb119c0..7ac14885 100644 --- a/libs/Forc.lock +++ b/libs/Forc.lock @@ -1,6 +1,6 @@ [[package]] name = "core" -source = "path+from-root-EF1196EF955CE54B" +source = "path+from-root-7053AAA90CC5E690" [[package]] name = "standards" @@ -9,7 +9,7 @@ dependencies = ["std"] [[package]] name = "std" -source = "git+https://github.com/fuellabs/sway?tag=v0.63.3#f55c81cce61aac31913ac0e87306cbaed7da679a" +source = "git+https://github.com/fuellabs/sway?tag=v0.66.2#31486c0b47669612acb7c64d66ecb50aea281282" dependencies = ["core"] [[package]] diff --git a/libs/src/reentrancy.sw b/libs/src/reentrancy.sw index 39757f41..2737e5ac 100644 --- a/libs/src/reentrancy.sw +++ b/libs/src/reentrancy.sw @@ -15,8 +15,6 @@ use std::registers::frame_ptr; /// /// Not needed if the Checks-Effects-Interactions (CEI) pattern is followed (as prompted by the /// compiler). -/// > Caution: While this can protect against both single-function reentrancy and cross-function -/// reentrancy attacks, it WILL NOT PREVENT a cross-contract reentrancy attack. /// /// # Examples /// diff --git a/mlc_config.json b/mlc_config.json new file mode 100644 index 00000000..26061fe8 --- /dev/null +++ b/mlc_config.json @@ -0,0 +1,10 @@ +{ + "ignorePatterns": [ + { + "pattern": "^https://crates\\.io.*" + }, + { + "pattern": "https://github.com/FuelLabs/devrel-requests/issues/new/choose" + } + ] +} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index d1d53ffd..96fe727c 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" [dependencies] fuel-merkle = { version = "0.56.0" } -fuels = { version = "0.66.2" } +fuels = { version = "0.66.9" } sha2 = { version = "0.10" } tokio = { version = "1.12", features = ["rt", "macros"] } rand = { version = "0.8.5", default-features = false, features = [ diff --git a/tests/Forc.lock b/tests/Forc.lock index 6bd6ac1a..7ce10f0c 100644 --- a/tests/Forc.lock +++ b/tests/Forc.lock @@ -2,7 +2,7 @@ name = "admin_test" source = "member" dependencies = [ - "standards", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", "std", "sway_libs", ] @@ -22,7 +22,7 @@ dependencies = ["std"] [[package]] name = "core" -source = "path+from-root-EF1196EF955CE54B" +source = "path+from-root-7053AAA90CC5E690" [[package]] name = "i128_test" @@ -132,7 +132,7 @@ dependencies = [ name = "native_asset_lib" source = "member" dependencies = [ - "standards", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", "std", "sway_libs", ] @@ -141,7 +141,7 @@ dependencies = [ name = "ownership_test" source = "member" dependencies = [ - "standards", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", "std", "sway_libs", ] @@ -183,6 +183,29 @@ dependencies = [ "std", ] +[[package]] +name = "reentrancy_fallback_abi" +source = "path+from-root-F53252C7DB7025EE" +dependencies = ["std"] + +[[package]] +name = "reentrancy_proxy_abi" +source = "member" +dependencies = [ + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5", + "std", +] + +[[package]] +name = "reentrancy_proxy_contract" +source = "member" +dependencies = [ + "reentrancy_proxy_abi", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5", + "std", + "sway_libs", +] + [[package]] name = "reentrancy_target_abi" source = "member" @@ -193,6 +216,7 @@ name = "reentrancy_target_contract" source = "member" dependencies = [ "reentrancy_attacker_abi", + "reentrancy_fallback_abi", "reentrancy_target_abi", "std", "sway_libs", @@ -213,16 +237,21 @@ name = "standards" source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c" dependencies = ["std"] +[[package]] +name = "standards" +source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5" +dependencies = ["std"] + [[package]] name = "std" -source = "git+https://github.com/fuellabs/sway?tag=v0.63.3#f55c81cce61aac31913ac0e87306cbaed7da679a" +source = "git+https://github.com/fuellabs/sway?tag=v0.66.2#31486c0b47669612acb7c64d66ecb50aea281282" dependencies = ["core"] [[package]] name = "sway_libs" source = "path+from-root-8E8363697A2C7D80" dependencies = [ - "standards", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", "std", ] @@ -230,7 +259,7 @@ dependencies = [ name = "upgradability_test" source = "member" dependencies = [ - "standards", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", "std", "sway_libs", ] diff --git a/tests/Forc.toml b/tests/Forc.toml index 0f4249e4..84d96492 100644 --- a/tests/Forc.toml +++ b/tests/Forc.toml @@ -15,6 +15,8 @@ members = [ "./src/reentrancy/reentrancy_attack_helper_contract", "./src/reentrancy/reentrancy_target_abi", "./src/reentrancy/reentrancy_target_contract", + "./src/reentrancy/reentrancy_proxy_abi", + "./src/reentrancy/reentrancy_proxy_contract", "./src/signed_integers/signed_i8", "./src/signed_integers/signed_i16", "./src/signed_integers/signed_i32", diff --git a/tests/src/bytecode/tests/utils/mod.rs b/tests/src/bytecode/tests/utils/mod.rs index 1cce15ba..91f7f026 100644 --- a/tests/src/bytecode/tests/utils/mod.rs +++ b/tests/src/bytecode/tests/utils/mod.rs @@ -41,10 +41,10 @@ const HEX_STR_1: &str = "0xb4ca495f61ac3433e9a78cbf3adfb0e4486913bb548029cef99d1 const HEX_STR_2: &str = "0x5d617010b482b54332741fab0dfd1b15dfad07e8895360af0fb9f3e3a04b0c74"; const HEX_STR_3: &str = "0xfebf0fdda20de46a0f2261a69556b0f9fdeea85759af1edb322831cf7d0dc8d5"; const SIMPLE_PREDICATE_OFFSET: u64 = 376; -const SIMPLE_CONTRACT_OFFSET: u64 = 1400; -const COMPLEX_CONTRACT_OFFSET_1: u64 = 22968; -const COMPLEX_CONTRACT_OFFSET_2: u64 = 22928; -const COMPLEX_CONTRACT_OFFSET_3: u64 = 22856; +const SIMPLE_CONTRACT_OFFSET: u64 = 1384; +const COMPLEX_CONTRACT_OFFSET_1: u64 = 22584; +const COMPLEX_CONTRACT_OFFSET_2: u64 = 22544; +const COMPLEX_CONTRACT_OFFSET_3: u64 = 22472; pub mod abi_calls { diff --git a/tests/src/reentrancy/mod.rs b/tests/src/reentrancy/mod.rs index 410f8fdd..0961b076 100644 --- a/tests/src/reentrancy/mod.rs +++ b/tests/src/reentrancy/mod.rs @@ -9,6 +9,7 @@ use fuels::{ abigen!( Contract(name="AttackerContract", abi="src/reentrancy/reentrancy_attacker_contract/out/release/reentrancy_attacker_contract-abi.json"), Contract(name="TargetContract", abi="src/reentrancy/reentrancy_target_contract/out/release/reentrancy_target_contract-abi.json"), + Contract(name="ProxyContract", abi="src/reentrancy/reentrancy_proxy_contract/out/release/reentrancy_proxy_contract-abi.json"), Contract(name="AttackHelperContract", abi="src/reentrancy/reentrancy_attack_helper_contract/out/release/reentrancy_attack_helper_contract-abi.json"), ); @@ -22,6 +23,10 @@ const REENTRANCY_TARGET_BIN: &str = "src/reentrancy/reentrancy_target_contract/out/release/reentrancy_target_contract.bin"; const REENTRANCY_TARGET_STORAGE: &str = "src/reentrancy/reentrancy_target_contract/out/release/reentrancy_target_contract-storage_slots.json"; +const REENTRANCY_PROXY_BIN: &str = + "src/reentrancy/reentrancy_proxy_contract/out/release/reentrancy_proxy_contract.bin"; +const REENTRANCY_PROXY_STORAGE: &str = "src/reentrancy/reentrancy_proxy_contract/out/release/reentrancy_proxy_contract-storage_slots.json"; + pub async fn get_attacker_instance( wallet: WalletUnlocked, ) -> (AttackerContract, ContractId) { @@ -43,6 +48,7 @@ pub async fn get_attacker_instance( pub async fn get_target_instance( wallet: WalletUnlocked, + attack_contract_id: ContractId, ) -> (TargetContract, ContractId) { let storage_configuration = StorageConfiguration::default().add_slot_overrides_from_file(REENTRANCY_TARGET_STORAGE); @@ -57,6 +63,44 @@ pub async fn get_target_instance( let instance = TargetContract::new(id.clone(), wallet); + instance + .methods() + .set_attack_contract(attack_contract_id) + .call() + .await + .unwrap(); + + (instance, id.into()) +} + +pub async fn get_proxy_instance( + wallet: WalletUnlocked, + target_contract: ContractId, +) -> (ProxyContract, ContractId) { + let configurables = ProxyContractConfigurables::default() + .with_INITIAL_TARGET(Some(target_contract)) + .unwrap() + .with_INITIAL_OWNER(State::Initialized(wallet.address().into())) + .unwrap(); + + let storage_configuration = + StorageConfiguration::default().add_slot_overrides_from_file(REENTRANCY_PROXY_STORAGE); + + let id = Contract::load_from( + REENTRANCY_PROXY_BIN, + LoadConfiguration::default() + .with_storage_configuration(storage_configuration.unwrap()) + .with_configurables(configurables), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let instance = ProxyContract::new(id.clone(), wallet); + + instance.methods().initialize_proxy().call().await.unwrap(); + (instance, id.into()) } @@ -80,12 +124,18 @@ mod success { #[tokio::test] async fn can_detect_reentrancy() { let wallet = launch_provider_and_get_wallet().await.unwrap(); - let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await; - let (instance, target_id) = get_target_instance(wallet).await; + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); let result = attacker_instance .methods() - .launch_attack(target_id) + .launch_attack(Some(target_id)) .with_contracts(&[&instance]) .call() .await @@ -97,12 +147,18 @@ mod success { #[tokio::test] async fn can_call_guarded_function() { let wallet = launch_provider_and_get_wallet().await.unwrap(); - let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await; - let (instance, target_id) = get_target_instance(wallet).await; + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); attacker_instance .methods() - .innocent_call(target_id) + .innocent_call(Some(target_id)) .with_contracts(&[&instance]) .call() .await @@ -117,12 +173,18 @@ mod revert { #[should_panic(expected = "NonReentrant")] async fn can_block_reentrancy() { let wallet = launch_provider_and_get_wallet().await.unwrap(); - let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await; - let (instance, target_id) = get_target_instance(wallet).await; + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); attacker_instance .methods() - .launch_thwarted_attack_1(target_id) + .launch_thwarted_attack_1(Some(target_id)) .with_contracts(&[&instance]) .call() .await @@ -133,12 +195,18 @@ mod revert { #[should_panic(expected = "NonReentrant")] async fn can_block_cross_function_reentrancy() { let wallet = launch_provider_and_get_wallet().await.unwrap(); - let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await; - let (instance, target_id) = get_target_instance(wallet).await; + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); attacker_instance .methods() - .launch_thwarted_attack_2(target_id) + .launch_thwarted_attack_2(Some(target_id)) .with_contracts(&[&instance]) .call() .await @@ -149,16 +217,141 @@ mod revert { #[should_panic(expected = "NonReentrant")] async fn can_block_cross_contract_reentrancy() { let wallet = launch_provider_and_get_wallet().await.unwrap(); - let (attacker_instance, _) = get_attacker_instance(wallet.clone()).await; + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; let (helper_instance, helper_id) = get_attack_helper_id(wallet.clone()).await; - let (target_instance, target_id) = get_target_instance(wallet).await; + let (target_instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); attacker_instance .methods() - .launch_thwarted_attack_3(target_id, helper_id) + .launch_thwarted_attack_3(Some(target_id), helper_id) .with_contracts(&[&target_instance, &helper_instance]) .call() .await .unwrap(); } + + #[tokio::test] + #[should_panic(expected = "NonReentrant")] + async fn can_block_fallback_reentrancy() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet, attacker_id).await; + attacker_instance + .methods() + .set_target_contract(target_id) + .call() + .await + .unwrap(); + + attacker_instance + .methods() + .launch_thwarted_attack_4(Some(target_id)) + .with_contracts(&[&instance]) + .call() + .await + .unwrap(); + } + + #[tokio::test] + // TODO: Add expected substring when https://github.com/FuelLabs/fuels-rs/issues/1550 is resolved + #[should_panic] + async fn can_block_reentrancy_with_proxy() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet.clone(), attacker_id).await; + let (proxy_instance, proxy_id) = get_proxy_instance(wallet, target_id).await; + attacker_instance + .methods() + .set_target_contract(proxy_id) + .call() + .await + .unwrap(); + + attacker_instance + .methods() + .launch_thwarted_attack_1(Some(proxy_id)) + .with_contracts(&[&proxy_instance, &instance]) + .call() + .await + .unwrap(); + } + + #[tokio::test] + // TODO: Add expected substring when https://github.com/FuelLabs/fuels-rs/issues/1550 is resolved + #[should_panic] + async fn can_block_cross_function_reentrancy_with_proxy() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet.clone(), attacker_id).await; + let (proxy_instance, proxy_id) = get_proxy_instance(wallet, target_id).await; + attacker_instance + .methods() + .set_target_contract(proxy_id) + .call() + .await + .unwrap(); + + attacker_instance + .methods() + .launch_thwarted_attack_2(Some(proxy_id)) + .with_contracts(&[&proxy_instance, &instance]) + .call() + .await + .unwrap(); + } + + #[tokio::test] + // TODO: Add expected substring when https://github.com/FuelLabs/fuels-rs/issues/1550 is resolved + #[should_panic] + async fn can_block_cross_contract_reentrancy_with_proxy() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (helper_instance, helper_id) = get_attack_helper_id(wallet.clone()).await; + let (target_instance, target_id) = get_target_instance(wallet.clone(), attacker_id).await; + let (proxy_instance, proxy_id) = get_proxy_instance(wallet, target_id).await; + attacker_instance + .methods() + .set_target_contract(proxy_id) + .call() + .await + .unwrap(); + + attacker_instance + .methods() + .launch_thwarted_attack_3(Some(proxy_id), helper_id) + .with_contracts(&[&proxy_instance, &target_instance, &helper_instance]) + .call() + .await + .unwrap(); + } + + #[tokio::test] + // TODO: Add expected substring when https://github.com/FuelLabs/fuels-rs/issues/1550 is resolved + #[should_panic] + async fn can_block_fallback_reentrancy_with_proxy() { + let wallet = launch_provider_and_get_wallet().await.unwrap(); + let (attacker_instance, attacker_id) = get_attacker_instance(wallet.clone()).await; + let (instance, target_id) = get_target_instance(wallet.clone(), attacker_id).await; + let (proxy_instance, proxy_id) = get_proxy_instance(wallet, target_id).await; + attacker_instance + .methods() + .set_target_contract(proxy_id) + .call() + .await + .unwrap(); + + attacker_instance + .methods() + .launch_thwarted_attack_4(Some(proxy_id)) + .with_contracts(&[&proxy_instance, &instance]) + .call() + .await + .unwrap(); + } } diff --git a/tests/src/reentrancy/reentrancy_attack_fallback_abi/Forc.toml b/tests/src/reentrancy/reentrancy_attack_fallback_abi/Forc.toml new file mode 100644 index 00000000..91a50c27 --- /dev/null +++ b/tests/src/reentrancy/reentrancy_attack_fallback_abi/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reentrancy_fallback_abi" + +[dependencies] diff --git a/tests/src/reentrancy/reentrancy_attack_fallback_abi/src/main.sw b/tests/src/reentrancy/reentrancy_attack_fallback_abi/src/main.sw new file mode 100644 index 00000000..856bca10 --- /dev/null +++ b/tests/src/reentrancy/reentrancy_attack_fallback_abi/src/main.sw @@ -0,0 +1,5 @@ +library; + +abi FallbackAttack { + fn nonexistant_function(contract_id: ContractId); +} diff --git a/tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw b/tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw index f1788f87..574f166b 100644 --- a/tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw +++ b/tests/src/reentrancy/reentrancy_attacker_abi/src/main.sw @@ -1,16 +1,24 @@ library; abi Attacker { - fn launch_attack(target: ContractId) -> bool; - fn launch_thwarted_attack_1(target: ContractId); - fn launch_thwarted_attack_2(target: ContractId); - #[storage(write)] - fn launch_thwarted_attack_3(target: ContractId, helper: ContractId); - fn innocent_call(target: ContractId); + #[storage(read)] + fn launch_attack(target: Option) -> bool; + #[storage(read)] + fn launch_thwarted_attack_1(target: Option); + #[storage(read)] + fn launch_thwarted_attack_2(target: Option); + #[storage(read, write)] + fn launch_thwarted_attack_3(target: Option, helper: ContractId); + #[storage(read)] + fn launch_thwarted_attack_4(target: Option); + #[storage(read)] + fn innocent_call(target: Option); fn evil_callback_1() -> bool; fn evil_callback_2(); fn evil_callback_3(); #[storage(read)] fn evil_callback_4(); fn innocent_callback(); + #[storage(write)] + fn set_target_contract(target_contract_id: ContractId); } diff --git a/tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw b/tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw index cbf534a1..2c00f1e9 100644 --- a/tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw +++ b/tests/src/reentrancy/reentrancy_attacker_contract/src/main.sw @@ -1,64 +1,82 @@ contract; -use std::auth::*; +use std::{auth::*, call_frames::*,}; use reentrancy_target_abi::Target; use reentrancy_attacker_abi::Attacker; use reentrancy_attack_helper_abi::AttackHelper; -// Return the sender as a ContractId or panic: -fn get_msg_sender_id_or_panic() -> ContractId { - match msg_sender().unwrap() { - Identity::ContractId(v) => v, - _ => revert(0), - } -} - storage { target_id: ContractId = ContractId::zero(), helper: ContractId = ContractId::zero(), } impl Attacker for Contract { - fn launch_attack(target: ContractId) -> bool { - abi(Target, target.bits()).reentrancy_detected() + #[storage(read)] + fn launch_attack(target: Option) -> bool { + match target { + Some(target_id) => abi(Target, target_id.bits()).reentrancy_detected(), + None => abi(Target, storage.target_id.read().bits()).reentrancy_detected(), + } } - fn launch_thwarted_attack_1(target: ContractId) { - abi(Target, target.bits()).reentrance_denied(); + #[storage(read)] + fn launch_thwarted_attack_1(target: Option) { + log("launch_thwarted_attack_1"); + match target { + Some(target_id) => abi(Target, target_id.bits()).reentrance_denied(), + None => abi(Target, storage.target_id.read().bits()).reentrance_denied(), + } } - fn launch_thwarted_attack_2(target: ContractId) { - abi(Target, target.bits()).intra_contract_call(); + #[storage(read)] + fn launch_thwarted_attack_2(target: Option) { + match target { + Some(target_id) => abi(Target, target_id.bits()).intra_contract_call(), + None => abi(Target, storage.target_id.read().bits()).intra_contract_call(), + }; } - #[storage(write)] - fn launch_thwarted_attack_3(target: ContractId, helper: ContractId) { - storage.target_id.write(target); + #[storage(read, write)] + fn launch_thwarted_attack_3(target: Option, helper: ContractId) { storage.helper.write(helper); - abi(Target, target - .bits()) - .cross_contract_reentrancy_denied(); + + match target { + Some(target_id) => abi(Target, target_id.bits()).cross_contract_reentrancy_denied(), + None => abi(Target, storage.target_id.read().bits()).cross_contract_reentrancy_denied(), + }; } - fn innocent_call(target: ContractId) { - abi(Target, target.bits()).guarded_function_is_callable(); + #[storage(read)] + fn launch_thwarted_attack_4(target: Option) { + match target { + Some(target_id) => abi(Target, target_id.bits()).fallback_contract_call(), + None => abi(Target, storage.target_id.read().bits()).fallback_contract_call(), + }; + } + + #[storage(read)] + fn innocent_call(target: Option) { + match target { + Some(target_id) => abi(Target, target_id.bits()).guarded_function_is_callable(), + None => abi(Target, storage.target_id.read().bits()).guarded_function_is_callable(), + }; } fn evil_callback_1() -> bool { - abi(Attacker, ContractId::this().bits()).launch_attack(get_msg_sender_id_or_panic()) + abi(Attacker, ContractId::this().bits()).launch_attack(None) } fn evil_callback_2() { abi(Attacker, ContractId::this() .bits()) - .launch_thwarted_attack_1(get_msg_sender_id_or_panic()); + .launch_thwarted_attack_1(None); } fn evil_callback_3() { abi(Attacker, ContractId::this() .bits()) - .launch_thwarted_attack_2(get_msg_sender_id_or_panic()); + .launch_thwarted_attack_2(None); } #[storage(read)] @@ -70,4 +88,17 @@ impl Attacker for Contract { } fn innocent_callback() {} + + #[storage(write)] + fn set_target_contract(target_contract_id: ContractId) { + storage.target_id.write(target_contract_id); + } +} + +#[fallback] +fn fallback() { + let call_args = called_args::(); + + let target_abi = abi(Target, call_args.bits()); + target_abi.fallback_contract_call(); } diff --git a/tests/src/reentrancy/reentrancy_proxy_abi/Forc.lock b/tests/src/reentrancy/reentrancy_proxy_abi/Forc.lock new file mode 100644 index 00000000..2381caba --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_abi/Forc.lock @@ -0,0 +1,21 @@ +[[package]] +name = "core" +source = "path+from-root-7053AAA90CC5E690" + +[[package]] +name = "reentrancy_proxy_abi" +source = "member" +dependencies = [ + "standards", + "std", +] + +[[package]] +name = "standards" +source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5" +dependencies = ["std"] + +[[package]] +name = "std" +source = "git+https://github.com/fuellabs/sway?tag=v0.66.2#31486c0b47669612acb7c64d66ecb50aea281282" +dependencies = ["core"] diff --git a/tests/src/reentrancy/reentrancy_proxy_abi/Forc.toml b/tests/src/reentrancy/reentrancy_proxy_abi/Forc.toml new file mode 100644 index 00000000..cc7da15d --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_abi/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reentrancy_proxy_abi" + +[dependencies] +standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.1" } diff --git a/tests/src/reentrancy/reentrancy_proxy_abi/src/main.sw b/tests/src/reentrancy/reentrancy_proxy_abi/src/main.sw new file mode 100644 index 00000000..a3e938df --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_abi/src/main.sw @@ -0,0 +1,11 @@ +library; + +use standards::src5::State; + +abi OwnedProxy { + #[storage(write)] + fn initialize_proxy(); + + #[storage(write)] + fn set_proxy_owner(new_proxy_owner: State); +} diff --git a/tests/src/reentrancy/reentrancy_proxy_contract/Forc.lock b/tests/src/reentrancy/reentrancy_proxy_contract/Forc.lock new file mode 100644 index 00000000..b8f46a46 --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_contract/Forc.lock @@ -0,0 +1,44 @@ +[[package]] +name = "core" +source = "path+from-root-7053AAA90CC5E690" + +[[package]] +name = "reentrancy_proxy_abi" +source = "path+from-root-8A067F669BC61EA7" +dependencies = [ + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5", + "std", +] + +[[package]] +name = "reentrancy_proxy_contract" +source = "member" +dependencies = [ + "reentrancy_proxy_abi", + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5", + "std", + "sway_libs", +] + +[[package]] +name = "standards" +source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c" +dependencies = ["std"] + +[[package]] +name = "standards" +source = "git+https://github.com/FuelLabs/sway-standards?tag=v0.6.1#792639cdf391565e6e6a02482ea8a46d9604a6f5" +dependencies = ["std"] + +[[package]] +name = "std" +source = "git+https://github.com/fuellabs/sway?tag=v0.66.2#31486c0b47669612acb7c64d66ecb50aea281282" +dependencies = ["core"] + +[[package]] +name = "sway_libs" +source = "path+from-root-8A067F669BC61EA7" +dependencies = [ + "standards git+https://github.com/FuelLabs/sway-standards?tag=v0.6.0#65e09f95ea8b9476b171a66c8a47108f352fa32c", + "std", +] diff --git a/tests/src/reentrancy/reentrancy_proxy_contract/Forc.toml b/tests/src/reentrancy/reentrancy_proxy_contract/Forc.toml new file mode 100644 index 00000000..86a14eda --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_contract/Forc.toml @@ -0,0 +1,10 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "reentrancy_proxy_contract" + +[dependencies] +reentrancy_proxy_abi = { path = "../reentrancy_proxy_abi" } +standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.1" } +sway_libs = { path = "../../../../libs" } diff --git a/tests/src/reentrancy/reentrancy_proxy_contract/src/main.sw b/tests/src/reentrancy/reentrancy_proxy_contract/src/main.sw new file mode 100644 index 00000000..8e33872d --- /dev/null +++ b/tests/src/reentrancy/reentrancy_proxy_contract/src/main.sw @@ -0,0 +1,158 @@ +contract; + +use reentrancy_proxy_abi::OwnedProxy; +use sway_libs::{ + ownership::errors::InitializationError, + upgradability::{ + _proxy_owner, + _proxy_target, + _set_proxy_owner, + _set_proxy_target, + only_proxy_owner, + }, +}; +use standards::{src14::{SRC14, SRC14Extension}, src5::State}; +use std::execution::run_external; + +configurable { + /// The initial value of `storage::SRC14.target`. + INITIAL_TARGET: Option = None, + /// The initial value of `storage::SRC14.proxy_owner`. + INITIAL_OWNER: State = State::Uninitialized, +} + +storage { + SRC14 { + /// The [ContractId] of the target contract. + /// + /// # Additional Information + /// + /// `target` is stored at sha256("storage_SRC14_0") + target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option = None, + /// The [State] of the proxy owner. + /// + /// # Additional Information + /// + /// `proxy_owner` is stored at sha256("storage_SRC14_1") + proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized, + }, +} + +impl SRC14 for Contract { + /// Change the target contract of the proxy contract. + /// + /// # Additional Information + /// + /// This method can only be called by the `proxy_owner`. + /// + /// # Arguments + /// + /// * `new_target`: [ContractId] - The new proxy contract to which all fallback calls will be passed. + /// + /// # Reverts + /// + /// * When not called by `proxy_owner`. + /// + /// # Number of Storage Accesses + /// + /// * Reads: `1` + /// * Write: `1` + #[storage(read, write)] + fn set_proxy_target(new_target: ContractId) { + only_proxy_owner(); + _set_proxy_target(new_target); + } + + /// Returns the target contract of the proxy contract. + /// + /// # Returns + /// + /// * [Option] - The new proxy contract to which all fallback calls will be passed or `None`. + /// + /// # Number of Storage Accesses + /// + /// * Reads: `1` + #[storage(read)] + fn proxy_target() -> Option { + _proxy_target() + } +} + +impl SRC14Extension for Contract { + /// Returns the owner of the proxy contract. + /// + /// # Returns + /// + /// * [State] - Represents the state of ownership for this contract. + /// + /// # Number of Storage Accesses + /// + /// * Reads: `1` + #[storage(read)] + fn proxy_owner() -> State { + _proxy_owner() + } +} + +impl OwnedProxy for Contract { + /// Initializes the proxy contract. + /// + /// # Additional Information + /// + /// This method sets the storage values using the values of the configurable constants `INITIAL_TARGET` and `INITIAL_OWNER`. + /// This then allows methods that write to storage to be called. + /// This method can only be called once. + /// + /// # Reverts + /// + /// * When `storage::SRC14.proxy_owner` is not [State::Uninitialized]. + /// + /// # Number of Storage Accesses + /// + /// * Writes: `2` + #[storage(write)] + fn initialize_proxy() { + require( + _proxy_owner() == State::Uninitialized, + InitializationError::CannotReinitialized, + ); + + storage::SRC14.target.write(INITIAL_TARGET); + storage::SRC14.proxy_owner.write(INITIAL_OWNER); + } + + /// Changes proxy ownership to the passed State. + /// + /// # Additional Information + /// + /// This method can be used to transfer ownership between Identities or to revoke ownership. + /// + /// # Arguments + /// + /// * `new_proxy_owner`: [State] - The new state of the proxy ownership. + /// + /// # Reverts + /// + /// * When the sender is not the current proxy owner. + /// * When the new state of the proxy ownership is [State::Uninitialized]. + /// + /// # Number of Storage Accesses + /// + /// * Reads: `1` + /// * Writes: `1` + #[storage(write)] + fn set_proxy_owner(new_proxy_owner: State) { + _set_proxy_owner(new_proxy_owner); + } +} + +/// Loads and runs the target contract's code within the proxy contract's context. +/// +/// # Additional Information +/// +/// Used when a method that does not exist in the proxy contract is called. +#[fallback] +#[storage(read)] +fn fallback() { + run_external(_proxy_target().expect("FallbackError::TargetNotSet")) +} diff --git a/tests/src/reentrancy/reentrancy_target_abi/src/main.sw b/tests/src/reentrancy/reentrancy_target_abi/src/main.sw index 8aab28e0..987c27f0 100644 --- a/tests/src/reentrancy/reentrancy_target_abi/src/main.sw +++ b/tests/src/reentrancy/reentrancy_target_abi/src/main.sw @@ -1,10 +1,18 @@ library; abi Target { + #[storage(read)] fn reentrancy_detected() -> bool; + #[storage(read)] fn reentrance_denied(); + #[storage(read)] fn cross_function_reentrance_denied(); fn intra_contract_call(); fn guarded_function_is_callable(); + #[storage(read)] fn cross_contract_reentrancy_denied(); + #[storage(read)] + fn fallback_contract_call(); + #[storage(write)] + fn set_attack_contract(attack_contract_id: ContractId); } diff --git a/tests/src/reentrancy/reentrancy_target_contract/Forc.toml b/tests/src/reentrancy/reentrancy_target_contract/Forc.toml index 642fe012..1f043cad 100644 --- a/tests/src/reentrancy/reentrancy_target_contract/Forc.toml +++ b/tests/src/reentrancy/reentrancy_target_contract/Forc.toml @@ -6,5 +6,6 @@ name = "reentrancy_target_contract" [dependencies] reentrancy_attacker_abi = { path = "../reentrancy_attacker_abi" } +reentrancy_fallback_abi = { path = "../reentrancy_attack_fallback_abi" } reentrancy_target_abi = { path = "../reentrancy_target_abi" } sway_libs = { path = "../../../../libs" } diff --git a/tests/src/reentrancy/reentrancy_target_contract/src/main.sw b/tests/src/reentrancy/reentrancy_target_contract/src/main.sw index 72d06aef..39e44e58 100644 --- a/tests/src/reentrancy/reentrancy_target_contract/src/main.sw +++ b/tests/src/reentrancy/reentrancy_target_contract/src/main.sw @@ -5,41 +5,45 @@ use sway_libs::reentrancy::*; use reentrancy_attacker_abi::Attacker; use reentrancy_target_abi::Target; +use reentrancy_fallback_abi::FallbackAttack; -// Return the sender as a ContractId or panic: -pub fn get_msg_sender_id_or_panic() -> ContractId { - match msg_sender().unwrap() { - Identity::ContractId(v) => v, - _ => revert(0), - } +storage { + attack_contract: ContractId = ContractId::zero(), } impl Target for Contract { + #[storage(read)] fn reentrancy_detected() -> bool { if is_reentrant() { true } else { // this call transfers control to the attacker contract, allowing it to execute arbitrary code. - abi(Attacker, get_msg_sender_id_or_panic().bits()).evil_callback_1() + abi(Attacker, storage.attack_contract.read().bits()).evil_callback_1() } } + #[storage(read)] fn reentrance_denied() { // panic if reentrancy detected reentrancy_guard(); // this call transfers control to the attacker contract, allowing it to execute arbitrary code. - abi(Attacker, get_msg_sender_id_or_panic() + abi(Attacker, storage + .attack_contract + .read() .bits()) .evil_callback_2(); } + #[storage(read)] fn cross_function_reentrance_denied() { // panic if reentrancy detected reentrancy_guard(); // this call transfers control to the attacker contract, allowing it to execute arbitrary code. - abi(Attacker, get_msg_sender_id_or_panic() + abi(Attacker, storage + .attack_contract + .read() .bits()) .evil_callback_3(); } @@ -55,12 +59,33 @@ impl Target for Contract { reentrancy_guard(); } + #[storage(read)] fn cross_contract_reentrancy_denied() { // panic if reentrancy detected reentrancy_guard(); // this call transfers control to the attacker contract, allowing it to execute arbitrary code. - abi(Attacker, get_msg_sender_id_or_panic() + abi(Attacker, storage + .attack_contract + .read() .bits()) .evil_callback_4(); } + + #[storage(read)] + fn fallback_contract_call() { + // panic if reentrancy detected + reentrancy_guard(); + + // this call transfers control to the attacker contract, allowing it to execute arbitrary code. + abi(FallbackAttack, storage + .attack_contract + .read() + .bits()) + .nonexistant_function(ContractId::this()); + } + + #[storage(write)] + fn set_attack_contract(attack_contract_id: ContractId) { + storage.attack_contract.write(attack_contract_id); + } }