Skip to content

Support Dynamic Resources in Static Analyzer#2090

Open
BusyWritingCode wants to merge 7 commits intodevelopfrom
fix/static-analyzer-dynamic-resource
Open

Support Dynamic Resources in Static Analyzer#2090
BusyWritingCode wants to merge 7 commits intodevelopfrom
fix/static-analyzer-dynamic-resource

Conversation

@BusyWritingCode
Copy link
Collaborator

Summary

The static resource movements analyzer previously only understood resources with known, pre-existing ResourceAddress values. When a manifest created a new resource within the transaction itself (via allocate_global_address + one of the *CreateWithInitialSupply calls) and then performed operations on it using named addresses (withdrawals, deposits, mints, etc.), the analyzer had no way to track those resources. They fell through to the "unspecified resources" path, which eventually caused a panic: "Account withdraw output should not have unspecified resources".

This was hit in production, the radix-engine-toolkit uses this analyzer, and when the Gateway API's aggregator encountered one of these manifests on mainnet, it panicked and stopped aggregating.

What changed

New AnalyzerResourceAddress type, A new enum that can represent both static (on-ledger, known ResourceAddress) and dynamic (created within the manifest being analyzed) resources:

pub enum AnalyzerResourceAddress {
    Static(ResourceAddress),
    Dynamic {
        blueprint_id: BlueprintId,
        named_address: u32,
    },
}

For dynamic resources, the blueprint_id identifies the creating blueprint (e.g. FungibleResourceManager) and named_address ties back to the manifest-level named address. This gives us enough information to determine fungibility and track the resource across instructions without knowing its final on-ledger address.

All core types updated, TrackedResources, ResourceBounds, AccountWithdraw, AccountDeposit, NetWithdraws, NetDeposits, ManifestResourceConstraints, and related types all changed from ResourceAddress to AnalyzerResourceAddress. A From<ResourceAddress> impl and impl Into<AnalyzerResourceAddress> parameters keep the API ergonomic for the common static-address case.

Address reservation tracking in the visitor, The StaticResourceMovementsVisitor now tracks address reservations (mapping reservation -> (blueprint, named_address)) alongside named addresses. This is populated from OnNewNamedAddress events when the address has an associated reservation.

Effect implementations updated for dynamic resolution, Previously, any invocation with a ManifestResourceAddress::Named(...) would bail out to "unspecified resources". Now, the analyzer resolves named addresses through the tracked named addresses and address reservations maps to produce a proper AnalyzerResourceAddress::Dynamic. This covers:

  • Account operations: AccountWithdraw, AccountWithdrawNonFungibles, AccountLockFeeAndWithdraw, AccountLockFeeAndWithdrawNonFungibles
  • Account locker operations: AccountLockerRecover, AccountLockerRecoverNonFungibles, AccountLockerClaim, AccountLockerClaimNonFungibles
  • Pool operations: TwoResourcePoolProtectedWithdraw, MultiResourcePoolProtectedWithdraw
  • Resource creation: FungibleResourceManagerCreateWithInitialSupply, NonFungibleResourceManagerCreateWithInitialSupply, NonFungibleResourceManagerCreateRuidWithInitialSupply, these were previously lumped in with "unknown output" invocations and now have proper implementations that resolve the address reservation to a dynamic resource address
  • Minting on dynamic resources: FungibleResourceManagerMint, NonFungibleResourceManagerMint, NonFungibleResourceManagerMintRuid, NonFungibleResourceManagerMintSingleRuid, when called on a named address receiver, these now produce tracked output with the correct dynamic address

If a named address can't be resolved (i.e. it wasn't set up through allocate_global_address), the analyzer gracefully falls back to the existing "unspecified resources" behavior.

