Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a toy example - rock's candy shop #2

Merged
merged 8 commits into from Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions toys/rocks-candy-shop/Cargo.toml
@@ -0,0 +1,21 @@
[package]
name = "rocks-candy-shop"
version = "0.1.0"
edition = "2018"

[dependencies]
sbor = { path = "/mnt/manygb/rock/dev/Radix/Scrypto/radixdlt-scrypto/radixdlt-scrypto/sbor" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use relative path in this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Usually I do that but in this case my local version of the community-scrypto repo is housed in a very different place than the radixdlt-scrypto such that the relative paths don't make much sense. I was unsure how to proceed and decided that it was safer to do it this way for now. I made a note to add some soft links, if possible, to make the relative path approach work in the usual way for this code and after I do that then I will adjust this file accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. To make this work I had to add 6 soft links in the directory 3 levels up. Of course that included sbor, scrypto and radix-engine as referenced in the Cargo.toml file, but also required were soft links to sbor-derive, scryto-abi and scrypto-derive as well.

scrypto = { path = "/mnt/manygb/rock/dev/Radix/Scrypto/radixdlt-scrypto/radixdlt-scrypto/scrypto" }

[dev-dependencies]
radix-engine = { path = "/mnt/manygb/rock/dev/Radix/Scrypto/radixdlt-scrypto/radixdlt-scrypto/radix-engine" }

[profile.release]
opt-level = 's' # Optimize for size.
lto = true # Enable Link Time Optimization.
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic.

[lib]
crate-type = ["cdylib", "lib"]
name = "out"
79 changes: 79 additions & 0 deletions toys/rocks-candy-shop/revup_initial_supply.json
@@ -0,0 +1,79 @@
{
"commands": [
{
"cmd": "reset",
"args": [],
"envs": []
},
{
"cmd": "new-account",
"args": [],
"envs": [
"account",
"pubkey"
]
},
{
"cmd": "new-account",
"args": [],
"envs": [
"account2",
"pubkey2"
]
},
{
"cmd": "new-token-fixed",
"args": [
"10000",
"--name",
"emunie",
"--symbol",
"EMT"
],
"envs": [
"tokenEMT"
]
},
{
"cmd": "new-token-fixed",
"args": [
"10000",
"--name",
"gmunie",
"--symbol",
"GMT"
],
"envs": [
"tokenGMT"
]
},
{
"cmd": "publish",
"args": [
"."
],
"envs": [
"package"
]
},
{
"cmd": "call-function",
"args": [
"$package",
"CandyShop",
"initial_supply",
"500"
],
"envs": [
"tokenGUM",
"tokenJAWB",
"tokenLPOP",
"tokenCANE",
"tokenJELLY",
"tokenMINT",
"tokenBEAR",
"component"
]
}
]
}
71 changes: 71 additions & 0 deletions toys/rocks-candy-shop/revup_new.json
@@ -0,0 +1,71 @@
{
"commands": [
{
"cmd": "reset",
"args": [],
"envs": []
},
{
"cmd": "new-account",
"args": [],
"envs": [
"account",
"pubkey"
]
},
{
"cmd": "new-account",
"args": [],
"envs": [
"account2",
"pubkey2"
]
},
{
"cmd": "new-token-fixed",
"args": [
"10000",
"--name",
"emunie",
"--symbol",
"EMT"
],
"envs": [
"tokenEMT"
]
},
{
"cmd": "new-token-fixed",
"args": [
"10000",
"--name",
"gmunie",
"--symbol",
"GMT"
],
"envs": [
"tokenGMT"
]
},
{
"cmd": "publish",
"args": [
"."
],
"envs": [
"package"
]
},
{
"cmd": "call-function",
"args": [
"$package",
"CandyShop",
"new"
],
"envs": [
"component"
]
}
]
}
119 changes: 119 additions & 0 deletions toys/rocks-candy-shop/src/lib.rs
@@ -0,0 +1,119 @@
use scrypto::prelude::*;

/* This a non-trivial example that shows off a blueprint that operates with
with an adjustable number of vaults. My main discover was that you can
return Vec<Bucket> and the resim methd call can handle it and update
the account correctly.

If someone wants to do so there are many possible extensions:
- add purchase method(s) via XRD
- have the purchase method(s) also return change
- add badges to control who can get the collected XRD
- allow for different prices for each type of candy
- allow candy restocking by a badged account (hint: mutable supply)
- tag the candy_vaults with the rri instead of the token symbol
- many more...
*/

blueprint! {
struct CandyShop {
// The different kinds of candies are kept here with each in a tuple
// with a unique tag string that doubles as the candies' token symbol.
candy_vaults: Vec<(String,Vault)>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider using a HashMap or LazyMap to make vault retrieval easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a separate example called Vending Machine that uses a HashMap of Vaults and so I think that I will leave this one as is just to show a different way to handle a requirement for dynamic Vaults. I will look into Option and fit that into an example somewhere as well. The empty_bucket approach shown is going to be a common idiom I would think simply because of it's simplicity.

}

impl CandyShop {

pub fn new() -> Component {
// This constructor sets up an empty candy shop
let tagged_vaults: Vec<(String,Vault)> = Vec::new();
Self {
candy_vaults: tagged_vaults
}
.instantiate()
}

pub fn initial_supply( supply_size: u32 ) -> Component {
// This constructor sets up a variety of candies with the specified amount.
let mut tagged_vaults: Vec<(String,Vault)> = Vec::new();
// Now define the meta data for each type of candy.
let mut metas = vec![("Gumball", "GUM", "The best gumball in the world.")];
metas.push(("Jawbreaker", "JAWB", "Jawbreakers teach patience."));
metas.push(("Lollipop","LPOP","You can't lick Lollipops!"));
metas.push(("Candy Cane", "CANE", "Striped candy rules!"));
metas.push(("Jelly Bean", "JELLY", "Jelly Beans are best!"));
metas.push(("Mint Candy", "MINT", "Mints are wonderful!"));
metas.push(("Gummy Bear", "BEAR", "Gummy Bears rules!"));
// Create a supply
for tup in metas {
let bucket = ResourceBuilder::new()
.metadata("name", tup.0.to_string())
.metadata("symbol", tup.1.to_string())
.metadata("description", tup.2.to_string())
.new_token_fixed(supply_size);
tagged_vaults.push((tup.1.to_string(), Vault::with_bucket(bucket)));
}
Self {
candy_vaults: tagged_vaults
}
.instantiate()
}

fn take_from_vault(&self, symbol: String, quantity: Decimal) -> Bucket {
// private function returns a bucket with the specified number and type of candy (or an empty bucket)
for c in &self.candy_vaults[..] {
if c.0 == symbol {
let v = &c.1;
if v.amount() >= quantity {
return v.take(quantity)
} else {
break;
}
}
}
let empty_bucket: Bucket = Bucket::new(RADIX_TOKEN); // canonical way to make an empty_bucket
return empty_bucket
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting case where the bucket "type" is dynamic and sometimes the method doesn't even want to return a bucket.

I would consider returning an Option<Bucket>, but this introduces complexity from an interface perspective.


pub fn free_gum(&self) -> Bucket {
// Return a gumball if we have at least one available.
// If there is no GUM vault or the GUM vaut is empty, this method will fail.
self.take_from_vault("GUM".to_string(), 1.into())
}

pub fn free_samples(&mut self) -> Vec<Bucket> {
let mut buckets = Vec::new();
for c in &self.candy_vaults[..] {
if c.1.amount() > 0.into() {
buckets.push(c.1.take(1));
}
}
return buckets
}

fn contains(&self, symbol: &str) -> bool {
// return True if the symbol is found in the candy_vaults based on the token symbol
let mut found: bool = false;
let symbol_String = symbol.to_string();
for c in &self.candy_vaults[..] {
if c.0 == symbol_String {
found = true;
break;
}
}
return found
}

pub fn add_candy(&mut self, name: String, symbol: String, description: String, supply_size: Decimal) {
scrypto_assert!(supply_size >= 1.into(), "Not enough initial candy");
scrypto_assert!(self.contains(&symbol) == false, "That type of candy is already available.");
// Add a new kind of candy to the CandyShop
let bucket = ResourceBuilder::new()
.metadata("name", name)
.metadata("symbol", symbol.to_string())
.metadata("description", description)
.new_token_fixed(supply_size);
self.candy_vaults.push((symbol, Vault::with_bucket(bucket)));
}
}
}
34 changes: 34 additions & 0 deletions toys/rocks-candy-shop/tests/lib.rs
@@ -0,0 +1,34 @@
use radix_engine::ledger::*;
use radix_engine::transaction::*;
use scrypto::prelude::*;

#[test]
fn test_hello() {
// Set up environment.
let mut ledger = InMemoryLedger::with_bootstrap();
let mut executor = TransactionExecutor::new(&mut ledger, 0, 0);
let key = executor.new_public_key();
let account = executor.new_account(key);
let package = executor.publish_package(include_code!());

// Test the `new` function.
let transaction1 = TransactionBuilder::new(&executor)
.call_function(package, "Hello", "new", vec![], None)
.build(vec![key])
.unwrap();
let receipt1 = executor.run(transaction1, false).unwrap();
println!("{:?}\n", receipt1);
assert!(receipt1.success);

// Test the `free_token` method.
let component = receipt1.component(0).unwrap();
let transaction2 = TransactionBuilder::new(&executor)
.call_method(component, "free_token", vec![], Some(account))
.drop_all_bucket_refs()
.deposit_all_buckets(account)
.build(vec![key])
.unwrap();
let receipt2 = executor.run(transaction2, false).unwrap();
println!("{:?}\n", receipt2);
assert!(receipt2.success);
}