Skip to content

Commit

Permalink
Merge pull request #5 from devmannic/feature/scrypto-v0.4.1
Browse files Browse the repository at this point in the history
Feature/scrypto v0.4.1
  • Loading branch information
devmannic authored Jul 2, 2022
2 parents 3e46be3 + f43a324 commit 1c7dce2
Show file tree
Hide file tree
Showing 28 changed files with 1,119 additions and 996 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
toolchain: stable
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
toolchain: 1.56.0 # MSRV (default features)
toolchain: 1.60.0 # MSRV (default features)
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
toolchain: 1.56.0 # MSRV (all features)
toolchain: 1.60.0 # MSRV (all features)
## no nightly support since upstream deps are broken
#- os: ubuntu-latest
# target: x86_64-unknown-linux-gnu
Expand Down Expand Up @@ -56,14 +56,14 @@ jobs:
run: |
cargo test --release --all-features
- name: Test scrypto_statictypes (default features, MSRV)
if: ${{ matrix.toolchain == '1.56.0' }}
if: ${{ matrix.toolchain == '1.60.0' }}
run: |
cargo test --release
- name: Test scrypto_statictypes (all features, MSRV)
if: ${{ matrix.toolchain == '1.56.0' }}
if: ${{ matrix.toolchain == '1.60.0' }}
run: |
cargo test --release --all-features
- name: Test examples
run: |
cargo install --git https://github.com/radixdlt/radixdlt-scrypto --tag v0.3.0 simulator
cargo install --git https://github.com/radixdlt/radixdlt-scrypto --tag v0.4.1 simulator
./utils/test_examples.sh
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [0.5.0] - 2022-07-02
### Added
- `resource_manager()` methods for type-safe version of `borrow_resource_manager!(...resource_address())`
- `mint()`, `mint_non_fungible()` and `burn()` methods can be called directly on a `ResourceOf` with typed inputs/outputs
### Changed
- Compatibility: API now matches Scrypto v0.4.0 (builds against v0.4.1)
- Note: ResourceOf<> now takes the place of a ResourceAddress since ResourceDef no longer exists
- MSRV: 1.56.0 -> 1.60.0

## [0.4.1] - 2022-02-20
### Fixed
- Update non-runtime-checks variant of example/fixburn1
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "scrypto_statictypes"
version = "0.4.1"
rust-version = "1.56"
version = "0.5.0"
rust-version = "1.60"
authors = ["devmannic <[email protected]>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
Expand Down Expand Up @@ -39,11 +39,11 @@ nightly = [] # enables optimizations or features requiring nightly rust
runtime_typechecks = []

[dependencies]
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }

[dev-dependencies]
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }

