---
title: "13. Before You Release: Hardening a Blueprint"
url: "https://radix.wiki/developers/scrypto/13-before-you-release"
updated: 2026-11-14
license: CC-BY-4.0
license_url: "https://creativecommons.org/licenses/by/4.0/"
version: "1.0.0"
---

# 13. Before You Release: Hardening a Blueprint

|  |  |
| --- | --- |
| Series | [Scrypto track](/developers/scrypto), part 13 |
| Tested with | Scrypto 1.4.0 and `scrypto-test`'s ledger simulator, 14 November 2026 |
| Deterministic builder | [radixdlt/scrypto-builder](https://hub.docker.com/r/radixdlt/scrypto-builder/tags), newest tag v1.3.1 |

## Introduction

Radix's [asset-oriented](/contents/tech/core-concepts/asset-oriented-programming) engine rules out several classes of smart-contract bug, but not the ones in a blueprint's own logic. The Radix docs collect the mistakes that recur in Scrypto code under "Before You Release": two pages, one on security and one on preparing a package for production ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening), [Productionize Your Code](https://docs.radixdlt.com/docs/productionize-your-code)). This guide condenses both into the checks to run before publishing to mainnet, with the arithmetic cases tested in the ledger simulator.

## Check the resource of every proof

A method that takes a `Proof` as an argument must check which resource the proof is of before reading anything from it. Otherwise an attacker creates their own resource, mints a non-fungible with the local ID they want to impersonate, and passes a proof of that. Scrypto makes the check hard to skip: a `Proof` reveals nothing until it is converted with `check`, `check_with_message` or `skip_checking`, so the unsafe version has `skip_checking` written in it ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)).

```
pub fn claim(&mut self, proof: Proof) -> Bucket {
    let id = proof
        // Fails the call unless the proof is of the user badge resource.
        .check_with_message(self.user_badge.address(), "Not a user badge")
        .as_non_fungible()
        .non_fungible_local_id();
    self.claims.get_mut(&id).unwrap().take_all()
}
```

In the claim blueprint of [Deposit and Withdraw Patterns](/developers/scrypto/11-deposit-and-withdraw-patterns), a proof of another resource failed with the check's message. The same applies to buckets passed in: check `resource_address()` before trusting an amount.

## Can a proof be counted twice?

Yes. Proofs can be cloned, and the holder can create a new proof from the same vault as often as they like, so a vote weighted by a proof's amount can be cast any number of times. The docs' fix borrows from [liquid stake units](/contents/tech/core-concepts/liquid-stake-units): take the tokens themselves in a bucket, hold them for the vote's duration, and return a claim token the voter redeems afterwards ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)):

```
pub fn vote(&mut self, votes: Bucket, proposal_id: u64, vote: Vote) -> Bucket {
    assert_eq!(votes.resource_address(), self.voting_resource.address());
    let weight = votes.amount();
    // ...record the vote...
    self.locked_votes.put(votes);
    // Return a claim the voter exchanges for their tokens after the vote closes.
    self.voting_claim_resource.mint(weight).into()
}
```

## Keep state from growing without bound

A `Vec`, `HashMap`, `BTreeMap`, `IndexMap` or set in a component's state is loaded whole on every call, and reading state costs fees. A collection that grows with use makes every call dearer, and past the transaction's cost limit makes the component uncallable, with any vaults inside it locked for good. The docs' example is a DEX that keeps its pairs in an `IndexMap`: enough pairs and nobody can swap or withdraw liquidity ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)).

The fix is a `KeyValueStore`, which loads one entry at a time; a set is `KeyValueStore`. A `KeyValueStore` cannot be iterated on-ledger. Where iteration is needed, the docs point to community structures such as [Ociswap's AVL tree](https://github.com/ociswap/scrypto-avltree), and warn that one transaction can still read and write only a limited number of substates. A fixed-size array, or a collection the code guarantees never grows, is safe in ordinary state.

## Decimal arithmetic

The `Decimal` operators panic on overflow, which fails the transaction. A panic is safe, but for an input the developer did not foresee it can stop a component working, and in a pool it can trap deposits. The docs ask for `checked_add`, `checked_mul` and the like, which return `None` and force a decision about what an overflow means, and for ordering operations so they cannot overflow: `units / total_supply * reserves` cannot overflow where `units * reserves / total_supply` can ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)).

Dividing first loses precision, so the docs recommend computing in `PreciseDecimal`, with 36 decimal places, and converting to `Decimal` at the end ([Data Types and the Runtime API](/developers/scrypto/12-data-types-and-runtime)).