Testing

  • Added a new test (dynamic_resource_create_deposit_withdraw_redeposit_is_correctly_classified) that exercises the full lifecycle: allocate a global address, create a fungible resource with initial supply using that reservation, deposit to an account, withdraw via the named address, and redeposit. The test verifies that all withdrawals, deposits, net withdrawals, and net deposits are correctly tracked using AnalyzerResourceAddress::Dynamic.
  • All existing static analyzer tests were updated to work with AnalyzerResourceAddress (adding .into() conversions where needed) and continue to pass.
  • Beyond the in-repo tests: all transactions available on mainnet and stokenet were pulled and run through the analyzer to validate the core assumption that static analysis succeeds for all successful transactions. This dataset is too large to include in the repository but confirmed there are no regressions and that the fix resolves the production failures.

@BusyWritingCode BusyWritingCode requested a review from a team as a code owner March 20, 2026 04:23
@github-actions
Copy link

github-actions bot commented Mar 20, 2026

Docker tags
docker.io/radixdlt/private-scrypto-dev-container:9dbd6a296c

@github-actions
Copy link

github-actions bot commented Mar 20, 2026

Docker tags
docker.io/radixdlt/private-scrypto-builder:9dbd6a296c

@github-actions
Copy link

github-actions bot commented Mar 20, 2026

Benchmark for 9dbd6a2