[profile.release]
opt-level = 's' # Optimize for size.
Expand Down
57 changes: 30 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
![GitHub release (latest by date)](https://img.shields.io/github/v/release/devmannic/scrypto_statictypes?display_name=tag)
[![Crate](https://img.shields.io/badge/crates.io-on%20hold-orange)](https://crates.io/crates/scrypto_statictypes)
[![API](https://img.shields.io/badge/api-master-green.svg)](https://devmannic.github.io/scrypto_statictypes)
[![Scrypto version](https://img.shields.io/badge/scrypto-0.4.1-darkgreen.svg)](https://github.com/radixdlt/radixdlt-scrypto/releases/tag/v0.4.1)
[![Minimum rustc version](https://img.shields.io/badge/rustc-1.56+-darkgreen.svg)](https://github.com/devmannic/scrypto_statictypes#rust-version-requirements)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](https://github.com/devmannic/scrypto_statictypes#license)

Expand All @@ -16,8 +17,8 @@ A Scrypto (Rust) library for static types, featuring:
- Safe drop in replacements coexist with existing types. Gradually apply these only where you need them:
- `Bucket` --> `BucketOf<MYTOKEN>`
- `Vault` --> `VaultOf<MYTOKEN>`
- `ResourceDef` --> `ResourceOf<MYTOKEN>`
- `BucketRef` --> `BucketRefOf<MYTOKEN>`
- `ResourceAddress` --> `ResourceOf<MYTOKEN>`
- `Proof` --> `ProofOf<MYTOKEN>`
- Conveniently defined `XRD` Resource to use with `VaultOf<XRD>`, and friends.
- Simple macro to declare new resources: `declare_resource!(MYTOKEN)`
- Optional feature `runtime_typechecks` for safety critical code, or use in
Expand All @@ -29,12 +30,11 @@ It's also worth pointing out what `scrypto_statictypes` *is not*:
there are no resource mismatches, and it can do this all at compile time.
But, There is no completely static way to ensure proper usage of resources
received by a component. Any (incorrect) ABI may be used when calling a
function or method on a component. Transactions are inherently dynamic
with arguments referencing `ResourceDef`'s which are simply wrappers around
an `Address`. Runtime checks are still needed, and luckily are performed
function or method on a component. Transactions are inherently dynamic.
Runtime checks are still needed, and luckily are performed
by the Radix Engine. However, this library can still help avoid
implementation errors which would go undetected by the Radix Engine (ie.
burning the wrong type of resource).
logic errors manipulating the wrong type of resource).

## Usage

Expand Down Expand Up @@ -77,27 +77,31 @@ blueprint! {
// my_vault: Vault // the old way
my_vault: VaultOf<MYTOKEN> // the new way
}
pub fn new() -> Component {
let my_bucket = ResourceBuilder::new_fungible(DIVISIBILITY_MAXIMUM) // notice we didn't have to explicitly write out the type ie. Bucket<MYTOKEN>
pub fn new() -> ComponentAddress {
let my_bucket = ResourceBuilder::new_fungible() // notice we didn't have to explicitly write out the type ie. Bucket<MYTOKEN>
.metadata("name", "MyToken")
.metadata("symbol", "MYTOKEN")
.initial_supply_fungible(1000)
.initial_supply(1000)
.into(); // the new way: .into() needed to convert Bucket -> Bucket<MYTOKEN>
Self {
// my_vault: Vault::with_bucket(my_bucket) // the old way
my_vault: VaultOf::with_bucket(my_bucket) // the new way: use VaultOf instead of Vault
}
.instantiate()
.globalize()
}
// or even fewer changes when only setting up a vault.
pub fn new_easier() -> Component {
let my_bucket = ResourceBuilder::new() // this is a regular Bucket, but we're only using it to fill the vault
pub fn new_easier() -> ComponentAddress {
let my_bucket = ResourceBuilder::new_fungible() // this is a regular Bucket, but we're only using it to fill the vault
.metadata("name", "MyToken")
.metadata("symbol", "MYTOKEN")
.new_token_fixed(1000);
.initial_supply(1000);
Self {
// my_vault: Vault::with_bucket(my_bucket) // the old way
my_vault: Vault::with_bucket(my_bucket).into() // the new way: .into() needed to convert Vault -> VaultOf<MYTOKEN>
}
.instantiate()
.globalize()
}

// old way (or for when the resource type really is allowed to be anything, just keep using Bucket)
Expand All @@ -121,17 +125,17 @@ in a `Bucket` or `Vault`. This includes badges and NFTs.

That's just the beginning.... You can also replace:

`ResourceDef` -> `ResourceOf<MYTOKEN>`
`ResourceAddress` -> `ResourceOf<MYTOKEN>`

`BucketRef` -> `BucketRefOf<MYTOKEN>`
`Proof` -> `ProofOf<MYTOKEN>`

And with runtime checks enabled, you get a more
convenient and safe API for checks that used to be done with `#[auth(some_resource_def)]`. You can
use one or more `BucketRefOf` arguments to limit access without manual checks against
`some_resource_def`. Simply change the type of `some_resource_def` to `ResourceOf<SOMETHING>` in the Component struct. Or if you already have a `VaultOf<SOMETHING>` no new `ResourceOf` is needed.
convenient and safe API for doing "pass-by-intent" style Proof arguments. You can
use one or more `ProofOf` arguments to limit access without manual checks against
some `ResourceAddress`. Simply change the type from `ResourceAddress` to `ResourceOf<SOMETHING>` in the Component struct. Or if you already have a `VaultOf<SOMETHING>` no extra `ResourceOf` is needed.

To let the compiler help as much as possible the `BucketRefOf` type will automatically
drop the reference to its bucket when it goes out of scope. This works correctly when returning `BucketRefOf`s or calling other functions/methods. Never worry about explicitly calling `bucketref.drop()`.
To let the compiler help as much as possible the `ProofOf` type will automatically
drop the reference to its bucket when it goes out of scope. This works correctly when returning `ProofOf`s or calling other functions/methods. Never worry about explicitly calling `some_proof.drop()`.

These new types are usable everywhere: replace function/method argument and return types, local variables, fields in structs (including the main component storage struct). Everything just works.

Expand Down Expand Up @@ -159,7 +163,7 @@ Optionally, the following features can be enabled:
instantiate the component struct very early and decode a `Vid` into the `VaultOf<NAME>` which correctly binds
the address.
- Errors caught are trapped to the Radix Engine runtime failing the transaction immediately in
exactly the same way as when using `Bucket` or `Vault`, even with the exact same error as with a "bad" `Bucket::put` or `Vault::put`. Respectively `Err(InvokeError(Trap(Trap { kind: Host(BucketError(MismatchingResourceDef)) })))` and `Err(InvokeError(Trap(Trap { kind: Host(VaultError(AccountingError(MismatchingResourceDef))) })))`
exactly the same way as when using `Bucket` or `Vault`, even with the exact same error as with a "bad" `Bucket::put` or `Vault::put`. Respectively `Err(InvokeError(Trap(Trap { kind: Host(BucketError(MismatchingResourceManager)) })))` and `Err(InvokeError(Trap(Trap { kind: Host(VaultError(AccountingError(MismatchingResourceManager))) })))`


## Examples
Expand All @@ -169,14 +173,15 @@ See the directories in [/examples](/examples) for complete scrypto packages util
* [/examples/mycomponent](/examples/mycomponent) - Same example as in the [API reference (master branch)](https://devmannic.github.io/scrypto_statictypes) so you can easily try and see the compiler errors
* [/examples/badburn1](/examples/badburn1) - Example blueprint which does *NOT* use `scrypto_statictypes` and has a logic error which leads to burning the bucket argument even if it was the wrong asset
* [/examples/fixburn1](/examples/fixburn1) - Direct modification of `BadBurn` to use static types everywhere, and enable runtime type checks. The test case shows the "bad burn" is caught and the tx fails. -- checkout just the diff of changes in [/misc/bad2fixburn1.diff](/misc/bad2fixburn1.diff)
* [/examples/manyrefs](/examples/manyrefs) - Example using BucketRefOf a whole lot showing it's usefulness for nuanced authentication/verification
* [/examples/manyrefs](/examples/manyrefs) - Example using ProofOf a whole lot showing it's usefulness for nuanced authentication/verification

## Versions

Scrypto Static Types is suitable for general usage, but not yet at 1.0. We maintain compatibility with Scrypto and pinned versions of the Rust compiler (see below).

Latest Scrypto Static Types versions for Scrypto versions are:

- Version 0.5.0 depending on Scrypto version 0.4.1
- Version 0.4.1 depending on Scrypto version 0.3.0
- Version 0.3.1 depending on Alexandria Scrypto version 0.2.0
- Version 0.1.1 depending on Pre-Alexandria Scrypto version 0.1.1
Expand Down Expand Up @@ -206,7 +211,7 @@ issue tracker with the keyword `yank` *should* uncover the motivation.

### Rust version requirements

Since version 0.1, Scrypto Static Types requires **Rustc version 1.56 (2021 edition) or greater**.
Since version 0.5, Scrypto Static Types requires **Rustc version 1.60 (2021 edition) or greater**.

Continuous Integration (CI) will always test the minimum supported Rustc version
(the MSRV). The current policy is that this can be updated in any
Expand All @@ -218,10 +223,8 @@ I believe Radix will be a game-changing technology stack for Decentralized
Finance. Scrypto is already amazing and going to continue to evolve. I think
at this very early stage it does so many things right, however it's a missed
opportunity to treat all `Bucket`s and `Vault`s as dynamic types that could
hold anything, when in fact they are bound by their `ResourceDef` upon creation.
There is also a lot of duplicate code checking `BucketRef`s as the main form of
authentication when it could be done declaratively in more places than just the existing
auth macros. These omissions can lead
hold anything, when in fact they are bound by their held `ResourceAddress` upon creation.
There is also a lot of duplicate code to check `Proof`s which are often security critical when they are used for authentication. These omissions can lead
to entire classes of logic errors which could be avoided. This means
lost productivity at best, and real vulnerabilities at worst. I didn't want
development practices to standardize leaving these gaps.
Expand All @@ -236,7 +239,7 @@ gets us the rest of the way. It makes the usage seamless. And since
it's implemented with Rust's generics and `PhantomData` there is no extra
storage of the type information.

Then, going a step further I added runtime tests of the underlying `Address`es.
Then, going a step further I added runtime tests of the underlying `ResourceAddress`es.
This is behind a feature flag so is opt-in as it does add some extra
performance overhead. But, there are cases where it can absolutely detect a
logic error due to misuse of a Bucket or Vault, when the current Radix Engine
Expand Down
9 changes: 4 additions & 5 deletions examples/badburn1/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[package]
name = "badburn1"
version = "0.3.0"
version = "0.4.1"
edition = "2021"

[dependencies]
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }
scrypto = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }
scrypto_statictypes = { path = "../../" }

[dev-dependencies]
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.3.0" }
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "v0.4.1" }

[profile.release]
opt-level = 's' # Optimize for size.
Expand All @@ -19,6 +19,5 @@ panic = 'abort' # Abort on panic.

[lib]
crate-type = ["cdylib", "lib"]
name = "out"

[workspace]
44 changes: 26 additions & 18 deletions examples/badburn1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,59 @@ blueprint! {
// Define what resources and data will be managed by Hello components
flam_vault: Vault,
inflam_vault: Vault,
auth_def: ResourceDef,
auth_def: ResourceAddress,
minter: Vault,
}

impl BadBurn {
pub fn new() -> (Component, Bucket, Bucket) {
pub fn new() -> (ComponentAddress, Bucket, Bucket) {
// create one owner badge for auth for the burn_it functino
let minter = ResourceBuilder::new_fungible(DIVISIBILITY_NONE).initial_supply_fungible(1);
let owner = ResourceBuilder::new_fungible().divisibility(DIVISIBILITY_NONE).initial_supply(1);

// create 1 minter badge for 2 resources, FLAM and INFLAM
let owner = ResourceBuilder::new_fungible(DIVISIBILITY_NONE).initial_supply_fungible(1);
let minter = ResourceBuilder::new_fungible().divisibility(DIVISIBILITY_NONE).initial_supply(1);

// create FLAM and mint 1000
let mut flammable_bucket = ResourceBuilder::new_fungible(DIVISIBILITY_MAXIMUM)
let mut flammable_bucket = ResourceBuilder::new_fungible()
.metadata("name", "BurnMe")
.metadata("symbol", "FLAM")
.flags(MINTABLE | BURNABLE)
.badge(minter.resource_address(), MAY_MINT | MAY_BURN)
.initial_supply_fungible(1000);
.mintable(rule!(require(minter.resource_address())), LOCKED)
.burnable(rule!(require(minter.resource_address())), LOCKED)
.initial_supply(1000);

// create INFLAM and mint 1000
let inflammable_bucket = ResourceBuilder::new_fungible(DIVISIBILITY_MAXIMUM)
let inflammable_bucket = ResourceBuilder::new_fungible()
.metadata("name", "KeepMe")
.metadata("symbol", "INFLAM")
.flags(MINTABLE | BURNABLE)
.badge(minter.resource_address(), MAY_MINT | MAY_BURN)
.initial_supply_fungible(1000);
.mintable(rule!(require(minter.resource_address())), LOCKED)
.burnable(rule!(require(minter.resource_address())), LOCKED)
.initial_supply(1000);

// setup component storage
let flam_vault = Vault::with_bucket(flammable_bucket.take(800)); // FLAM: 800 stay here, 200 are returned
let c = Self {
flam_vault: flam_vault,
inflam_vault: Vault::with_bucket(inflammable_bucket), // all 1000 INFLAM stay here
auth_def: owner.resource_def(), // save this so we can use #[auth(auth_def)]
auth_def: owner.resource_address(), // save this so we can authorize calling burn_it()
minter: Vault::with_bucket(minter), // keep this so we can burn)
}
.instantiate();
.instantiate()
.add_access_check(
AccessRules::new()
.method("burn_it", rule!(require("auth_def")))
.default(rule!(allow_all))
)
.globalize();
(c, owner, flammable_bucket)
}

#[auth(auth_def)]
pub fn burn_it(&mut self, mut incoming: Bucket) -> Bucket {
// burn all but 5, give back same amount of inflam
if incoming.amount() > 5.into() {
self.flam_vault.put(incoming.take(5));
if incoming.amount() > dec!(5) {
self.flam_vault.put(incoming.take(dec!(5)));
}
let result = self.inflam_vault.take(incoming.amount());
self.minter.authorize(|auth| incoming.burn_with_auth(auth));
self.minter.authorize(|| incoming.burn());
result
}
}
Expand Down
Loading

0 comments on commit 1c7dce2

Please sign in to comment.