The result then has to fit the resource. Taking an amount with more decimal places than the resource's divisibility allows fails the transaction; `take_advanced` rounds instead:

```
// A resource with divisibility 2, and an amount with 17 decimal places.
let amount = dec!(1) / dec!(3) * dec!(10); // 3.33333333333333333

supply.take(amount);
// fails: BucketError(InvalidAmount(3.33333333333333333))

supply.take_advanced(amount, WithdrawStrategy::Rounded(RoundingMode::ToZero));
// takes 3.33
```

Both lines ran in the simulator against a resource of divisibility 2, with the results shown. A DEX that skips this rounding fails some swaps and not others, depending on the amounts.

## Make sure protected methods are protected

Without `enable_method_auth!`, every `pub` method of a blueprint can be called by anyone. With it, a method marked `PUBLIC` by mistake, or a role given `rule!(allow_all)` at instantiation, has the same effect. The macros enforce that every method is mapped to a role and every role to a rule, as a compile error, but not that the mapping is right. Read through both before release ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)):

```
enable_method_auth! {
    roles {
        manager => updatable_by: [];
    },
    methods {
        mint_admin_badge => restrict_to: [manager];
    }
}
// ...and at instantiation, a real rule for the role, not allow_all:
.roles(roles! { manager => manager_rule; })
```

Functions need the same review: `enable_function_auth!` restricts who can call a blueprint's functions.

## Plan for bugs found after release

A published package's code cannot be changed: the native package blueprint offers publishing and royalty claims and no upgrade ([package invocations](https://github.com/radixdlt/radixdlt-scrypto/blob/main/radix-engine-interface/src/blueprints/package/invocations.rs)). The docs ask every non-trivial application to answer, before release, how it will move assets and state to fixed code, how users will find the new version, and whether it can pause while a fix is prepared. One architecture they describe keeps state and vaults in one component and logic in another that reads and writes it with a badge, so retiring old logic means moving the badge, with a proxy component whose address never changes in front ([Code Hardening](https://docs.radixdlt.com/docs/code-hardening)).

## Production settings

- **Give everything an owner.** Every package, component and resource should have an owner role, because the docs expect future engine features, such as upgrades, to be controlled by it. An instantiation function should take an `OwnerRole` and pass it to everything it creates, and the owner badge must sit somewhere that can produce proofs, such as an account or an [access controller](/contents/tech/core-concepts/access-controller). `OwnerRole::Updatable` lets the rule itself change later; `OwnerRole::Fixed` does not ([Productionize Your Code](https://docs.radixdlt.com/docs/productionize-your-code)).
- **Follow the metadata standards, and leave metadata updatable.** The wallet and dashboard read names, icons and the two-way links to the [dApp definition](/developers/frontend/04-dapp-definition-and-verification) from metadata, and the standards change; locked metadata cannot follow them.
- **Check how it looks in the wallet.** A manifest that matches one of the wallet's conforming types is shown as deposits and withdrawals; any other is shown as raw manifest text. A deposit of a known amount should use `TAKE_FROM_WORKTOP` with that amount rather than `TAKE_ALL_FROM_WORKTOP` or `ENTIRE_WORKTOP`, or the wallet asks the user to set a guarantee.
- **Register non-fungible and key-value store types.** Listing them in `#[types(...)]` above the blueprint module moves their schemas into the package definition at compile time, which the docs say makes the WASM smaller and every call cheaper.
- **Build deterministically if users should be able to verify the code.** `scrypto build` output differs between machines. The docs' answer is the `radixdlt/scrypto-builder` Docker image at the same version as the package's Scrypto dependency; on 14 November 2026 Docker Hub's newest tag was v1.3.1, with none for 1.4.0.

## Next Steps

- [Testing Scrypto Blueprints](/developers/scrypto/05-testing-scrypto) – turn each check above into a test that fails if it regresses
- [Deploying to Stokenet and Mainnet](/developers/getting-started/03-deploying)

## External Links

- [Code Hardening](https://docs.radixdlt.com/docs/code-hardening) – Radix documentation, with misconfigured and corrected examples
- [Productionize Your Code](https://docs.radixdlt.com/docs/productionize-your-code) – Radix documentation
- [Metadata for Wallet Display](https://docs.radixdlt.com/docs/metadata-for-wallet-display) – the metadata standard
- [Conforming Manifest Types](https://docs.radixdlt.com/docs/conforming-manifest-types) – what the wallet can summarise