Click to view benchmark
Test Base PR %
costing::bench_prepare_wasm 43.9±0.20ms 44.7±0.30ms +1.82%
costing::decode_encoded_i8_array_to_manifest_raw_value 15.5±0.01ms 15.6±0.06ms +0.65%
costing::decode_encoded_i8_array_to_manifest_value 56.6±0.12ms 56.8±0.30ms +0.35%
costing::decode_encoded_tuple_array_to_manifest_raw_value 49.5±0.04ms 47.9±0.04ms -3.23%
costing::decode_encoded_tuple_array_to_manifest_value 136.9±1.05ms 136.4±0.90ms -0.37%
costing::decode_encoded_u8_array_to_manifest_raw_value 32.3±0.06µs 25.9±0.08µs -19.81%
costing::decode_encoded_u8_array_to_manifest_value 56.7±0.09ms 56.6±0.09ms -0.18%
costing::decode_rpd_to_manifest_raw_value 9.9±0.04µs 9.3±0.04µs -6.06%
costing::decode_rpd_to_manifest_value 14.0±0.03µs 14.1±0.06µs +0.71%
costing::deserialize_wasm 1260.1±3.69µs 1233.2±3.05µs -2.13%
costing::execute_transaction_creating_big_vec_substates 824.0±10.12ms 833.9±6.06ms +1.20%
costing::execute_transaction_reading_big_vec_substates 546.6±0.92ms 555.0±1.27ms +1.54%
costing::instantiate_flash_loan 956.0±1649.19µs 938.9±1511.70µs -1.79%
costing::instantiate_radiswap 1227.0±2310.69µs 1077.4±2115.52µs -12.19%
costing::scrypto_malloc 756.3±1.71ms 770.0±3.47ms +1.81%
costing::scrypto_sbor_decode 839.4±8.35ms 779.9±15.79ms -7.09%
costing::scrypto_sha256 755.0±2.42ms 710.3±3.37ms -5.92%
costing::spin_loop_v1 504.7±0.84ms 521.7±2.07ms +3.37%
costing::spin_loop_v2 665.0±0.79ms 616.0±1.54ms -7.37%
costing::validate_sbor_payload 28.0±0.16µs 29.7±0.08µs +6.07%
costing::validate_sbor_payload_bytes 235.9±0.90ns 244.1±2.46ns +3.48%
costing::validate_secp256k1 77.1±0.05µs 77.4±0.09µs +0.39%
costing::validate_wasm 37.8±0.04ms 33.8±0.05ms -10.58%
decimal::add/0 8.8±0.09ns 8.7±0.01ns -1.14%
decimal::add/rust-native 9.8±0.00ns 9.9±0.05ns +1.02%
decimal::add/wasmi 463.7±17.00ns 465.2±9.43ns +0.32%
decimal::add/wasmi-call-native 3.4±0.02µs 3.3±0.01µs -2.94%
decimal::div/0 165.3±0.44ns 173.7±1.84ns +5.08%
decimal::from_string/0 152.4±0.08ns 148.2±0.12ns -2.76%
decimal::mul/0 129.0±0.13ns 129.7±0.08ns +0.54%
decimal::mul/rust-native 132.0±0.23ns 129.9±0.10ns -1.59%
decimal::mul/wasmi 22.7±0.03µs 21.0±0.06µs -7.49%
decimal::mul/wasmi-call-native 3.9±0.01µs 3.5±0.01µs -10.26%
decimal::pow/0 608.3±0.34ns 609.0±0.47ns +0.12%
decimal::pow/rust-native 606.8±0.58ns 606.4±0.29ns -0.07%
decimal::pow/wasmi 104.4±0.17µs 104.1±0.24µs -0.29%
decimal::pow/wasmi-call-native 5.4±0.01µs 5.4±0.02µs 0.00%
decimal::root/0 8.4±0.01µs 8.4±0.01µs 0.00%
decimal::sub/0 8.7±0.00ns 10.3±0.01ns +18.39%
decimal::to_string/0 458.0±0.65ns 450.3±2.63ns -1.68%
large_transaction_processing::prepare 2.7±0.00ms 2.8±0.00ms +3.70%
large_transaction_processing::prepare_and_decompile 6.7±0.01ms 6.5±0.04ms -2.99%
large_transaction_processing::prepare_and_decompile_and_recompile 26.2±0.09ms 30.7±2.04ms +17.18%
metadata_validation::validate_urls 5.1±0.01µs 5.0±0.08µs -1.96%
precise_decimal::add/0 9.5±0.18ns 10.1±0.01ns +6.32%
precise_decimal::add/rust-native 10.9±0.04ns 10.9±0.04ns 0.00%
precise_decimal::add/wasmi 603.1±2.86ns 594.2±2.61ns -1.48%
precise_decimal::add/wasmi-call-native 4.2±0.01µs 4.3±0.01µs +2.38%
precise_decimal::div/0 297.3±1.05ns 290.7±0.30ns -2.22%
precise_decimal::from_string/0 194.2±0.24ns 199.4±0.14ns +2.68%
precise_decimal::mul/0 337.0±0.18ns 336.1±0.44ns -0.27%
precise_decimal::mul/rust-native 289.0±0.37ns 290.3±0.35ns +0.45%
precise_decimal::mul/wasmi 52.2±0.34µs 52.7±0.24µs +0.96%
precise_decimal::mul/wasmi-call-native 4.6±0.01µs 4.7±0.00µs +2.17%
precise_decimal::pow/0 1797.9±2.35ns 1767.5±2.77ns -1.69%
precise_decimal::pow/rust-native 1335.1±0.61ns 1343.0±1.34ns +0.59%
precise_decimal::pow/wasmi 255.1±0.77µs 252.6±0.31µs -0.98%
precise_decimal::pow/wasmi-call-native 8.6±0.01µs 8.6±0.03µs 0.00%
precise_decimal::root/0 58.9±0.03µs 58.3±0.09µs -1.02%
precise_decimal::sub/0 9.6±0.03ns 9.4±0.08ns -2.08%
precise_decimal::to_string/0 693.1±0.36ns 690.0±0.14ns -0.45%
schema::validate_payload 377.9±1.34µs 365.2±0.26µs -3.36%
transaction::radiswap 5.1±0.02ms 5.2±0.02ms +1.96%
transaction::transfer 1796.7±5.59µs 1808.1±5.66µs +0.63%
transaction_validation::validate_manifest 43.4±0.05µs 43.4±0.02µs 0.00%
transaction_validation::verify_bls_2KB 1003.0±8.80µs 1031.1±26.64µs +2.80%
transaction_validation::verify_bls_32B 1030.9±21.75µs 1001.4±5.62µs -2.86%
transaction_validation::verify_ecdsa 75.3±0.05µs 75.2±0.04µs -0.13%
transaction_validation::verify_ed25519 46.4±0.04µs 42.7±0.14µs -7.97%

@BusyWritingCode BusyWritingCode requested a review from a team as a code owner March 20, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant