diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6de7e01442e..19285205f41 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,6 +88,22 @@ Release workflow: ``` more details: [sccache - Shared Compilation Cache](https://github.com/mozilla/sccache) +8. (Optional) Enable LFS to pull assets under `assets-lfs` + 1. Install `git-lfs`: + - MacOS + ``` + brew install git-lfs + ``` + - Ubuntu + ``` + curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash + sudo apt-get install git-lfs + ``` + 2. Pull + ``` + git lfs install + git lfs pull + ``` Bash scripts that might be of help: * `format.sh` - Formats the entire repo @@ -96,6 +112,26 @@ Bash scripts that might be of help: * `test_extra.sh` - Runs the additional tests * `assets/update-assets.sh` - Updates `Account`/`Faucet` scrypto packages (needed when your change would affect the output WASM) +## Project Layout + +- `radix-blueprint-schema-init`: Blueprint schema initialization structures, used by Radix Package Definition (RPD). +- `radix-clis`: Various CLI tools, like `resim`, `scrypto`, `rtmc` and `rtmd`. +- `radix-common-derive`: Macros for defining `Decimal` and `PreciseDecimal`. +- `radix-common`: Common libraries used by Radix Engine and Scrypto. +- `radix-engine`: The Radix Engine implementation. +- `radix-native-sdk`: Library to assist native blueprint development. +- `radix-sbor-derives`: Macros for encoding and decoding Scrypto SBOR and Manifest SBOR data. +- `radix-substate-store-impls`: Various substate store implementations. +- `radix-substate-store-interface`: The interface of any substate store. +- `radix-substate-store-queries`: Interprets data in substate data by injecting high-level knowledge. +- `radix-transaction-scenarios`: Defines various transaction scenarios, for testing. +- `radix-transactions`: Radix transaction manifest compiler, transaction models, signing and validating logic. +- `sbor-derive`: Macros for encoding and decoding SBOR data. +- `sbor`: A generic binary data format, upon which Scrypto SBOR and Manifest SBOR are built. +- `scrypto-derive`: Macros for defining blueprints. +- `scrypto-test`: Library for testing Scrypto blueprints. +- `scrypto`: Scrypto language abstraction. + ## Branching strategy ### Branches diff --git a/README.md b/README.md index 29d06871222..1769a89b6a4 100644 --- a/README.md +++ b/README.md @@ -6,195 +6,16 @@ Language for building DeFi apps on Radix. Documentation: https://docs.radixdlt.com/docs/scrypto-1 - -## Installation - -1. Install Rust - we recommend to use Rust 1.81.0 - - Install Rust (if Rust is installed you can skip this step) - - Windows: - - Download and install [`rustup-init.exe`](https://win.rustup.rs/x86_64) - - Install "Desktop development with C++" with [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) - - Install [LLVM 13.0.1](https://github.com/llvm/llvm-project/releases/download/llvmorg-13.0.1/LLVM-13.0.1-win64.exe) (make sure you tick the option that adds LLVM to the system PATH) - - Enable git long path support: - ```bash - git config --system core.longpaths true - ``` - - macOS: - - Make sure you have the `xcode` command line tools: `xcode-select --install`. - - Install cmake: `brew install cmake llvm`
- Add LLVM to the system path by adding below line to the: - - `~/.profile` if `bash` is the default shell - - `~/.zshrc` if `zsh` is the default shell - - respective config file in case of other shell - ``` - PATH="$(brew --prefix llvm)/bin:$PATH" - ``` - - You can check, which shell is the default one by inspecting `$SHELL` variable: - ```bash - echo $SHELL - ``` - - - Install the Rust compiler: - ```bash - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - ``` - - Linux: - - Make sure a C++ compiler, LLVM, cmake and clang is installed: - ```bash - sudo apt install build-essential llvm cmake clang - ``` - - Install the Rust compiler: - ```bash - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - ``` - - Set Rust version - ```bash - rustup update - rustup default 1.81.0 - ``` - -2. Enable `cargo` in the current shell: - - Windows: - - Start a new PowerShell - - Linux and macOS: - ```bash - source $HOME/.cargo/env - ``` -3. Add WebAssembly target - ```bash - rustup target add wasm32-unknown-unknown - ``` -4. Install Radix CLIs - ```bash - cargo install radix-clis - ``` - -## Getting Started - -If you want a quick walkthrough of how to deploy and run some code, please see the [Run Your First Project](https://docs-babylon.radixdlt.com/main/getting-started-developers/first-component/run-first-project.html) tutorial. If you prefer to soldier through on your own, keep reading below. - -### Writing Scrypto Code - -1. Start by creating a new package: - -```bash -scrypto new-package -cd -``` - -2. Check out the files under your current directory: - -- Source code is within `src/lib.rs`; -- Test code is within `tests/lib.rs`. - -3. Build your package: - -```bash -scrypto build -``` - -4. Run tests: - -```bash -scrypto test -``` - -### Playing with Radix Engine - -| Action | Command | -| ---------------------------------- | -------------------------------------------------------------------------- | -| Create an account | `resim new-account` | -| Change the default account | `resim set-default-account ` | -| Create a token with fixed supply | `resim new-token-fixed ` | -| Create a token with mutable supply | `resim new-token-mutable ` | -| Create a badge with fixed supply | `resim new-badge-fixed ` | -| Create a badge with mutable supply | `resim new-badge-mutable ` | -| Mint resource | `resim mint ` | -| Transfer resource | `resim transfer ` | -| Publish a package | `resim publish ` | -| Call a function | `resim call-function ` | -| Call a method | `resim call-method ` | -| Export the definition of a package | `resim export-package-definition ` | -| Show info about an entity | `resim show ` | -| Show info about default account | `resim show` | -| List all entities | `resim show-ledger ` | -| Reset ledger state | `resim reset` | - -**Note:** The commands use the default account as transaction sender. - -## Compile blueprints with dockerized `radix-clis` - -Follow this guide to build reproducible WASM and RDP files for your Scrypto blueprints. - -### Using local docker image - -The Dockerfile in the root of the repo should be work to build a docker image which will contain all the dependencies needed to be able build a blueprint using scrypto. - -Build the docker image like. From the repo root - -```bash -docker build -t radixdlt/radix-clis . -``` - -Then build your package by just running - -```bash -docker run -v :/src radixdlt/radix-clis -``` - -### Using published docker image - -If you would like to avoid building the docker image, you can skip the build step and do the second step directly, docker will automatically download the docker image we publish - -Build your blueprints directly with - -```bash -docker run -v :/src radixdlt/radix-clis -``` - -## Project Layout - -- `radix-blueprint-schema-init`: Blueprint schema initialization structures, used by Radix Package Definition (RPD). -- `radix-clis`: Various CLI tools, like `resim`, `scrypto`, `rtmc` and `rtmd`. -- `radix-common-derive`: Macros for defining `Decimal` and `PreciseDecimal`. -- `radix-common`: Common libraries used by Radix Engine and Scrypto. -- `radix-engine`: The Radix Engine implementation. -- `radix-native-sdk`: Library to assist native blueprint development. -- `radix-sbor-derives`: Macros for encoding and decoding Scrypto SBOR and Manifest SBOR data. -- `radix-substate-store-impls`: Various substate store implementations. -- `radix-substate-store-interface`: The interface of any substate store. -- `radix-substate-store-queries`: Interprets data in substate data by injecting high-level knowledge. -- `radix-transaction-scenarios`: Defines various transaction scenarios, for testing. -- `radix-transactions`: Radix transaction manifest compiler, transaction models, signing and validating logic. -- `sbor-derive`: Macros for encoding and decoding SBOR data. -- `sbor`: A generic binary data format, upon which Scrypto SBOR and Manifest SBOR are built. -- `scrypto-derive`: Macros for defining blueprints. -- `scrypto-test`: Library for testing Scrypto blueprints. -- `scrypto`: Scrypto language abstraction. - -## LFS - -Assets under `assets-lfs` are stored in Git LFS. - -To fetch files from LFS, install `git-lfs` first: - -- MacOS - ``` - brew install git-lfs - ``` -- Ubuntu - ``` - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash - sudo apt-get install git-lfs - ``` - -and then: - -``` -git lfs install -git lfs pull -``` +## Scrypto Development + +- Setup + - [Install rust and the scrypto toolchain](https://docs.radixdlt.com/docs/getting-rust-scrypto) + - [Choose an IDE and install the extensions](https://docs.radixdlt.com/docs/choosing-an-ide) +- Learning + - The [learning step-by-step](https://docs.radixdlt.com/docs/learning-step-by-step) guide is our recommended path to start your journey learning scrypto. +- CLI references + - [`scrypto`](https://docs.radixdlt.com/docs/scrypto-cli-tool) + - [`resim`](https://docs.radixdlt.com/docs/resim-radix-engine-simulator) ## Contribute diff --git a/radix-common/src/constants/transaction_execution.rs b/radix-common/src/constants/transaction_execution.rs index a3af1cfb2c0..a76d224b757 100644 --- a/radix-common/src/constants/transaction_execution.rs +++ b/radix-common/src/constants/transaction_execution.rs @@ -124,6 +124,3 @@ pub const USD_PRICE_IN_XRD: &str = "16.666666666666666666"; /// The maximum that a package or component owner is allowed to set their method royalty to. 10 USD pub const MAX_PER_FUNCTION_ROYALTY_IN_XRD: &str = "166.666666666666666666"; - -/// The maximum number of "live" buffers maintained by Scrypto runtime. -pub const MAX_NUMBER_OF_BUFFERS: usize = 32; diff --git a/radix-common/src/data/manifest/model/manifest_resource_assertion.rs b/radix-common/src/data/manifest/model/manifest_resource_assertion.rs index 11d07c36817..ee975ed8bd0 100644 --- a/radix-common/src/data/manifest/model/manifest_resource_assertion.rs +++ b/radix-common/src/data/manifest/model/manifest_resource_assertion.rs @@ -1,3 +1,5 @@ +use core::ops::AddAssign; + use crate::internal_prelude::*; // This file isn't part of the Manifest SBOR value model, but is included here @@ -151,42 +153,60 @@ impl ManifestResourceConstraints { pub fn validate( self, - mut fungible_resources: IndexMap, - mut non_fungible_resources: IndexMap>, - exact: bool, - ) -> Result<(), ManifestResourceConstraintsError> { + balances: AggregateResourceBalances, + prevent_unspecified_resource_balances: bool, + ) -> Result<(), ResourceConstraintsError> { + let AggregateResourceBalances { + fungible_resources, + non_fungible_resources, + } = balances; + + if prevent_unspecified_resource_balances { + for (resource_address, amount) in fungible_resources.iter() { + if !self.specified_resources.contains_key(resource_address) && amount.is_positive() + { + return Err( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: *resource_address, + }, + ); + } + } + + for (resource_address, ids) in non_fungible_resources.iter() { + if !self.specified_resources.contains_key(resource_address) && !ids.is_empty() { + return Err( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: *resource_address, + }, + ); + } + } + } + + let zero_balance = Decimal::ZERO; + let empty_ids: IndexSet = Default::default(); for (resource_address, constraint) in self.specified_resources { if resource_address.is_fungible() { let amount = fungible_resources - .swap_remove(&resource_address) - .unwrap_or_default(); - constraint - .validate_fungible(amount) - .map_err(ManifestResourceConstraintsError::ResourceConstraint)?; + .get(&resource_address) + .unwrap_or(&zero_balance); + constraint.validate_fungible(*amount).map_err(|error| { + ResourceConstraintsError::ResourceConstraintFailed { + resource_address, + error, + } + })?; } else { let ids = non_fungible_resources - .swap_remove(&resource_address) - .unwrap_or_default(); - constraint - .validate_non_fungible(ids) - .map_err(ManifestResourceConstraintsError::ResourceConstraint)?; - } - } - - if exact { - for (fungible_resource, amount) in fungible_resources { - if amount.is_positive() { - return Err(ManifestResourceConstraintsError::UnwantedResourcesExist( - fungible_resource, - )); - } - } - for (non_fungible_resource, ids) in non_fungible_resources { - if !ids.is_empty() { - return Err(ManifestResourceConstraintsError::UnwantedResourcesExist( - non_fungible_resource, - )); - } + .get(&resource_address) + .unwrap_or(&empty_ids); + constraint.validate_non_fungible(ids).map_err(|error| { + ResourceConstraintsError::ResourceConstraintFailed { + resource_address, + error, + } + })?; } } @@ -194,10 +214,54 @@ impl ManifestResourceConstraints { } } -#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -pub enum ManifestResourceConstraintsError { - ResourceConstraint(ResourceConstraintError), - UnwantedResourcesExist(ResourceAddress), +pub struct AggregateResourceBalances { + fungible_resources: IndexMap, + non_fungible_resources: IndexMap>, +} + +impl AggregateResourceBalances { + pub fn new() -> Self { + Self { + fungible_resources: Default::default(), + non_fungible_resources: Default::default(), + } + } + + pub fn add_fungible(&mut self, resource_address: ResourceAddress, amount: Decimal) { + if amount.is_positive() { + self.fungible_resources + .entry(resource_address) + .or_default() + .add_assign(amount); + } + } + + pub fn add_non_fungible( + &mut self, + resource_address: ResourceAddress, + ids: IndexSet, + ) { + if !ids.is_empty() { + self.non_fungible_resources + .entry(resource_address) + .or_default() + .extend(ids); + } + } + + pub fn validate_only( + self, + constraints: ManifestResourceConstraints, + ) -> Result<(), ResourceConstraintsError> { + constraints.validate(self, true) + } + + pub fn validate_includes( + self, + constraints: ManifestResourceConstraints, + ) -> Result<(), ResourceConstraintsError> { + constraints.validate(self, false) + } } impl IntoIterator for ManifestResourceConstraints { @@ -257,7 +321,7 @@ impl ManifestResourceConstraint { pub fn validate_non_fungible( self, - ids: IndexSet, + ids: &IndexSet, ) -> Result<(), ResourceConstraintError> { let amount = Decimal::from(ids.len()); match self { @@ -270,7 +334,7 @@ impl ManifestResourceConstraint { if amount.ne(&expected_exact_amount) { return Err(ResourceConstraintError::ExpectedExactAmount { actual_amount: amount, - expected_exact_amount, + expected_amount: expected_exact_amount, }); } } @@ -283,25 +347,26 @@ impl ManifestResourceConstraint { } } ManifestResourceConstraint::ExactNonFungibles(expected_exact_ids) => { - if !expected_exact_ids.eq(&ids) { - return Err(ResourceConstraintError::ExpectedExactNonFungibles { - expected_exact_ids: Box::new(expected_exact_ids), - actual_ids: Box::new(ids), + if let Some(missing_id) = expected_exact_ids.difference(ids).next() { + return Err(ResourceConstraintError::NonFungibleMissing { + missing_id: missing_id.clone(), + }); + } + if let Some(disallowed_id) = ids.difference(&expected_exact_ids).next() { + return Err(ResourceConstraintError::NonFungibleNotAllowed { + disallowed_id: disallowed_id.clone(), }); } } ManifestResourceConstraint::AtLeastNonFungibles(expected_at_least_ids) => { - if !expected_at_least_ids.is_subset(&ids) { - return Err(ResourceConstraintError::ExpectedAtLeastNonFungibles { - actual_ids: Box::new(ids), - expected_at_least_ids: Box::new(expected_at_least_ids.clone()), + if let Some(missing_id) = expected_at_least_ids.difference(ids).next() { + return Err(ResourceConstraintError::NonFungibleMissing { + missing_id: missing_id.clone(), }); } } ManifestResourceConstraint::General(constraint) => { - constraint - .validate_non_fungible(&ids) - .map_err(ResourceConstraintError::GeneralResourceConstraintError)?; + constraint.validate_non_fungible_ids(ids)?; } } @@ -319,7 +384,7 @@ impl ManifestResourceConstraint { if amount.ne(&expected_exact_amount) { return Err(ResourceConstraintError::ExpectedExactAmount { actual_amount: amount, - expected_exact_amount, + expected_amount: expected_exact_amount, }); } } @@ -332,15 +397,17 @@ impl ManifestResourceConstraint { } } ManifestResourceConstraint::ExactNonFungibles(..) => { - return Err(ResourceConstraintError::ExpectedNonFungibleResourceButIsFungible); + return Err( + ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource, + ); } ManifestResourceConstraint::AtLeastNonFungibles(..) => { - return Err(ResourceConstraintError::ExpectedNonFungibleResourceButIsFungible); + return Err( + ResourceConstraintError::NonFungibleConstraintNotValidForFungibleResource, + ); } ManifestResourceConstraint::General(constraint) => { - constraint - .validate_fungible(amount) - .map_err(ResourceConstraintError::GeneralResourceConstraintError)?; + constraint.validate_fungible(amount)?; } } @@ -348,27 +415,43 @@ impl ManifestResourceConstraint { } } +#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] +pub enum ResourceConstraintsError { + UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: ResourceAddress, + }, + ResourceConstraintFailed { + resource_address: ResourceAddress, + error: ResourceConstraintError, + }, +} + #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] pub enum ResourceConstraintError { + NonFungibleConstraintNotValidForFungibleResource, ExpectedNonZeroAmount, ExpectedExactAmount { - expected_exact_amount: Decimal, + expected_amount: Decimal, actual_amount: Decimal, }, ExpectedAtLeastAmount { expected_at_least_amount: Decimal, actual_amount: Decimal, }, - ExpectedExactNonFungibles { - expected_exact_ids: Box>, - actual_ids: Box>, + ExpectedAtMostAmount { + expected_at_most_amount: Decimal, + actual_amount: Decimal, + }, + // We purposefully don't have an `ExpectedExactNonFungibles` to avoid + // a malicious transaction creating a 2MB native error with a massive + // list of required non-fungibles. Instead, we return one of + // `RequiredNonFungibleMissing` or `NonFungibleNotAllowed`. + NonFungibleMissing { + missing_id: NonFungibleLocalId, }, - ExpectedAtLeastNonFungibles { - expected_at_least_ids: Box>, - actual_ids: Box>, + NonFungibleNotAllowed { + disallowed_id: NonFungibleLocalId, }, - GeneralResourceConstraintError(GeneralResourceConstraintError), - ExpectedNonFungibleResourceButIsFungible, } /// [`GeneralResourceConstraint`] captures constraints on the balance of a single fungible @@ -508,73 +591,32 @@ impl GeneralResourceConstraint { && self.is_valid_independent_of_resource_type() } - pub fn validate_fungible(&self, amount: Decimal) -> Result<(), GeneralResourceConstraintError> { + pub fn validate_fungible(&self, amount: Decimal) -> Result<(), ResourceConstraintError> { self.validate_amount(amount)?; // Static checker should have validated that there are no invalid non fungible checks Ok(()) } - pub fn validate_non_fungible( + pub fn validate_non_fungible_ids( &self, ids: &IndexSet, - ) -> Result<(), GeneralResourceConstraintError> { + ) -> Result<(), ResourceConstraintError> { self.validate_amount(Decimal::from(ids.len()))?; - for id in &self.required_ids { - if !ids.contains(id) { - return Err(GeneralResourceConstraintError::MissingRequiredNonFungible { - missing_id: id.clone(), - }); - } - } - match &self.allowed_ids { - AllowedIds::Allowlist(allowed) => { - for id in ids { - if !allowed.contains(id) { - return Err(GeneralResourceConstraintError::InvalidNonFungible { - invalid_id: id.clone(), - }); - } - } - } - AllowedIds::Any => {} + if let Some(missing_id) = self.required_ids.difference(ids).next() { + return Err(ResourceConstraintError::NonFungibleMissing { + missing_id: missing_id.clone(), + }); } + self.allowed_ids.validate_ids(ids)?; + Ok(()) } - fn validate_amount(&self, amount: Decimal) -> Result<(), GeneralResourceConstraintError> { - match self.lower_bound { - LowerBound::NonZero => { - if amount.is_zero() { - return Err(GeneralResourceConstraintError::ExpectedNonZeroAmount); - } - } - LowerBound::Inclusive(inclusive) => { - if amount < inclusive { - return Err( - GeneralResourceConstraintError::LowerBoundAmountNotSatisfied { - lower_bound_inclusive: inclusive, - actual: amount, - }, - ); - } - } - } - match self.upper_bound { - UpperBound::Inclusive(inclusive) => { - if amount > inclusive { - return Err( - GeneralResourceConstraintError::UpperBoundAmountNotSatisfied { - upper_bound_inclusive: inclusive, - actual: amount, - }, - ); - } - } - UpperBound::Unbounded => {} - } - + fn validate_amount(&self, amount: Decimal) -> Result<(), ResourceConstraintError> { + self.lower_bound.validate_amount(&amount)?; + self.upper_bound.validate_amount(&amount)?; Ok(()) } @@ -644,25 +686,6 @@ impl GeneralResourceConstraint { } } -#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -pub enum GeneralResourceConstraintError { - ExpectedNonZeroAmount, - LowerBoundAmountNotSatisfied { - lower_bound_inclusive: Decimal, - actual: Decimal, - }, - UpperBoundAmountNotSatisfied { - upper_bound_inclusive: Decimal, - actual: Decimal, - }, - MissingRequiredNonFungible { - missing_id: NonFungibleLocalId, - }, - InvalidNonFungible { - invalid_id: NonFungibleLocalId, - }, -} - /// Represents a lower bound on a non-negative decimal. /// /// [`LowerBound::NonZero`] represents a lower bound of an infinitesimal amount above 0, @@ -738,6 +761,25 @@ impl LowerBound { lower_bound.resolve() } + pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> { + match self { + LowerBound::NonZero => { + if amount.is_zero() { + return Err(ResourceConstraintError::ExpectedNonZeroAmount); + } + } + LowerBound::Inclusive(inclusive) => { + if amount < inclusive { + return Err(ResourceConstraintError::ExpectedAtLeastAmount { + expected_at_least_amount: *inclusive, + actual_amount: *amount, + }); + } + } + } + Ok(()) + } + pub fn is_valid_for_fungible_use(&self) -> bool { match self { LowerBound::NonZero => true, @@ -906,6 +948,21 @@ impl UpperBound { upper_bound.resolve() } + pub fn validate_amount(&self, amount: &Decimal) -> Result<(), ResourceConstraintError> { + match self { + UpperBound::Inclusive(inclusive) => { + if amount > inclusive { + return Err(ResourceConstraintError::ExpectedAtMostAmount { + expected_at_most_amount: *inclusive, + actual_amount: *amount, + }); + } + } + UpperBound::Unbounded => {} + } + Ok(()) + } + pub fn is_valid_for_fungible_use(&self) -> bool { match self { UpperBound::Inclusive(amount) => !amount.is_negative(), @@ -997,6 +1054,25 @@ impl AllowedIds { Self::Any } + pub fn validate_ids( + &self, + ids: &IndexSet, + ) -> Result<(), ResourceConstraintError> { + match self { + AllowedIds::Allowlist(allowed) => { + for id in ids { + if !allowed.contains(id) { + return Err(ResourceConstraintError::NonFungibleNotAllowed { + disallowed_id: id.clone(), + }); + } + } + } + AllowedIds::Any => {} + } + Ok(()) + } + pub fn allowlist_equivalent_length(&self) -> usize { match self { Self::Allowlist(allowlist) => allowlist.len(), diff --git a/radix-common/src/math/bnum_integer.rs b/radix-common/src/math/bnum_integer.rs index 2551e89f263..ad38823d26e 100644 --- a/radix-common/src/math/bnum_integer.rs +++ b/radix-common/src/math/bnum_integer.rs @@ -8,7 +8,7 @@ use num_bigint::BigInt; use num_integer::Roots; use num_traits::{FromPrimitive, One, Pow, ToPrimitive, Zero}; use paste::paste; -use sbor::rust::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use sbor::rust::cmp::{Ord, PartialEq, PartialOrd}; use sbor::rust::convert::{From, TryFrom}; use sbor::rust::fmt; use sbor::rust::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign}; @@ -48,7 +48,7 @@ macro_rules! types { #[doc = "`" $t "` will have the same methods and traits as"] /// the built-in counterpart. #[cfg_attr(feature = "fuzzing", derive(Arbitrary, Serialize, Deserialize))] - #[derive(Clone , Copy)] + #[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] #[repr(transparent)] pub struct $t(pub $wrap); @@ -100,35 +100,6 @@ macro_rules! types { Self::ONE } } - - impl Ord for $t { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for $t { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - // The following three trait implementations must be aligned. - - impl PartialEq for $t { - fn eq(&self, other: &Self) -> bool { - self.0.eq(&other.0) - } - } - - impl Eq for $t { - } - - impl sbor::rust::hash::Hash for $t { - fn hash(&self, state: &mut H) where H: sbor::rust::hash::Hasher { - self.0.hash(state) - } - } )* } }; diff --git a/radix-common/src/math/bnum_integer/test.rs b/radix-common/src/math/bnum_integer/test.rs index 34209459956..32df740d256 100644 --- a/radix-common/src/math/bnum_integer/test.rs +++ b/radix-common/src/math/bnum_integer/test.rs @@ -6,6 +6,8 @@ use paste::paste; use num_bigint::{BigInt, Sign}; use radix_common::*; +#[allow(unused_imports)] // It's needed by the `test_impl!` macro +use sbor::rust::cmp::Ordering; test_impl! {I192, I256, I320, I384, I448, I512, I768} test_impl! {U192, U256, U320, U384, U448, U512, U768} diff --git a/radix-engine-tests/Cargo.toml b/radix-engine-tests/Cargo.toml index 801ef21a324..f8420e26e91 100644 --- a/radix-engine-tests/Cargo.toml +++ b/radix-engine-tests/Cargo.toml @@ -73,6 +73,10 @@ harness = false name = "transaction_validation" harness = false +[[bench]] +name = "metadata_validation" +harness = false + [lib] # Our benches use criterion.rs - which requires disabling normal bench harness # generation below - to workaround this issue: diff --git a/radix-engine-tests/assets/blueprints/Cargo.lock b/radix-engine-tests/assets/blueprints/Cargo.lock index efb280d4a85..f841325eb01 100644 --- a/radix-engine-tests/assets/blueprints/Cargo.lock +++ b/radix-engine-tests/assets/blueprints/Cargo.lock @@ -30,6 +30,17 @@ dependencies = [ "scrypto", ] +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.76", +] + [[package]] name = "auth_scenarios" version = "1.0.0" @@ -115,6 +126,12 @@ dependencies = [ "scrypto", ] +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + [[package]] name = "cast" version = "1.0.0" @@ -556,6 +573,20 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "metadata2" +version = "1.0.0" +dependencies = [ + "scrypto", +] + +[[package]] +name = "metadata3" +version = "1.0.0" +dependencies = [ + "scrypto", +] + [[package]] name = "metadata_component" version = "1.0.0" @@ -1100,6 +1131,26 @@ dependencies = [ "digest", ] +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", +] + +[[package]] +name = "sha256_in_scrypto" +version = "1.0.0" +dependencies = [ + "scrypto", + "sha256", +] + [[package]] name = "sha3" version = "0.10.8" @@ -1242,6 +1293,13 @@ dependencies = [ "scrypto", ] +[[package]] +name = "threading" +version = "1.0.0" +dependencies = [ + "scrypto", +] + [[package]] name = "threadpool" version = "1.8.1" @@ -1310,6 +1368,13 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm_limits" +version = "1.0.0" +dependencies = [ + "scrypto", +] + [[package]] name = "wasm_non_mvp" version = "1.0.0" diff --git a/radix-engine-tests/assets/blueprints/Cargo.toml b/radix-engine-tests/assets/blueprints/Cargo.toml index 3837073cc66..8442c27e06b 100644 --- a/radix-engine-tests/assets/blueprints/Cargo.toml +++ b/radix-engine-tests/assets/blueprints/Cargo.toml @@ -27,6 +27,8 @@ members = [ "local_component", "local_recursion", "metadata_component", + "metadata2", + "metadata3", "module", "non_fungible", "package", @@ -55,6 +57,7 @@ members = [ "royalty-auth", "scrypto_env", "system", + "threading", "transaction_limits", "transaction_runtime", "tx_processor_access", @@ -77,6 +80,8 @@ members = [ "oracles/oracle_v3", "steal", "locker-factory", + "wasm_limits", + "sha256_in_scrypto", ] resolver = "2" diff --git a/radix-engine-tests/assets/blueprints/metadata2/Cargo.toml b/radix-engine-tests/assets/blueprints/metadata2/Cargo.toml new file mode 100644 index 00000000000..5e26dd152f7 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/metadata2/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "metadata2" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } + +[lib] +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/radix-engine-tests/assets/blueprints/metadata2/src/lib.rs b/radix-engine-tests/assets/blueprints/metadata2/src/lib.rs new file mode 100644 index 00000000000..7f70652dd54 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/metadata2/src/lib.rs @@ -0,0 +1,53 @@ +use scrypto::prelude::*; + +#[blueprint] +mod metadata2 { + struct M {} + + impl M { + pub fn f() { + let package_address = METADATA_MODULE_PACKAGE; + let blueprint_name = METADATA_BLUEPRINT; + let function_name = METADATA_CREATE_WITH_DATA_IDENT; + + /* + // Use the following code to generate payload + + let mut urls: Vec = vec![]; + for _ in 0..10_000 { + urls.push(UncheckedUrl::of(format!("https://www.example.com/test?q=x"))); + } + urls.push(UncheckedUrl::of("invalid")); + + let mut data = MetadataInit::default(); + data.set_metadata("urls", urls.clone()); + let args = scrypto_encode(&MetadataCreateWithDataInput { data }).unwrap(); + */ + let mut args = Vec::with_capacity(330033); + args.extend([ + 92, 33, 1, 35, 12, 33, 1, 4, 117, 114, 108, 115, 2, 34, 1, 1, 34, 141, 1, 32, 12, + 145, 78, + ]); + for _ in 0..10_000 { + args.extend([ + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, + 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + ]); + } + args.extend([7, 105, 110, 118, 97, 108, 105, 100, 1, 0]); + + unsafe { + scrypto::prelude::wasm_api::blueprint::blueprint_call( + package_address.as_bytes().as_ptr(), + package_address.as_bytes().len(), + blueprint_name.as_ptr(), + blueprint_name.len(), + function_name.as_ptr(), + function_name.len(), + args.as_ptr(), + args.len(), + ); + } + } + } +} diff --git a/radix-engine-tests/assets/blueprints/metadata3/Cargo.toml b/radix-engine-tests/assets/blueprints/metadata3/Cargo.toml new file mode 100644 index 00000000000..4a870901009 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/metadata3/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "metadata3" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } + +[lib] +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/radix-engine-tests/assets/blueprints/metadata3/src/lib.rs b/radix-engine-tests/assets/blueprints/metadata3/src/lib.rs new file mode 100644 index 00000000000..ab377bd7fbc --- /dev/null +++ b/radix-engine-tests/assets/blueprints/metadata3/src/lib.rs @@ -0,0 +1,231 @@ +use scrypto::prelude::*; + +#[blueprint] +mod metadata3 { + struct M {} + + impl M { + pub fn f() { + let package_address = METADATA_MODULE_PACKAGE; + let blueprint_name = METADATA_BLUEPRINT; + let function_name = METADATA_CREATE_WITH_DATA_IDENT; + + /* + // Use the following code to generate payload + + let mut urls: Vec = vec![]; + for _ in 0..100 { + urls.push(UncheckedUrl::of(format!("https://www.example.com/test?q=x"))); + } + + let mut data = MetadataInit::default(); + data.set_metadata("urls", urls.clone()); + let args = scrypto_encode(&MetadataCreateWithDataInput { data }).unwrap(); + */ + let args: [u8; 3324] = [ + 92, 33, 1, 35, 12, 33, 1, 4, 117, 114, 108, 115, 2, 34, 1, 1, 34, 141, 1, 32, 12, + 100, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, + 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, + 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, + 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, + 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, + 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, + 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, + 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, + 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, + 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, + 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, + 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, + 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, + 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, + 101, 115, 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, + 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, + 116, 63, 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, + 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, + 113, 61, 120, 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, + 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, + 32, 104, 116, 116, 112, 115, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, + 108, 101, 46, 99, 111, 109, 47, 116, 101, 115, 116, 63, 113, 61, 120, 1, 0, + ]; + + loop { + scrypto::prelude::wasm_api::copy_buffer(unsafe { + scrypto::prelude::wasm_api::blueprint::blueprint_call( + package_address.as_bytes().as_ptr(), + package_address.as_bytes().len(), + blueprint_name.as_ptr(), + blueprint_name.len(), + function_name.as_ptr(), + function_name.len(), + args.as_ptr(), + args.len(), + ) + }); + } + } + } +} diff --git a/radix-engine-tests/assets/blueprints/sha256_in_scrypto/Cargo.toml b/radix-engine-tests/assets/blueprints/sha256_in_scrypto/Cargo.toml new file mode 100644 index 00000000000..b46d6ea9207 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/sha256_in_scrypto/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "sha256_in_scrypto" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } +sha256 = { version = "1.5.0", default-features = false } + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/radix-engine-tests/assets/blueprints/sha256_in_scrypto/src/lib.rs b/radix-engine-tests/assets/blueprints/sha256_in_scrypto/src/lib.rs new file mode 100644 index 00000000000..4a1792359e8 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/sha256_in_scrypto/src/lib.rs @@ -0,0 +1,16 @@ +use scrypto::prelude::*; +use sha256::digest; + +#[blueprint] +mod s { + struct Test {} + + impl Test { + pub fn f() { + loop { + // Avoid loop being optimised away! + std::hint::black_box(digest("hello")); + } + } + } +} diff --git a/radix-engine-tests/assets/blueprints/threading/Cargo.toml b/radix-engine-tests/assets/blueprints/threading/Cargo.toml new file mode 100644 index 00000000000..ac91523b449 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/threading/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "threading" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } + +[lib] +crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/radix-engine-tests/assets/blueprints/threading/src/lib.rs b/radix-engine-tests/assets/blueprints/threading/src/lib.rs new file mode 100644 index 00000000000..f371a903cfe --- /dev/null +++ b/radix-engine-tests/assets/blueprints/threading/src/lib.rs @@ -0,0 +1,49 @@ +use scrypto::prelude::*; + +#[derive(ScryptoSbor, NonFungibleData)] +pub struct MyData {} + +#[blueprint] +mod threading { + + struct Threading { + vault: Vault, + } + + impl Threading { + pub fn new(bucket: Bucket) -> ComponentAddress { + Self { + vault: Vault::with_bucket(bucket), + } + .instantiate() + .prepare_to_globalize(OwnerRole::None) + .globalize() + .address() + } + + pub fn new2(reservation: GlobalAddressReservation) -> ComponentAddress { + Self { + vault: Vault::new(XRD), + } + .instantiate() + .prepare_to_globalize(OwnerRole::None) + .with_address(reservation) + .globalize() + .address() + } + + pub fn create_locked_bucket(&mut self, amount: Decimal) -> (Bucket, Proof) { + let bucket = self.vault.take(amount); + let proof = bucket.create_proof_of_all(); + (bucket, proof) + } + + pub fn call(node_id: NodeId) { + let address = unsafe { ComponentAddress::new_unchecked(node_id.into()) }; + let component: Global = address.into(); + component.method(); + } + + pub fn method(&self) {} + } +} diff --git a/radix-engine-tests/assets/blueprints/wasm_limits/Cargo.toml b/radix-engine-tests/assets/blueprints/wasm_limits/Cargo.toml new file mode 100644 index 00000000000..adb2d8c5da3 --- /dev/null +++ b/radix-engine-tests/assets/blueprints/wasm_limits/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "wasm_limits" +version = "1.0.0" +edition = "2021" + +[dependencies] +scrypto = { path = "../../../../scrypto" } + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/radix-engine-tests/assets/blueprints/wasm_limits/src/lib.rs b/radix-engine-tests/assets/blueprints/wasm_limits/src/lib.rs new file mode 100644 index 00000000000..85565f7f1fc --- /dev/null +++ b/radix-engine-tests/assets/blueprints/wasm_limits/src/lib.rs @@ -0,0 +1,14 @@ +use scrypto::prelude::*; + +#[blueprint] +mod wasm_limits { + struct WasmLimits {} + + impl WasmLimits { + pub fn create_buffers(n: usize) { + for _ in 0..n { + let _ = unsafe { wasm_api::actor::actor_get_blueprint_name() }; + } + } + } +} diff --git a/radix-engine-tests/benches/costing.rs b/radix-engine-tests/benches/costing.rs index 203d02c58d4..f49b8cdc880 100644 --- a/radix-engine-tests/benches/costing.rs +++ b/radix-engine-tests/benches/costing.rs @@ -3,22 +3,15 @@ use paste::paste; use radix_common::crypto::{verify_and_recover_secp256k1, verify_secp256k1}; use radix_common::prelude::*; use radix_engine::{ - system::system_modules::costing::SystemLoanFeeReserve, - transaction::CostingParameters, utils::ExtractSchemaError, vm::{ - wasm::{ - DefaultWasmEngine, ScryptoV1WasmValidator, WasmEngine, WasmInstance, WasmModule, - WasmRuntime, - }, - wasm_runtime::NoOpWasmRuntime, + wasm::{DefaultWasmEngine, ScryptoV1WasmValidator, WasmEngine, WasmModule}, ScryptoVmVersion, }, }; use radix_engine_interface::prelude::*; use radix_engine_tests::common::*; use radix_substate_store_queries::typed_substate_layout::{CodeHash, PackageDefinition}; -use radix_transactions::prelude::TransactionCostingParameters; use sbor::rust::iter; use scrypto_test::prelude::*; use wabt::wat2wasm; @@ -178,52 +171,44 @@ fn bench_validate_secp256k1(c: &mut Criterion) { }); } -fn bench_spin_loop(c: &mut Criterion) { +// Usage: cargo bench --bench costing -- spin_loop_v1 +// Note that this benchmark replaces the `spin_loop` before this commit, which uses NoOpRuntime +fn bench_spin_loop_v1(c: &mut Criterion) { // Prepare code - let code = wat2wasm(&include_local_wasm_str!("loop.wat").replace("${n}", "100000")).unwrap(); - - // Instrument - let validator = ScryptoV1WasmValidator::new(ScryptoVmVersion::latest()); - let instrumented_code = validator - .validate(&code, iter::empty()) - .map_err(|e| ExtractSchemaError::InvalidWasm(e)) - .unwrap() - .0; - - // Note that wasm engine maintains an internal cache, which means costing - // isn't taking WASM parsing into consideration. - let wasm_engine = DefaultWasmEngine::default(); - let mut wasm_execution_units_consumed = 0; - c.bench_function("costing::spin_loop", |b| { - b.iter(|| { - let fee_reserve = SystemLoanFeeReserve::new( - CostingParameters::babylon_genesis(), - TransactionCostingParameters { - free_credit_in_xrd: Decimal::try_from(PREVIEW_CREDIT_IN_XRD).unwrap(), - tip: Default::default(), - }, - false, - ); - wasm_execution_units_consumed = 0; - let mut runtime: Box = Box::new(NoOpWasmRuntime::new( - fee_reserve, - &mut wasm_execution_units_consumed, - )); - let mut instance = - wasm_engine.instantiate(CodeHash(Hash([0u8; 32])), &instrumented_code); - instance - .invoke_export("Test_f", vec![Buffer(0)], &mut runtime) - .unwrap(); - }) - }); + let code = + wat2wasm(&include_local_wasm_str!("loop.wat").replace("${n}", &i32::MAX.to_string())) + .unwrap(); + let mut ledger = LedgerSimulatorBuilder::new().build(); + let package_address = ledger.publish_package_simple(PackagePublishingSource::PublishExisting( + code, + single_function_package_definition("Test", "f"), + )); - println!( - "WASM execution units consumed: {}", - wasm_execution_units_consumed + let manifest = ManifestBuilder::new() + // First, lock the fee so that the loan will be repaid + .lock_fee_from_faucet() + // Now spin-loop to wait for the fee loan to burn through + .call_function(package_address, "Test", "f", manifest_args!()) + .build(); + + // The transaction failed, consuming almost all execution cost units. + assert!( + ledger + .execute_manifest(manifest.clone(), []) + .fee_summary + .total_execution_cost_units_consumed + >= 99_000_000 ); + + c.bench_function("costing::spin_loop_v1", |b| { + b.iter(|| ledger.execute_manifest(manifest.clone(), [])) + }); } // Usage: cargo bench --bench costing -- spin_loop_v2 +// Different from spin_loop_v1, this is the smallest possible loop. +// There is only one instruction `br` per iteration. +// It's extremely helpful for stress testing the `consume_wasm_execution_units` host function. fn bench_spin_loop_v2(c: &mut Criterion) { let code = wat2wasm(&include_local_wasm_str!("loop_v2.wat")).unwrap(); let mut ledger = LedgerSimulatorBuilder::new().build(); @@ -239,47 +224,42 @@ fn bench_spin_loop_v2(c: &mut Criterion) { .call_function(package_address, "Test", "f", manifest_args!()) .build(); + // The transaction failed, consuming almost all execution cost units. + assert!( + ledger + .execute_manifest(manifest.clone(), []) + .fee_summary + .total_execution_cost_units_consumed + >= 99_000_000 + ); + c.bench_function("costing::spin_loop_v2", |b| { b.iter(|| ledger.execute_manifest(manifest.clone(), [])) }); } -// Usage: cargo bench --bench costing -- spin_loop_v3 -// This is an basically the same as 'spin_loop_should_end_in_reasonable_amount_of_time' test, -// but it is benchmarked for more precise results. -fn bench_spin_loop_v3(c: &mut Criterion) { +// Usage: cargo bench --bench costing -- sha256 +fn bench_sha256(c: &mut Criterion) { let mut ledger = LedgerSimulatorBuilder::new().build(); - let (code, definition) = PackageLoader::get("fee"); - let package_address = - ledger.publish_package((code, definition), BTreeMap::new(), OwnerRole::None); - - let manifest = ManifestBuilder::new() - .lock_fee_from_faucet() - .get_free_xrd_from_faucet() - .take_all_from_worktop(XRD, "bucket") - .with_name_lookup(|builder, lookup| { - builder.call_function( - package_address, - "Fee", - "new", - manifest_args!(lookup.bucket("bucket")), - ) - }) - .build(); - - let component_address = ledger - .execute_manifest(manifest.clone(), []) - .expect_commit_success() - .new_component_addresses()[0]; + let package_address = ledger.publish_package_simple(PackageLoader::get("sha256_in_scrypto")); let manifest = ManifestBuilder::new() // First, lock the fee so that the loan will be repaid .lock_fee_from_faucet() // Now spin-loop to wait for the fee loan to burn through - .call_method(component_address, "spin_loop", manifest_args!()) + .call_function(package_address, "Test", "f", manifest_args!()) .build(); - c.bench_function("costing::spin_loop_v3", |b| { + // The transaction failed, consuming almost all execution cost units. + assert!( + ledger + .execute_manifest(manifest.clone(), []) + .fee_summary + .total_execution_cost_units_consumed + >= 99_000_000 + ); + + c.bench_function("costing::sha256", |b| { b.iter(|| ledger.execute_manifest(manifest.clone(), [])) }); } @@ -453,7 +433,6 @@ criterion_group!( bench_validate_sbor_payload, bench_validate_sbor_payload_bytes, bench_validate_secp256k1, - bench_spin_loop, bench_instantiate_radiswap, bench_instantiate_flash_loan, bench_deserialize_wasm, @@ -470,7 +449,6 @@ criterion_group!( .sample_size(20) .measurement_time(core::time::Duration::from_secs(20)) .warm_up_time(core::time::Duration::from_millis(3000)); - targets = bench_spin_loop_v2, - bench_spin_loop_v3, + targets = bench_spin_loop_v1,bench_spin_loop_v2,bench_sha256 ); criterion_main!(costing, costing_long); diff --git a/radix-engine-tests/benches/metadata_validation.rs b/radix-engine-tests/benches/metadata_validation.rs new file mode 100644 index 00000000000..a7864f3c616 --- /dev/null +++ b/radix-engine-tests/benches/metadata_validation.rs @@ -0,0 +1,16 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use radix_common::prelude::*; +use scrypto::prelude::CheckedUrl; +use std::hint::black_box; + +#[allow(unused_must_use)] +fn bench_validate_urls(c: &mut Criterion) { + c.bench_function("metadata_validation::validate_urls", |b| { + b.iter(|| { + black_box(CheckedUrl::of("https://www.example.com/test?q=x").unwrap()); + }) + }); +} + +criterion_group!(metadata_validation, bench_validate_urls); +criterion_main!(metadata_validation); diff --git a/radix-engine-tests/tests/blueprints/account_deposit_modes.rs b/radix-engine-tests/tests/blueprints/account_deposit_modes.rs index f202f0fde13..f571413100f 100644 --- a/radix-engine-tests/tests/blueprints/account_deposit_modes.rs +++ b/radix-engine-tests/tests/blueprints/account_deposit_modes.rs @@ -5,14 +5,15 @@ use radix_engine::transaction::TransactionReceipt; use radix_engine_interface::blueprints::account::*; use radix_engine_interface::prelude::*; use radix_substate_store_queries::typed_substate_layout::AccountError; +use radix_transactions::manifest::BuildableManifest; use radix_transactions::prelude::*; use scrypto_test::prelude::{DefaultLedgerSimulator, LedgerSimulatorBuilder}; #[test] fn account_deposit_method_is_callable_with_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger.free_tokens_from_faucet_to_account(DepositMethod::Deposit, true); @@ -25,8 +26,8 @@ fn account_deposit_method_is_callable_with_owner_signature() { #[test] fn account_deposit_batch_method_is_callable_with_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger.free_tokens_from_faucet_to_account(DepositMethod::DepositBatch, true); @@ -39,8 +40,8 @@ fn account_deposit_batch_method_is_callable_with_owner_signature() { #[test] fn account_deposit_method_is_not_callable_with_out_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger.free_tokens_from_faucet_to_account(DepositMethod::Deposit, false); @@ -53,8 +54,8 @@ fn account_deposit_method_is_not_callable_with_out_owner_signature() { #[test] fn account_deposit_batch_method_is_not_callable_with_out_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger.free_tokens_from_faucet_to_account(DepositMethod::DepositBatch, false); @@ -67,8 +68,8 @@ fn account_deposit_batch_method_is_not_callable_with_out_owner_signature() { #[test] fn account_try_deposit_method_is_callable_with_out_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger.free_tokens_from_faucet_to_account(DepositMethod::TryDeposit, false); @@ -81,8 +82,8 @@ fn account_try_deposit_method_is_callable_with_out_owner_signature() { #[test] fn account_try_deposit_batch_or_refund_method_is_callable_without_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = ledger @@ -121,8 +122,8 @@ fn account_try_deposit_batch_or_refund_method_is_callable_with_array_of_resource #[test] fn account_try_deposit_or_abort_method_is_callable_without_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = @@ -136,8 +137,8 @@ fn account_try_deposit_or_abort_method_is_callable_without_owner_signature() { #[test] fn account_try_deposit_batch_or_abort_method_is_callable_without_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = @@ -151,8 +152,8 @@ fn account_try_deposit_batch_or_abort_method_is_callable_without_owner_signature #[test] fn changing_default_deposit_rule_is_callable_with_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = @@ -166,8 +167,8 @@ fn changing_default_deposit_rule_is_callable_with_owner_signature() { #[test] fn changing_default_deposit_rule_is_not_callable_with_out_owner_signature() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); // Act let receipt = @@ -181,8 +182,8 @@ fn changing_default_deposit_rule_is_not_callable_with_out_owner_signature() { #[test] fn allow_all_allows_for_all_resource_deposits() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); // Act @@ -197,8 +198,8 @@ fn allow_all_allows_for_all_resource_deposits() { #[test] fn allow_all_disallows_deposit_of_resource_in_deny_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .add_to_deny_list(resource_address, true) @@ -216,8 +217,8 @@ fn allow_all_disallows_deposit_of_resource_in_deny_list() { #[test] fn resource_in_deny_list_could_be_converted_to_resource_in_allow_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .add_to_deny_list(resource_address, true) @@ -238,8 +239,8 @@ fn resource_in_deny_list_could_be_converted_to_resource_in_allow_list() { #[test] fn resource_in_deny_list_could_be_removed_from_there() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .add_to_deny_list(resource_address, true) @@ -260,8 +261,8 @@ fn resource_in_deny_list_could_be_removed_from_there() { #[test] fn allow_existing_disallows_deposit_of_resources_on_deny_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); ledger .transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true) .expect_commit_success(); @@ -279,8 +280,8 @@ fn allow_existing_disallows_deposit_of_resources_on_deny_list() { #[test] fn allow_existing_allows_deposit_of_xrd_if_not_on_deny_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); ledger .transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true) .expect_commit_success(); @@ -297,8 +298,8 @@ fn allow_existing_allows_deposit_of_xrd_if_not_on_deny_list() { #[test] fn allow_existing_allows_deposit_of_an_existing_resource() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger @@ -321,8 +322,8 @@ fn allow_existing_allows_deposit_of_an_existing_resource() { #[test] fn allow_existing_allows_deposit_of_an_existing_resource_even_if_account_has_none_of_it() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger @@ -346,8 +347,8 @@ fn allow_existing_allows_deposit_of_an_existing_resource_even_if_account_has_non #[test] fn allow_existing_allows_deposit_of_a_resource_account_does_not_have_if_it_is_on_the_allow_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true) @@ -368,8 +369,8 @@ fn allow_existing_allows_deposit_of_a_resource_account_does_not_have_if_it_is_on #[test] fn removing_an_address_from_the_allow_list_removes_it() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true) @@ -393,8 +394,8 @@ fn removing_an_address_from_the_allow_list_removes_it() { #[test] fn transitioning_an_address_to_deny_list_works_as_expected() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true) @@ -418,8 +419,8 @@ fn transitioning_an_address_to_deny_list_works_as_expected() { #[test] fn disallow_all_does_not_permit_deposit_of_any_resource() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); ledger .transition_default_deposit_rule(DefaultDepositRule::Reject, true) .expect_commit_success(); @@ -436,8 +437,8 @@ fn disallow_all_does_not_permit_deposit_of_any_resource() { #[test] fn disallow_all_permits_deposit_of_resource_in_allow_list() { // Arrange - for is_virtual in [true, false] { - let mut ledger = AccountDepositModesLedgerSimulator::new(is_virtual); + for is_preallocated in [true, false] { + let mut ledger = AccountDepositModesLedgerSimulator::new(is_preallocated); let resource_address = ledger.freely_mintable_resource(); ledger .transition_default_deposit_rule(DefaultDepositRule::Reject, true) @@ -455,10 +456,112 @@ fn disallow_all_permits_deposit_of_resource_in_allow_list() { } } +#[test] +fn cannot_trick_account_allow_existing_by_adding_empty_bucket_to_account() { + // Arrange + let mut ledger = AccountDepositModesLedgerSimulator::new(false); + ledger.transition_default_deposit_rule(DefaultDepositRule::AllowExisting, true); + let account_address = ledger.account_address; + + let bad_resource_address = ledger.freely_mintable_resource(); + + // The potential attack is against the `AllowExisting` deposit filter of the account, + // and happens in two parts: + // + // - Part 1: + // - The user interacts with a dApp, and the dApp sneaks in an *empty* bucket of some + // "bad resource" they want to eventually secretly deposit to the user. + // - The manifest includes a worktop assertion that it only includes other resources, + // so the user in their wallet doesn't ever see that this bad resource is present + // in any of their reviews. + // - The manifest then does a deposit batch to also deposit the empty bucket of the + // bad resource. Because it is an owner-approved deposit, this is allowed despite + // the fact that the resource is not existing in the account. + // - The user, not knowing about this bad resource, signs the transaction and submits + // it, and the transaction commits. + // + // - Part 2: + // - After Part 1, the account now has a vault for this bad resource, so the bad + // resource is allowed to be deposited under the `AllowExisting` configuration. + // - The dApp uses `try_deposit_or_abort` to deposit a non-zero amount of the bad + // resource into the account, without the user being present. + // + // In my (David's) mind, the correct fix for this potential attack would be for the account + // not to actually do a deposit of an empty bucket. Maybe it emits an event, but doesn't + // actually create a vault, and instead drops the bucket? + // + // However, the account doesn't yet do this BUT we are saved from this happening at the + // manifest layer because the worktop has an invariant that it never stores empty + // buckets. This is a little indirect for my liking... But it does solve the main issue. + // + // So this test serves to act as some regression tests to ensure this attack isn't + // accidentally enabled due to changes in the worktop behaviour. And in particular, + // this test tries to create empty worktop buckets via a couple of different means, + // to serve as a better regression test. + // + // SIDE NOTE: + // Whilst this could be attacked at the scrypto layer, this attack isn't really an issue: + // - It’s not common for scrypto to have an owned account, and it's even less common for that + // owned account to use an AllowExisting deposit configuration + // - It’s not common for scrypto to encounter batches of unknown buckets + // - It's not common for scrypto code to use deposit or deposit_batch methods on an owned account + // - We don’t really expose any of this assertion logic at the scrypto layer, so the user would + // be validating these buckets manually and likely discard any bucket resource addresses + // not matching their filter. + + let user_manifests = [ + // Attempt 1 - just mint an empty bucket + ManifestBuilder::new_v2() + .lock_fee_from_faucet() + .get_free_xrd_from_faucet() + .mint_fungible(bad_resource_address, 0) + .assert_worktop_resources_only( + ManifestResourceConstraints::new().with_at_least_amount(XRD, Decimal::ONE_ATTO), + ) + .deposit_entire_worktop(account_address) + .build(), + // Attempt 2 - create and drop the bucket + ManifestBuilder::new_v2() + .lock_fee_from_faucet() + .get_free_xrd_from_faucet() + .mint_fungible(bad_resource_address, 10) + .take_from_worktop(bad_resource_address, 10, "bucket_to_burn") + .burn_resource("bucket_to_burn") + .assert_worktop_resources_only( + ManifestResourceConstraints::new().with_at_least_amount(XRD, Decimal::ONE_ATTO), + ) + .deposit_entire_worktop(account_address) + .build(), + ]; + for user_manifest in user_manifests { + let receipt = ledger.execute_manifest(user_manifest, true); + receipt.expect_commit_success(); + + let malicious_manifest = ManifestBuilder::new_v2() + .lock_fee_from_faucet() + .mint_fungible(bad_resource_address, dec!(666)) + .try_deposit_entire_worktop_or_abort(account_address, None) + .build(); + let receipt = ledger.execute_manifest(malicious_manifest, false); + + // Assert + let error = receipt.expect_commit_failure().outcome.expect_failure(); + let matches_result = matches!( + error, + RuntimeError::ApplicationError(ApplicationError::AccountError( + AccountError::NotAllBucketsCouldBeDeposited + )), + ); + if !matches_result { + panic!("Error is not DepositIsDisallowed {error:?}"); + } + } +} + struct AccountDepositModesLedgerSimulator { ledger: DefaultLedgerSimulator, public_key: PublicKey, - component_address: ComponentAddress, + account_address: ComponentAddress, } impl AccountDepositModesLedgerSimulator { @@ -467,7 +570,7 @@ impl AccountDepositModesLedgerSimulator { let (public_key, _, component_address) = ledger.new_account(preallocated_account); Self { - component_address, + account_address: component_address, public_key: public_key.into(), ledger, } @@ -484,7 +587,7 @@ impl AccountDepositModesLedgerSimulator { .mint_fungible(resource_address, 1) .take_all_from_worktop(resource_address, "bucket") .with_bucket("bucket", |builder, bucket| { - deposit_method.call(builder, self.component_address, bucket) + deposit_method.call(builder, self.account_address, bucket) }) .build(); self.execute_manifest(manifest, sign) @@ -500,7 +603,7 @@ impl AccountDepositModesLedgerSimulator { .get_free_xrd_from_faucet() .take_all_from_worktop(XRD, "free_tokens") .with_bucket("free_tokens", |builder, bucket| { - deposit_method.call(builder, self.component_address, bucket) + deposit_method.call(builder, self.account_address, bucket) }) .build(); self.execute_manifest(manifest, sign) @@ -514,7 +617,7 @@ impl AccountDepositModesLedgerSimulator { let manifest = ManifestBuilder::new() .lock_fee_from_faucet() .call_method( - self.component_address, + self.account_address, ACCOUNT_SET_DEFAULT_DEPOSIT_RULE_IDENT, AccountSetDefaultDepositRuleInput { default }, ) @@ -531,7 +634,7 @@ impl AccountDepositModesLedgerSimulator { let manifest = ManifestBuilder::new() .lock_fee_from_faucet() .call_method( - self.component_address, + self.account_address, ACCOUNT_SET_RESOURCE_PREFERENCE_IDENT, AccountSetResourcePreferenceInput { resource_address, @@ -550,7 +653,7 @@ impl AccountDepositModesLedgerSimulator { let manifest = ManifestBuilder::new() .lock_fee_from_faucet() .call_method( - self.component_address, + self.account_address, ACCOUNT_REMOVE_RESOURCE_PREFERENCE_IDENT, AccountRemoveResourcePreferenceInput { resource_address }, ) @@ -584,7 +687,7 @@ impl AccountDepositModesLedgerSimulator { OwnerRole::None, None, 18, - self.component_address, + self.account_address, ) } @@ -595,10 +698,10 @@ impl AccountDepositModesLedgerSimulator { let balance = self .ledger - .get_component_balance(self.component_address, resource_address); + .get_component_balance(self.account_address, resource_address); let manifest = ManifestBuilder::new() .lock_fee_from_faucet() - .withdraw_from_account(self.component_address, resource_address, balance) + .withdraw_from_account(self.account_address, resource_address, balance) .try_deposit_entire_worktop_or_refund(uninstantiated_account, None) .build(); @@ -608,7 +711,7 @@ impl AccountDepositModesLedgerSimulator { pub fn execute_manifest( &mut self, - manifest: TransactionManifestV1, + manifest: impl BuildableManifest, sign: bool, ) -> TransactionReceipt { self.ledger.execute_manifest( diff --git a/radix-engine-tests/tests/system/assert_bucket_contents.rs b/radix-engine-tests/tests/system/assert_bucket_contents.rs index babdc85f0ca..e23f6a10563 100644 --- a/radix-engine-tests/tests/system/assert_bucket_contents.rs +++ b/radix-engine-tests/tests/system/assert_bucket_contents.rs @@ -33,7 +33,7 @@ fn asserting_incorrect_exact_amount_should_fail() { ManifestResourceConstraint::ExactAmount(expected_exact_amount), Some(ResourceConstraintError::ExpectedExactAmount { actual_amount, - expected_exact_amount, + expected_amount: expected_exact_amount, }), ) } @@ -79,14 +79,8 @@ fn asserting_incorrect_at_least_non_fungibles_should_fail() { test_non_fungible_constraint( actual_ids.clone(), ManifestResourceConstraint::AtLeastNonFungibles(expected_at_least_ids.clone()), - Some(ResourceConstraintError::ExpectedAtLeastNonFungibles { - actual_ids: Box::new( - actual_ids - .into_iter() - .map(NonFungibleLocalId::from) - .collect(), - ), - expected_at_least_ids: Box::new(expected_at_least_ids), + Some(ResourceConstraintError::NonFungibleMissing { + missing_id: NonFungibleLocalId::from(2), }), ) } @@ -115,14 +109,8 @@ fn asserting_incorrect_exact_non_fungibles_should_fail() { test_non_fungible_constraint( actual_ids.clone(), ManifestResourceConstraint::ExactNonFungibles(expected_exact_ids.clone()), - Some(ResourceConstraintError::ExpectedExactNonFungibles { - actual_ids: Box::new( - actual_ids - .into_iter() - .map(NonFungibleLocalId::from) - .collect(), - ), - expected_exact_ids: Box::new(expected_exact_ids), + Some(ResourceConstraintError::NonFungibleMissing { + missing_id: NonFungibleLocalId::from(3), }), ) } @@ -203,19 +191,17 @@ fn asserting_incorrect_fungible_lower_bound_general_constraint_should_fail() { let lower_bound = dec!(10) + Decimal::from_attos(I192::ONE); let constraint = GeneralResourceConstraint { required_ids: Default::default(), - allowed_ids: AllowedIds::Any, lower_bound: LowerBound::Inclusive(lower_bound), upper_bound: UpperBound::Unbounded, + allowed_ids: AllowedIds::Any, }; test_fungible_constraint( amount, ManifestResourceConstraint::General(constraint), - Some(ResourceConstraintError::GeneralResourceConstraintError( - GeneralResourceConstraintError::LowerBoundAmountNotSatisfied { - actual: amount, - lower_bound_inclusive: lower_bound, - }, - )), + Some(ResourceConstraintError::ExpectedAtLeastAmount { + expected_at_least_amount: lower_bound, + actual_amount: amount, + }), ); } @@ -225,19 +211,17 @@ fn asserting_incorrect_fungible_upper_bound_general_constraint_should_fail() { let upper_bound = dec!(10) - Decimal::from_attos(I192::ONE); let constraint = GeneralResourceConstraint { required_ids: Default::default(), - allowed_ids: AllowedIds::Any, lower_bound: LowerBound::Inclusive(Decimal::zero()), upper_bound: UpperBound::Inclusive(upper_bound), + allowed_ids: AllowedIds::Any, }; test_fungible_constraint( amount, ManifestResourceConstraint::General(constraint), - Some(ResourceConstraintError::GeneralResourceConstraintError( - GeneralResourceConstraintError::UpperBoundAmountNotSatisfied { - actual: amount, - upper_bound_inclusive: upper_bound, - }, - )), + Some(ResourceConstraintError::ExpectedAtMostAmount { + expected_at_most_amount: upper_bound, + actual_amount: amount, + }), ); } @@ -246,18 +230,16 @@ fn asserting_incorrect_non_fungible_required_ids_general_constraint_should_fail( let actual_ids = vec![1, 2]; let constraint = GeneralResourceConstraint { required_ids: indexset!(NonFungibleLocalId::from(3)), - allowed_ids: AllowedIds::Any, lower_bound: LowerBound::Inclusive(Decimal::from(1)), upper_bound: UpperBound::Unbounded, + allowed_ids: AllowedIds::Any, }; test_non_fungible_constraint( actual_ids.clone(), ManifestResourceConstraint::General(constraint), - Some(ResourceConstraintError::GeneralResourceConstraintError( - GeneralResourceConstraintError::MissingRequiredNonFungible { - missing_id: NonFungibleLocalId::from(3), - }, - )), + Some(ResourceConstraintError::NonFungibleMissing { + missing_id: NonFungibleLocalId::from(3), + }), ); } @@ -266,21 +248,19 @@ fn asserting_incorrect_non_fungible_allowed_ids_general_constraint_should_fail() let actual_ids = vec![1, 3]; let constraint = GeneralResourceConstraint { required_ids: indexset!(), + lower_bound: LowerBound::NonZero, + upper_bound: UpperBound::Inclusive(Decimal::from(2)), allowed_ids: AllowedIds::Allowlist(indexset!( NonFungibleLocalId::from(3), NonFungibleLocalId::from(4) )), - lower_bound: LowerBound::NonZero, - upper_bound: UpperBound::Inclusive(Decimal::from(2)), }; test_non_fungible_constraint( actual_ids.clone(), ManifestResourceConstraint::General(constraint), - Some(ResourceConstraintError::GeneralResourceConstraintError( - GeneralResourceConstraintError::InvalidNonFungible { - invalid_id: NonFungibleLocalId::from(1), - }, - )), + Some(ResourceConstraintError::NonFungibleNotAllowed { + disallowed_id: NonFungibleLocalId::from(1), + }), ); } @@ -295,9 +275,9 @@ fn asserting_correct_empty_bucket_general_constraints_should_succeed() { for allowed_ids in &allowed_ids_list { let constraint = GeneralResourceConstraint { required_ids: Default::default(), - allowed_ids: allowed_ids.clone(), lower_bound: LowerBound::Inclusive(amount), upper_bound, + allowed_ids: allowed_ids.clone(), }; test_fungible_constraint( amount, @@ -313,16 +293,14 @@ fn asserting_incorrect_empty_bucket_lower_bound_general_constraint_should_fail() let amount = dec!(0); let constraint = GeneralResourceConstraint { required_ids: Default::default(), - allowed_ids: AllowedIds::Any, lower_bound: LowerBound::NonZero, upper_bound: UpperBound::Unbounded, + allowed_ids: AllowedIds::Any, }; test_fungible_constraint( amount, ManifestResourceConstraint::General(constraint), - Some(ResourceConstraintError::GeneralResourceConstraintError( - GeneralResourceConstraintError::ExpectedNonZeroAmount, - )), + Some(ResourceConstraintError::ExpectedNonZeroAmount), ); } diff --git a/radix-engine-tests/tests/system/assert_next_call_returns.rs b/radix-engine-tests/tests/system/assert_next_call_returns.rs index c5e0193ab28..2b2160e7911 100644 --- a/radix-engine-tests/tests/system/assert_next_call_returns.rs +++ b/radix-engine-tests/tests/system/assert_next_call_returns.rs @@ -37,9 +37,11 @@ fn when_more_is_returned_assert_next_call_returns_only_should_fail() { true, next_call_type, |_, resource2| { - Some(ManifestResourceConstraintsError::UnwantedResourcesExist( - resource2, - )) + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource2, + }, + ) }, ); } @@ -104,12 +106,13 @@ fn when_less_is_returned_assert_next_call_returns_include_should_fail() { false, next_call_type, |_, _| { - Some(ManifestResourceConstraintsError::ResourceConstraint( - ResourceConstraintError::ExpectedAtLeastAmount { + Some(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: XRD, + error: ResourceConstraintError::ExpectedAtLeastAmount { expected_at_least_amount: dec!(1), actual_amount: dec!(0), }, - )) + }) }, ); } @@ -129,13 +132,12 @@ fn when_less_is_returned_assert_next_call_returns_only_should_fail() { }, true, next_call_type, - |_, _| { - Some(ManifestResourceConstraintsError::ResourceConstraint( - ResourceConstraintError::ExpectedAtLeastAmount { - expected_at_least_amount: dec!(1), - actual_amount: dec!(0), + |_, resource2| { + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource2, }, - )) + ) }, ); } @@ -161,9 +163,11 @@ fn when_empty_constraints_on_assert_next_call_returns_only_should_fail() { true, next_call_type, |resource1, _resource2| { - Some(ManifestResourceConstraintsError::UnwantedResourcesExist( - resource1, - )) + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource1, + }, + ) }, ); } @@ -275,10 +279,7 @@ fn run_return_two_resources_test( constraints: fn(ResourceAddress, ResourceAddress) -> ManifestResourceConstraints, exact: bool, next_call_type: NextCallType, - expected_result: fn( - ResourceAddress, - ResourceAddress, - ) -> Option, + expected_result: fn(ResourceAddress, ResourceAddress) -> Option, ) { // Arrange let mut ledger = LedgerSimulatorBuilder::new().build(); diff --git a/radix-engine-tests/tests/system/assert_worktop_resources.rs b/radix-engine-tests/tests/system/assert_worktop_resources.rs index e408ef12c60..f32f1380ce4 100644 --- a/radix-engine-tests/tests/system/assert_worktop_resources.rs +++ b/radix-engine-tests/tests/system/assert_worktop_resources.rs @@ -25,9 +25,11 @@ fn when_more_is_returned_assert_worktop_resources_only_should_fail() { }, true, |_, resource2| { - Some(ManifestResourceConstraintsError::UnwantedResourcesExist( - resource2, - )) + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource2, + }, + ) }, ); } @@ -83,12 +85,13 @@ fn when_less_is_returned_assert_next_call_returns_include_should_fail() { }, false, |_, _| { - Some(ManifestResourceConstraintsError::ResourceConstraint( - ResourceConstraintError::ExpectedAtLeastAmount { + Some(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: XRD, + error: ResourceConstraintError::ExpectedAtLeastAmount { expected_at_least_amount: dec!(1), actual_amount: dec!(0), }, - )) + }) }, ); } @@ -105,13 +108,12 @@ fn when_less_is_returned_assert_next_call_returns_only_should_fail() { .with(XRD, ManifestResourceConstraint::AtLeastAmount(dec!(1))) }, true, - |_, _| { - Some(ManifestResourceConstraintsError::ResourceConstraint( - ResourceConstraintError::ExpectedAtLeastAmount { - expected_at_least_amount: dec!(1), - actual_amount: dec!(0), + |_, resource2| { + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource2, }, - )) + ) }, ); } @@ -131,9 +133,11 @@ fn when_empty_constraints_on_assert_next_call_returns_only_should_fail() { |_resource1, _resource2| ManifestResourceConstraints::new(), true, |resource1, _resource2| { - Some(ManifestResourceConstraintsError::UnwantedResourcesExist( - resource1, - )) + Some( + ResourceConstraintsError::UnexpectedNonZeroBalanceOfUnspecifiedResource { + resource_address: resource1, + }, + ) }, ); } @@ -230,10 +234,7 @@ fn when_withdrawing_zero_non_fungibles_with_zero_constraints_on_assert_worktop_r fn run_worktop_two_resources_test( constraints: fn(ResourceAddress, ResourceAddress) -> ManifestResourceConstraints, exact: bool, - expected_result: fn( - ResourceAddress, - ResourceAddress, - ) -> Option, + expected_result: fn(ResourceAddress, ResourceAddress) -> Option, ) { // Arrange let mut ledger = LedgerSimulatorBuilder::new().build(); @@ -268,9 +269,7 @@ fn run_worktop_two_resources_test( if let Some(error) = expected_result(resource1, resource2) { receipt.expect_specific_failure(|e| { e.eq(&RuntimeError::ApplicationError( - ApplicationError::WorktopError(WorktopError::ResourceConstraintsError( - error.clone(), - )), + ApplicationError::WorktopError(WorktopError::AssertionFailed(error.clone())), )) }); } else { diff --git a/radix-engine-tests/tests/system/fee.rs b/radix-engine-tests/tests/system/fee.rs index 2b26aac9811..389ee1f3b5f 100644 --- a/radix-engine-tests/tests/system/fee.rs +++ b/radix-engine-tests/tests/system/fee.rs @@ -295,7 +295,13 @@ fn test_fee_accounting_failure() { matches!( e, RuntimeError::ApplicationError(ApplicationError::WorktopError( - WorktopError::AssertionFailed + WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: XRD, + error: ResourceConstraintError::ExpectedAtLeastAmount { + expected_at_least_amount: Decimal::ONE, + actual_amount: Decimal::ZERO, + }, + }) )) ) }); @@ -456,7 +462,13 @@ fn test_contingent_fee_accounting_failure() { matches!( e, RuntimeError::ApplicationError(ApplicationError::WorktopError( - WorktopError::AssertionFailed + WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: XRD, + error: ResourceConstraintError::ExpectedAtLeastAmount { + expected_at_least_amount: Decimal::ONE, + actual_amount: Decimal::ZERO, + }, + }) )) ) }); diff --git a/radix-engine-tests/tests/system/metadata.rs b/radix-engine-tests/tests/system/metadata.rs index 7da57ae32fc..1665f5ef08f 100644 --- a/radix-engine-tests/tests/system/metadata.rs +++ b/radix-engine-tests/tests/system/metadata.rs @@ -1,6 +1,8 @@ use radix_common::prelude::*; use radix_engine::errors::{ApplicationError, RuntimeError, SystemError}; -use radix_engine::object_modules::metadata::MetadataError; +use radix_engine::object_modules::metadata::{ + MetadataError, MetadataKeyValidationError, MetadataValueValidationError, +}; use radix_engine_interface::object_modules::metadata::{ MetadataConversionError::UnexpectedType, MetadataValue, }; @@ -105,7 +107,9 @@ fn cannot_initialize_metadata_if_key_too_long() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::KeyStringExceedsMaxLength { .. } + MetadataError::MetadataKeyValidationError( + MetadataKeyValidationError::InvalidLength { .. } + ) )) ) }); @@ -139,7 +143,9 @@ fn cannot_set_metadata_if_key_too_long() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::KeyStringExceedsMaxLength { .. } + MetadataError::MetadataKeyValidationError( + MetadataKeyValidationError::InvalidLength { .. } + ) )) ) }); @@ -169,7 +175,9 @@ fn cannot_initialize_metadata_if_value_too_long() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::ValueSborExceedsMaxLength { .. } + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidLength { .. } + ) )) ) }); @@ -203,7 +211,9 @@ fn cannot_set_metadata_if_value_too_long() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::ValueSborExceedsMaxLength { .. } + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidLength { .. } + ) )) ) }); diff --git a/radix-engine-tests/tests/system/metadata2.rs b/radix-engine-tests/tests/system/metadata2.rs new file mode 100644 index 00000000000..5a485b18058 --- /dev/null +++ b/radix-engine-tests/tests/system/metadata2.rs @@ -0,0 +1,37 @@ +use radix_common::prelude::*; +use radix_engine::object_modules::metadata::{MetadataError, MetadataValueValidationError}; +use radix_engine_tests::common::*; +use scrypto_test::prelude::*; + +#[test] +fn test_large_vector_of_urls_metadata() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let package_address = ledger.publish_package_simple(PackageLoader::get("metadata2")); + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "M", "f", manifest_args!()) + .build(); + let start = std::time::Instant::now(); + let receipt = ledger.execute_manifest(manifest, vec![]); + let end = std::time::Instant::now(); + + // Usage: + // ``` + // cargo test --release --package radix-engine-tests --test system_folder -- system::metadata2::test_large_vector_of_urls_metadata --exact --show-output + // ``` + println!("{:?}", receipt); + println!("{} ms", end.duration_since(start).as_millis()); + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::ApplicationError(ApplicationError::MetadataError( + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidLength { + actual: 330019, + max: 4096 + } + ), + )) + ) + }); +} diff --git a/radix-engine-tests/tests/system/metadata3.rs b/radix-engine-tests/tests/system/metadata3.rs new file mode 100644 index 00000000000..816d09c0903 --- /dev/null +++ b/radix-engine-tests/tests/system/metadata3.rs @@ -0,0 +1,31 @@ +use radix_common::prelude::*; +use radix_engine_tests::common::*; +use scrypto_test::prelude::*; + +#[test] +fn test_many_vectors_of_urls_metadata() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let package_address = ledger.publish_package_simple(PackageLoader::get("metadata3")); + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "M", "f", manifest_args!()) + .build(); + let start = std::time::Instant::now(); + let receipt = ledger.execute_manifest(manifest, vec![]); + let end = std::time::Instant::now(); + + // Usage: + // ``` + // cargo test --release --package radix-engine-tests --test system_folder -- system::metadata3::test_many_vectors_of_urls_metadata --exact --show-output + // ``` + println!("{:?}", receipt); + println!("{} ms", end.duration_since(start).as_millis()); + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemModuleError(SystemModuleError::CostingError( + CostingError::FeeReserveError(FeeReserveError::LimitExceeded { .. }), + )) + ) + }); +} diff --git a/radix-engine-tests/tests/system/metadata_component.rs b/radix-engine-tests/tests/system/metadata_component.rs index 9c124571dc3..d3f69de7915 100644 --- a/radix-engine-tests/tests/system/metadata_component.rs +++ b/radix-engine-tests/tests/system/metadata_component.rs @@ -1,7 +1,7 @@ use radix_common::prelude::*; use radix_engine::{ errors::{ApplicationError, RuntimeError, SystemError}, - object_modules::metadata::{MetadataError, MetadataValidationError}, + object_modules::metadata::{MetadataError, MetadataValueValidationError}, }; use radix_engine_tests::common::*; use scrypto_test::prelude::*; @@ -29,7 +29,9 @@ fn cannot_create_metadata_with_invalid_value() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::MetadataValidationError(MetadataValidationError::InvalidURL(_)) + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidURL(_) + ) )) ) }); @@ -69,7 +71,9 @@ fn cannot_set_metadata_with_invalid_value() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::MetadataValidationError(MetadataValidationError::InvalidURL(_)) + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidURL(_) + ) )) ) }); @@ -91,7 +95,9 @@ fn cannot_set_metadata_with_invalid_value() { matches!( e, RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::MetadataValidationError(MetadataValidationError::InvalidOrigin(_)) + MetadataError::MetadataValueValidationError( + MetadataValueValidationError::InvalidOrigin(_) + ) )) ) }); diff --git a/radix-engine-tests/tests/system/mod.rs b/radix-engine-tests/tests/system/mod.rs index 4be6108b5b4..473cce19aae 100644 --- a/radix-engine-tests/tests/system/mod.rs +++ b/radix-engine-tests/tests/system/mod.rs @@ -37,6 +37,8 @@ mod invalid_stored_values; mod kv_store; mod leaks; mod metadata; +mod metadata2; +mod metadata3; mod metadata_component; mod metadata_identity; mod metadata_package; @@ -75,6 +77,7 @@ mod system_lock_fee; mod system_module_methods; mod system_reference; mod system_role_assignment; +mod threading; mod toolkit_receipt; mod track; mod transaction_limits; diff --git a/radix-engine-tests/tests/system/threading.rs b/radix-engine-tests/tests/system/threading.rs new file mode 100644 index 00000000000..c6771c59cfa --- /dev/null +++ b/radix-engine-tests/tests/system/threading.rs @@ -0,0 +1,324 @@ +use radix_engine_tests::common::PackageLoader; +use scrypto_test::prelude::*; + +// Some of the tests in this file are to demonstrate the current behavior. + +#[test] +fn can_transfer_locked_bucket_between_threads() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + + // Prepares a component that can return a locked bucket (and a proof). + // + // When a bucket is inserted into the worktop, it's added as-is if there is no corresponding bucket + // allocated for the resource address, otherwise it's "merged" into the existing bucket, which will check + // lock status. + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + let component_address = ledger + .execute_manifest( + ManifestBuilder::new() + .lock_fee_from_faucet() + .call_method(FAUCET, "free", ()) + .take_all_from_worktop(XRD, "bucket") + .call_function_with_name_lookup(package_address, "Threading", "new", |lookup| { + (lookup.bucket("bucket"),) + }) + .build(), + [], + ) + .expect_commit_success() + .new_component_addresses()[0]; + + // Flow: + // 1. root creates a locked bucket + // 2. root sends child the bucket + // 3. child returns the bucket + // 4. root frees the bucket + // 5. root deposit the bucket into an account + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| { + builder + // EntireWorktop will ensure the buckets are passed as-is. + .yield_to_parent((ManifestExpression::EntireWorktop,)) + }) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .call_method(component_address, "create_locked_bucket", (dec!(1),)) + // EntireWorktop will ensure the buckets are passed as-is. + .yield_to_child("child", (ManifestExpression::EntireWorktop,)) + // Free the bucket + .drop_all_proofs() + .try_deposit_entire_worktop_or_abort(account1, None) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} + +// Arguably, we may disallow transferring references +#[test] +fn can_pass_global_and_direct_access_references() { + let mut ledger = LedgerSimulatorBuilder::new().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let (_, _, account) = ledger.new_allocated_account(); + let vault = ledger.get_component_vaults(account, XRD).pop().unwrap(); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| { + builder + // Unfortunately, there is no way to grab the received references + .yield_to_parent(()) + }) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder.lock_fee(account1, 3).yield_to_child( + "child", + ( + ManifestAddress::Static(account.into_node_id()), + ManifestAddress::Static(vault), + ), + ) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} + +#[test] +fn can_not_pass_address_reservation() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .allocate_global_address( + package_address, + "Threading", + "address_reservation", + "address", + ) + .yield_to_child_with_name_lookup("child", |lookup| { + (lookup.address_reservation("address_reservation"),) + }) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_specific_failure(|e| { + matches!(e, RuntimeError::SystemError(SystemError::NotAnObject)) + }); +} + +#[test] +fn can_pass_named_address() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + let package_address = ledger.publish_package_simple(PackageLoader::get("threading")); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + builder + .lock_fee(account1, 3) + .allocate_global_address( + package_address, + "Threading", + "address_reservation", + "address", + ) + .yield_to_child_with_name_lookup("child", |lookup| { + (lookup.named_address("address"),) + }) + .call_function_with_name_lookup(package_address, "Threading", "new2", |lookup| { + (lookup.address_reservation("address_reservation"),) + }) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build(); + + let receipt = ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); + receipt.expect_commit_success(); +} + +#[should_panic( + expected = "Transaction should be convertible to executable: ManifestValidationError(ProofCannotBePassedToAnotherIntent)" +)] +#[test] +fn can_not_pass_proof_between_threads() { + let mut ledger = LedgerSimulatorBuilder::new().with_kernel_trace().build(); + let (pk1, sk1, account1) = ledger.new_allocated_account(); + + let start_epoch_inclusive = ledger.get_current_epoch(); + let end_epoch_exclusive = start_epoch_inclusive.after(1).unwrap(); + let transaction = TransactionV2Builder::new() + .add_signed_child( + "child", + PartialTransactionV2Builder::new() + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 1, + }) + .manifest_builder(|builder| builder.yield_to_parent(())) + .sign(&sk1) + .build(), + ) + .intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive, + end_epoch_exclusive, + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 2, + }) + .manifest_builder(|builder| { + // Note that we have to disable validation + builder + .lock_fee(account1, 3) + .create_proof_from_account_of_amount(account1, XRD, 10) + .create_proof_from_auth_zone_of_amount(XRD, 10, "proof") + .yield_to_child_with_name_lookup("child", |lookup| (lookup.proof("proof"),)) + }) + .transaction_header(TransactionHeaderV2 { + notary_public_key: pk1.into(), + notary_is_signatory: false, + tip_basis_points: 0, + }) + .sign(&sk1) + .notarize(&sk1) + .build_no_validate(); + + ledger.execute_transaction(&transaction, ExecutionConfig::for_test_transaction()); +} diff --git a/radix-engine-tests/tests/vm/mod.rs b/radix-engine-tests/tests/vm/mod.rs index 22489380944..307768bf581 100644 --- a/radix-engine-tests/tests/vm/mod.rs +++ b/radix-engine-tests/tests/vm/mod.rs @@ -14,6 +14,7 @@ mod scrypto_validation; mod scrypto_validator; mod stack_size; mod system_wasm_buffers; +mod wasm_limits; mod wasm_memory; mod wasm_metering; mod wasm_non_mvp; diff --git a/radix-engine-tests/tests/vm/wasm_limits.rs b/radix-engine-tests/tests/vm/wasm_limits.rs new file mode 100644 index 00000000000..86498076b22 --- /dev/null +++ b/radix-engine-tests/tests/vm/wasm_limits.rs @@ -0,0 +1,43 @@ +use radix_engine_tests::common::PackageLoader; +use scrypto_test::prelude::*; + +#[test] +fn test_create_buffers_within_limits() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let package = ledger.publish_package_simple(PackageLoader::get("wasm_limits")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package, "WasmLimits", "create_buffers", (4usize,)) + .build(); + + // Act + let receipt = ledger.execute_manifest(manifest, vec![]); + + // Assert + receipt.expect_commit_success(); +} + +#[test] +fn test_crate_buffers_beyond_limits() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + let package = ledger.publish_package_simple(PackageLoader::get("wasm_limits")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package, "WasmLimits", "create_buffers", (5usize,)) + .build(); + + // Act + let receipt = ledger.execute_manifest(manifest, vec![]); + + // Assert + receipt.expect_specific_failure(|runtime_error| { + matches!( + runtime_error, + RuntimeError::VmError(VmError::Wasm(WasmRuntimeError::TooManyBuffers)) + ) + }) +} diff --git a/radix-engine/src/blueprints/package/package.rs b/radix-engine/src/blueprints/package/package.rs index f1b77b931e8..08d40d123c9 100644 --- a/radix-engine/src/blueprints/package/package.rs +++ b/radix-engine/src/blueprints/package/package.rs @@ -1,7 +1,7 @@ use super::substates::*; use crate::blueprints::util::{check_name, InvalidNameError, SecurifiedRoleAssignment}; use crate::internal_prelude::*; -use crate::object_modules::metadata::MetadataNativePackage; +use crate::object_modules::metadata::{validate_metadata_init, MetadataNativePackage}; use crate::system::node_init::type_info_partition; use crate::system::system_modules::costing::{apply_royalty_cost, RoyaltyRecipient}; use crate::system::type_info::TypeInfoSubstate; @@ -734,7 +734,11 @@ pub fn create_package_partition_substates( METADATA_BLUEPRINT, indexmap!(), ); - let metadata_system_struct = MetadataNativePackage::init_system_struct(metadata).unwrap(); + // Additional validation has been added as part of this commit. + // The logic is backward compatible, as it's used by protocol updates only. + let metadata_system_struct = + MetadataNativePackage::init_system_struct(validate_metadata_init(metadata).unwrap()) + .unwrap(); let metadata_substates = SystemMapper::system_struct_to_node_substates( &metadata_schema, metadata_system_struct, diff --git a/radix-engine/src/blueprints/resource/worktop.rs b/radix-engine/src/blueprints/resource/worktop.rs index 3aa94c9c476..6147859c081 100644 --- a/radix-engine/src/blueprints/resource/worktop.rs +++ b/radix-engine/src/blueprints/resource/worktop.rs @@ -1,6 +1,3 @@ -use crate::blueprints::transaction_processor::ResourceConstraintChecker; -use crate::errors::ApplicationError; -use crate::errors::RuntimeError; use crate::internal_prelude::*; use crate::kernel::kernel_api::KernelSubstateApi; use crate::system::system_callback::SystemLockData; @@ -8,7 +5,7 @@ use crate::system::system_substates::FieldSubstate; use radix_engine_interface::api::field_api::LockFlags; use radix_engine_interface::api::{SystemApi, ACTOR_STATE_SELF}; use radix_engine_interface::blueprints::resource::*; -use radix_native_sdk::resource::{NativeBucket, NativeNonFungibleBucket, ResourceManager}; +use radix_native_sdk::resource::*; #[derive(Debug, ScryptoSbor)] pub struct WorktopSubstate { @@ -25,9 +22,12 @@ impl WorktopSubstate { #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] pub enum WorktopError { - AssertionFailed, + /// This is now unused, but kept in for backwards compatibility + /// of error models (at least whilst we need to do that for the node, + /// so that legacy errors can be serialized to string) + BasicAssertionFailed, InsufficientBalance, - ResourceConstraintsError(ManifestResourceConstraintsError), + AssertionFailed(ResourceConstraintsError), } pub struct WorktopBlueprint; @@ -409,8 +409,13 @@ impl WorktopBlueprint { Decimal::zero() }; if amount.is_zero() { + let worktop_error = + WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: input.resource_address, + error: ResourceConstraintError::ExpectedNonZeroAmount, + }); return Err(RuntimeError::ApplicationError( - ApplicationError::WorktopError(WorktopError::AssertionFailed), + ApplicationError::WorktopError(worktop_error), )); } api.field_close(worktop_handle)?; @@ -437,8 +442,16 @@ impl WorktopBlueprint { Decimal::zero() }; if amount < input.amount { + let worktop_error = + WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: input.resource_address, + error: ResourceConstraintError::ExpectedAtLeastAmount { + expected_at_least_amount: input.amount, + actual_amount: amount, + }, + }); return Err(RuntimeError::ApplicationError( - ApplicationError::WorktopError(WorktopError::AssertionFailed), + ApplicationError::WorktopError(worktop_error), )); } api.field_close(worktop_handle)?; @@ -459,15 +472,22 @@ impl WorktopBlueprint { LockFlags::read_only(), )?; let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?; - let ids = if let Some(bucket) = worktop.resources.get(&input.resource_address) { + let bucket_ids = if let Some(bucket) = worktop.resources.get(&input.resource_address) { let bucket = Bucket(bucket.clone()); bucket.non_fungible_local_ids(api)? } else { index_set_new() }; - if !ids.is_superset(&input.ids) { + if let Some(missing_id) = input.ids.difference(&bucket_ids).next() { + let worktop_error = + WorktopError::AssertionFailed(ResourceConstraintsError::ResourceConstraintFailed { + resource_address: input.resource_address, + error: ResourceConstraintError::NonFungibleMissing { + missing_id: missing_id.clone(), + }, + }); return Err(RuntimeError::ApplicationError( - ApplicationError::WorktopError(WorktopError::AssertionFailed), + ApplicationError::WorktopError(worktop_error), )); } api.field_close(worktop_handle)?; @@ -547,7 +567,15 @@ impl WorktopBlueprintCuttlefishExtension { .as_typed() .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?; - Self::assert_resources(input.constraints, false, api) + Self::aggregate_resources(api)? + .validate_includes(input.constraints) + .map_err(|e| { + RuntimeError::ApplicationError(ApplicationError::WorktopError( + WorktopError::AssertionFailed(e), + )) + })?; + + Ok(IndexedScryptoValue::from_typed(&())) } pub(crate) fn assert_resources_only>( @@ -558,14 +586,20 @@ impl WorktopBlueprintCuttlefishExtension { .as_typed() .map_err(|e| RuntimeError::ApplicationError(ApplicationError::InputDecodeError(e)))?; - Self::assert_resources(input.constraints, true, api) + Self::aggregate_resources(api)? + .validate_only(input.constraints) + .map_err(|e| { + RuntimeError::ApplicationError(ApplicationError::WorktopError( + WorktopError::AssertionFailed(e), + )) + })?; + + Ok(IndexedScryptoValue::from_typed(&())) } - fn assert_resources>( - constraints: ManifestResourceConstraints, - exact: bool, - api: &mut Y, - ) -> Result { + fn aggregate_resources( + api: &mut impl SystemApi, + ) -> Result { let worktop_handle = api.actor_open_field( ACTOR_STATE_SELF, WorktopField::Worktop.into(), @@ -573,28 +607,20 @@ impl WorktopBlueprintCuttlefishExtension { )?; let worktop: WorktopSubstate = api.field_read_typed(worktop_handle)?; - let mut constraint_checker = ResourceConstraintChecker::new(constraints, exact); + let mut aggregated_balances = AggregateResourceBalances::new(); for (resource, bucket) in worktop.resources { let bucket = Bucket(bucket.clone()); if resource.is_fungible() { let amount = bucket.amount(api)?; - constraint_checker.add_fungible(resource, amount); + aggregated_balances.add_fungible(resource, amount); } else { let ids = bucket.non_fungible_local_ids(api)?; - constraint_checker.add_non_fungible(resource, ids); + aggregated_balances.add_non_fungible(resource, ids); } } - constraint_checker.validate().map_err(|e| { - RuntimeError::ApplicationError(ApplicationError::WorktopError( - WorktopError::ResourceConstraintsError(e), - )) - })?; - - api.field_close(worktop_handle)?; - - Ok(IndexedScryptoValue::from_typed(&())) + Ok(aggregated_balances) } pub fn invoke_export>( diff --git a/radix-engine/src/errors.rs b/radix-engine/src/errors.rs index 7fdb1533c7c..05668d40bee 100644 --- a/radix-engine/src/errors.rs +++ b/radix-engine/src/errors.rs @@ -320,7 +320,7 @@ pub enum IntentError { VerifyParentFailed, InvalidIntentIndex(usize), NoParentToYieldTo, - AssertNextCallReturnsFailed(ManifestResourceConstraintsError), + AssertNextCallReturnsFailed(ResourceConstraintsError), AssertBucketContentsFailed(ResourceConstraintError), } diff --git a/radix-engine/src/object_modules/metadata/package.rs b/radix-engine/src/object_modules/metadata/package.rs index e56c0640677..4321dc71c79 100644 --- a/radix-engine/src/object_modules/metadata/package.rs +++ b/radix-engine/src/object_modules/metadata/package.rs @@ -19,7 +19,8 @@ pub enum MetadataError { KeyStringExceedsMaxLength { max: usize, actual: usize }, ValueSborExceedsMaxLength { max: usize, actual: usize }, ValueDecodeError(DecodeError), - MetadataValidationError(MetadataValidationError), + MetadataValueValidationError(MetadataValueValidationError), + MetadataKeyValidationError(MetadataKeyValidationError), } declare_native_blueprint_state! { @@ -262,8 +263,9 @@ impl MetadataNativePackage { Ok(Own(node_id)) } - pub fn init_system_struct( - data: MetadataInit, + /// This method assumes that the data has been pre-checked. + pub(crate) fn init_system_struct( + data: IndexMap, (Option>, bool)>, ) -> Result< ( IndexMap, @@ -272,36 +274,10 @@ impl MetadataNativePackage { MetadataError, > { let mut init_kv_entries = index_map_new(); - for (key, entry) in data.data { - if key.len() > MAX_METADATA_KEY_STRING_LEN { - return Err(MetadataError::KeyStringExceedsMaxLength { - max: MAX_METADATA_KEY_STRING_LEN, - actual: key.len(), - }); - } - - let key = scrypto_encode(&key).unwrap(); - - let value = match entry.value { - Some(metadata_value) => { - let value = scrypto_encode(&MetadataEntryEntryPayload::from_content_source( - metadata_value, - )) - .unwrap(); - if value.len() > MAX_METADATA_VALUE_SBOR_LEN { - return Err(MetadataError::ValueSborExceedsMaxLength { - max: MAX_METADATA_VALUE_SBOR_LEN, - actual: value.len(), - }); - } - Some(value) - } - None => None, - }; - + for (key, entry) in data { let kv_entry = KVEntry { - value, - locked: entry.lock, + value: entry.0, + locked: entry.1, }; init_kv_entries.insert(key, kv_entry); @@ -317,17 +293,8 @@ impl MetadataNativePackage { metadata_init: MetadataInit, api: &mut Y, ) -> Result { - for value in metadata_init.data.values() { - if let Some(v) = &value.value { - validate_metadata_value(&v).map_err(|e| { - RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::MetadataValidationError(e), - )) - })?; - } - } - - let (fields, kv_entries) = Self::init_system_struct(metadata_init) + let metadata_validated = validate_metadata_init(metadata_init)?; + let (fields, kv_entries) = Self::init_system_struct(metadata_validated) .map_err(|e| RuntimeError::ApplicationError(ApplicationError::MetadataError(e)))?; let node_id = api.new_object( @@ -346,41 +313,25 @@ impl MetadataNativePackage { value: MetadataValue, api: &mut Y, ) -> Result<(), RuntimeError> { - validate_metadata_value(&value).map_err(|e| { + let key_sbor = validate_metadata_key(&key).map_err(|e| { RuntimeError::ApplicationError(ApplicationError::MetadataError( - MetadataError::MetadataValidationError(e), + MetadataError::MetadataKeyValidationError(e), )) })?; - if key.len() > MAX_METADATA_KEY_STRING_LEN { - return Err(RuntimeError::ApplicationError( - ApplicationError::MetadataError(MetadataError::KeyStringExceedsMaxLength { - max: MAX_METADATA_KEY_STRING_LEN, - actual: key.len(), - }), - )); - } - - let sbor_value = scrypto_encode(&MetadataEntryEntryPayload::from_content_source( - value.clone(), - )) - .unwrap(); - if sbor_value.len() > MAX_METADATA_VALUE_SBOR_LEN { - return Err(RuntimeError::ApplicationError( - ApplicationError::MetadataError(MetadataError::ValueSborExceedsMaxLength { - max: MAX_METADATA_VALUE_SBOR_LEN, - actual: sbor_value.len(), - }), - )); - } + let value_sbor = validate_metadata_value(&value).map_err(|e| { + RuntimeError::ApplicationError(ApplicationError::MetadataError( + MetadataError::MetadataValueValidationError(e), + )) + })?; let handle = api.actor_open_key_value_entry( ACTOR_STATE_SELF, MetadataCollection::EntryKeyValue.collection_index(), - &scrypto_encode(&key).unwrap(), + &key_sbor, LockFlags::MUTABLE, )?; - api.key_value_entry_set(handle, sbor_value)?; + api.key_value_entry_set(handle, value_sbor)?; api.key_value_entry_close(handle)?; Runtime::emit_event(api, SetMetadataEvent { key, value })?; @@ -439,12 +390,69 @@ impl MetadataNativePackage { } #[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] -pub enum MetadataValidationError { +pub enum MetadataKeyValidationError { + InvalidValue, + InvalidLength { max: usize, actual: usize }, +} + +#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)] +pub enum MetadataValueValidationError { InvalidURL(String), InvalidOrigin(String), + InvalidValue, + InvalidLength { max: usize, actual: usize }, +} + +pub fn validate_metadata_init( + metadata_init: MetadataInit, +) -> Result, (Option>, bool)>, RuntimeError> { + let mut checked_data = index_map_new(); + for (key, value) in &metadata_init.data { + let key_sbor = validate_metadata_key(key).map_err(|e| { + RuntimeError::ApplicationError(ApplicationError::MetadataError( + MetadataError::MetadataKeyValidationError(e), + )) + })?; + + if let Some(v) = &value.value { + let value_sbor = validate_metadata_value(&v).map_err(|e| { + RuntimeError::ApplicationError(ApplicationError::MetadataError( + MetadataError::MetadataValueValidationError(e), + )) + })?; + checked_data.insert(key_sbor, (Some(value_sbor), value.lock)); + } else { + checked_data.insert(key_sbor, (None, value.lock)); + } + } + Ok(checked_data) } -pub fn validate_metadata_value(value: &MetadataValue) -> Result<(), MetadataValidationError> { +pub fn validate_metadata_key(key: &str) -> Result, MetadataKeyValidationError> { + if key.len() > MAX_METADATA_KEY_STRING_LEN { + return Err(MetadataKeyValidationError::InvalidLength { + max: MAX_METADATA_KEY_STRING_LEN, + actual: key.len(), + }); + } + + scrypto_encode(&key).map_err(|_| MetadataKeyValidationError::InvalidValue) +} + +pub fn validate_metadata_value( + value: &MetadataValue, +) -> Result, MetadataValueValidationError> { + let sbor_value = scrypto_encode(&MetadataEntryEntryPayload::from_content_source( + value.clone(), + )) + .map_err(|_| MetadataValueValidationError::InvalidValue)?; + if sbor_value.len() > MAX_METADATA_VALUE_SBOR_LEN { + return Err(MetadataValueValidationError::InvalidLength { + max: MAX_METADATA_VALUE_SBOR_LEN, + actual: sbor_value.len(), + }); + } + match value { MetadataValue::String(_) => {} MetadataValue::Bool(_) => {} @@ -460,13 +468,14 @@ pub fn validate_metadata_value(value: &MetadataValue) -> Result<(), MetadataVali MetadataValue::NonFungibleLocalId(_) => {} MetadataValue::Instant(_) => {} MetadataValue::Url(url) => { - CheckedUrl::of(url.as_str()) - .ok_or(MetadataValidationError::InvalidURL(url.as_str().to_owned()))?; + CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL( + url.as_str().to_owned(), + ))?; } MetadataValue::Origin(origin) => { - CheckedOrigin::of(origin.as_str()).ok_or(MetadataValidationError::InvalidOrigin( - origin.as_str().to_owned(), - ))?; + CheckedOrigin::of(origin.as_str()).ok_or( + MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()), + )?; } MetadataValue::PublicKeyHash(_) => {} MetadataValue::StringArray(_) => {} @@ -484,19 +493,20 @@ pub fn validate_metadata_value(value: &MetadataValue) -> Result<(), MetadataVali MetadataValue::InstantArray(_) => {} MetadataValue::UrlArray(urls) => { for url in urls { - CheckedUrl::of(url.as_str()) - .ok_or(MetadataValidationError::InvalidURL(url.as_str().to_owned()))?; + CheckedUrl::of(url.as_str()).ok_or(MetadataValueValidationError::InvalidURL( + url.as_str().to_owned(), + ))?; } } MetadataValue::OriginArray(origins) => { for origin in origins { CheckedOrigin::of(origin.as_str()).ok_or( - MetadataValidationError::InvalidOrigin(origin.as_str().to_owned()), + MetadataValueValidationError::InvalidOrigin(origin.as_str().to_owned()), )?; } } MetadataValue::PublicKeyHashArray(_) => {} } - Ok(()) + Ok(sbor_value) } diff --git a/radix-engine/src/system/transaction/instructions.rs b/radix-engine/src/system/transaction/instructions.rs index 7e33a9ff3c0..6e51d137aec 100644 --- a/radix-engine/src/system/transaction/instructions.rs +++ b/radix-engine/src/system/transaction/instructions.rs @@ -340,9 +340,10 @@ impl TxnNormalInstruction for AssertNextCallReturnsOnly { objects: &mut IntentProcessorObjects, _api: &mut Y, ) -> Result { - objects.next_call_return_constraints = Some(NextCallReturnsConstraints { + objects.next_call_return_constraints = Some(NextCallReturnsChecker { constraints: self.constraints, - exact: true, + prevent_unspecified_resource_balances: true, + aggregate_balances: AggregateResourceBalances::new(), }); Ok(InstructionOutput::None) @@ -356,9 +357,10 @@ impl TxnNormalInstruction for AssertNextCallReturnsInclude { objects: &mut IntentProcessorObjects, _api: &mut Y, ) -> Result { - objects.next_call_return_constraints = Some(NextCallReturnsConstraints { + objects.next_call_return_constraints = Some(NextCallReturnsChecker { constraints: self.constraints, - exact: false, + prevent_unspecified_resource_balances: false, + aggregate_balances: AggregateResourceBalances::new(), }); Ok(InstructionOutput::None) @@ -384,7 +386,7 @@ impl TxnNormalInstruction for AssertBucketContents { })?; } else { let ids = bucket.non_fungible_local_ids(api)?; - self.constraint.validate_non_fungible(ids).map_err(|e| { + self.constraint.validate_non_fungible(&ids).map_err(|e| { RuntimeError::SystemError(SystemError::IntentError( IntentError::AssertBucketContentsFailed(e), )) diff --git a/radix-engine/src/system/transaction/intent_processor.rs b/radix-engine/src/system/transaction/intent_processor.rs index ec2869eac82..2d29cdba7af 100644 --- a/radix-engine/src/system/transaction/intent_processor.rs +++ b/radix-engine/src/system/transaction/intent_processor.rs @@ -160,9 +160,25 @@ impl<'a, I: TxnInstruction + ManifestDecode + ManifestCategorize> IntentProcesso } } -pub struct NextCallReturnsConstraints { +pub struct NextCallReturnsChecker { pub constraints: ManifestResourceConstraints, - pub exact: bool, + pub prevent_unspecified_resource_balances: bool, + pub aggregate_balances: AggregateResourceBalances, +} + +impl NextCallReturnsChecker { + fn validate(self) -> Result<(), RuntimeError> { + let result = if self.prevent_unspecified_resource_balances { + self.aggregate_balances.validate_only(self.constraints) + } else { + self.aggregate_balances.validate_includes(self.constraints) + }; + result.map_err(|error| { + RuntimeError::SystemError(SystemError::IntentError( + IntentError::AssertNextCallReturnsFailed(error), + )) + }) + } } pub struct IntentProcessorObjects<'a> { @@ -174,7 +190,7 @@ pub struct IntentProcessorObjects<'a> { blobs_by_hash: &'a IndexMap>, max_total_size_of_blobs: usize, - pub next_call_return_constraints: Option, + pub next_call_return_constraints: Option, } impl<'a> IntentProcessorObjects<'a> { @@ -366,10 +382,7 @@ impl<'a> IntentProcessorObjects<'a> { worktop: &Worktop, api: &mut Y, ) -> Result<(), RuntimeError> { - let mut resource_constraint_checker = - self.next_call_return_constraints.take().map(|constraints| { - ResourceConstraintChecker::new(constraints.constraints, constraints.exact) - }); + let mut resource_constraint_checker = self.next_call_return_constraints.take(); // Auto move into worktop & auth_zone for node_id in value.owned_nodes() { @@ -388,7 +401,9 @@ impl<'a> IntentProcessorObjects<'a> { .expect() .try_into() .unwrap(); - checker.add_fungible(resource_address, bucket.amount(api)?); + checker + .aggregate_balances + .add_fungible(resource_address, bucket.amount(api)?); } worktop.put(bucket, api)?; } @@ -401,7 +416,7 @@ impl<'a> IntentProcessorObjects<'a> { .expect() .try_into() .unwrap(); - checker.add_non_fungible( + checker.aggregate_balances.add_non_fungible( resource_address, bucket.non_fungible_local_ids(api)?, ); @@ -426,65 +441,13 @@ impl<'a> IntentProcessorObjects<'a> { } if let Some(checker) = resource_constraint_checker { - checker.validate().map_err(|e| { - RuntimeError::SystemError(SystemError::IntentError( - IntentError::AssertNextCallReturnsFailed(e), - )) - })?; + checker.validate()?; } Ok(()) } } -pub struct ResourceConstraintChecker { - fungible_resources: IndexMap, - non_fungible_resources: IndexMap>, - constraints: ManifestResourceConstraints, - exact: bool, -} - -impl ResourceConstraintChecker { - pub fn new(constraints: ManifestResourceConstraints, exact: bool) -> Self { - Self { - fungible_resources: Default::default(), - non_fungible_resources: Default::default(), - constraints, - exact, - } - } - - pub fn add_fungible(&mut self, resource_address: ResourceAddress, amount: Decimal) { - if amount.is_positive() { - self.fungible_resources - .entry(resource_address) - .or_default() - .add_assign(amount); - } - } - - pub fn add_non_fungible( - &mut self, - resource_address: ResourceAddress, - ids: IndexSet, - ) { - if !ids.is_empty() { - self.non_fungible_resources - .entry(resource_address) - .or_default() - .extend(ids); - } - } - - pub fn validate(self) -> Result<(), ManifestResourceConstraintsError> { - self.constraints.validate( - self.fungible_resources, - self.non_fungible_resources, - self.exact, - ) - } -} - pub struct IntentProcessorObjectsWithApi<'a, 'e, Y: SystemApi> { pub(crate) worktop: &'a mut Worktop, pub(crate) objects: &'a mut IntentProcessorObjects<'e>, diff --git a/radix-engine/src/transaction/node_versioned_local_transaction_execution_cuttlefish.bin b/radix-engine/src/transaction/node_versioned_local_transaction_execution_cuttlefish.bin index 11b60809a77..83b11c2db96 100644 Binary files a/radix-engine/src/transaction/node_versioned_local_transaction_execution_cuttlefish.bin and b/radix-engine/src/transaction/node_versioned_local_transaction_execution_cuttlefish.bin differ diff --git a/radix-engine/src/vm/wasm_runtime/scrypto_runtime.rs b/radix-engine/src/vm/wasm_runtime/scrypto_runtime.rs index ecb75c52e7b..290f9a26053 100644 --- a/radix-engine/src/vm/wasm_runtime/scrypto_runtime.rs +++ b/radix-engine/src/vm/wasm_runtime/scrypto_runtime.rs @@ -20,7 +20,6 @@ pub struct ScryptoRuntime<'y, Y: SystemApi> { package_address: PackageAddress, export_name: String, wasm_execution_units_buffer: u32, - max_number_of_buffers: usize, scrypto_vm_version: ScryptoVmVersion, } @@ -38,7 +37,6 @@ impl<'y, Y: SystemApi> ScryptoRuntime<'y, Y> { package_address, export_name, wasm_execution_units_buffer: 0, - max_number_of_buffers: MAX_NUMBER_OF_BUFFERS, scrypto_vm_version, } } @@ -83,7 +81,14 @@ impl<'y, Y: SystemApi> WasmRuntime for ScryptoRuntime<'y, Y> { ) -> Result> { assert!(buffer.len() <= 0xffffffff); - if self.buffers.len() >= self.max_number_of_buffers { + let max_number_of_buffers = match self.scrypto_vm_version { + ScryptoVmVersion::V1_0 | ScryptoVmVersion::V1_1 => 32, + // Practically speaking, there is little gain of keeping multiple buffers open before + // [multi-value](https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md) is supported and used. + // We reduce it to `4` so that the amount of memory that a transaction can consume is reduced, which is beneficial for parallel execution. + ScryptoVmVersion::V1_2 => 4, + }; + if self.buffers.len() >= max_number_of_buffers { return Err(InvokeError::SelfError(WasmRuntimeError::TooManyBuffers)); } diff --git a/radix-rust/src/rust.rs b/radix-rust/src/rust.rs index c0eb1e98956..388085806c0 100644 --- a/radix-rust/src/rust.rs +++ b/radix-rust/src/rust.rs @@ -466,7 +466,7 @@ pub mod collections { /// This is safe for std and no-std use cases (unlike `IndexSet::new` which disappears when std is not in the toolchain - see /// [this article](https://faultlore.com/blah/defaults-affect-inference/) for deep technical reasons) pub fn new() -> IndexSet { - IndexSet::with_capacity_and_hasher(0, DefaultHashBuilder::default()) + IndexSet::with_hasher(DefaultHashBuilder::default()) } /// This is safe for std and no-std use cases (unlike `IndexSet::with_capacity` which disappears when std is not in the toolchain - see @@ -535,10 +535,7 @@ pub mod collections { impl NonIterMap { /// Creates an empty map. pub fn new() -> Self { - Self(HashMap::with_capacity_and_hasher( - 0, - DefaultHashBuilder::default(), - )) + Self(HashMap::with_hasher(DefaultHashBuilder::default())) } /// Gets the given key's corresponding entry in the map for in-place manipulation. diff --git a/radix-transaction-scenarios/generated-examples/cuttlefish/basic_subintents/receipts/007--first_transaction_with_subintent_which_fails.txt b/radix-transaction-scenarios/generated-examples/cuttlefish/basic_subintents/receipts/007--first_transaction_with_subintent_which_fails.txt index 935bfc61baf..69ac3c47365 100644 --- a/radix-transaction-scenarios/generated-examples/cuttlefish/basic_subintents/receipts/007--first_transaction_with_subintent_which_fails.txt +++ b/radix-transaction-scenarios/generated-examples/cuttlefish/basic_subintents/receipts/007--first_transaction_with_subintent_which_fails.txt @@ -1,4 +1,4 @@ -TRANSACTION STATUS: COMMITTED FAILURE: ApplicationError(WorktopError(AssertionFailed)) +TRANSACTION STATUS: COMMITTED FAILURE: ApplicationError(WorktopError(AssertionFailed(ResourceConstraintFailed { resource_address: ResourceAddress(5da66318c6318c61f5a61b4c6318c6318cf794aa8d295f14e6318c6318c6), error: ExpectedAtLeastAmount { expected_at_least_amount: 1, actual_amount: 0 } }))) TRANSACTION COST: 0.2204047197 XRD ├─ Network execution: 0.14506445 XRD, 2901289 execution cost units diff --git a/radix-transactions/src/builder/transaction_builder.rs b/radix-transactions/src/builder/transaction_builder.rs index c1720b20715..ad3781cd2b9 100644 --- a/radix-transactions/src/builder/transaction_builder.rs +++ b/radix-transactions/src/builder/transaction_builder.rs @@ -251,7 +251,8 @@ impl PartialTransactionV2Builder { for (child_name, (hash, _, _)) in self.child_partial_transactions.iter() { manifest_builder = manifest_builder.use_child(child_name, *hash); } - self.root_subintent_manifest = Some(build_manifest(manifest_builder).build()); + // The manifest will be validated as part of the transaction builder validation. + self.root_subintent_manifest = Some(build_manifest(manifest_builder).build_no_validate()); self } @@ -269,7 +270,9 @@ impl PartialTransactionV2Builder { for (child_name, (hash, _, _)) in self.child_partial_transactions.iter() { manifest_builder = manifest_builder.use_child(child_name, *hash); } - self.root_subintent_manifest = Some(build_manifest(manifest_builder, name_lookup).build()); + // The manifest will be validated as part of the transaction builder validation. + self.root_subintent_manifest = + Some(build_manifest(manifest_builder, name_lookup).build_no_validate()); self } @@ -654,7 +657,9 @@ impl TransactionV2Builder { for (child_name, (hash, _, _)) in self.child_partial_transactions.iter() { manifest_builder = manifest_builder.use_child(child_name, *hash); } - self.transaction_intent_manifest = Some(build_manifest(manifest_builder).build()); + // The manifest will be validated as part of the transaction builder validation. + self.transaction_intent_manifest = + Some(build_manifest(manifest_builder).build_no_validate()); self } @@ -672,8 +677,9 @@ impl TransactionV2Builder { for (child_name, (hash, _, _)) in self.child_partial_transactions.iter() { manifest_builder = manifest_builder.use_child(child_name, *hash); } + // The manifest will be validated as part of the transaction builder validation. self.transaction_intent_manifest = - Some(build_manifest(manifest_builder, name_lookup).build()); + Some(build_manifest(manifest_builder, name_lookup).build_no_validate()); self }