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
Changes from 4 commits
0d015de
92205bd
c80c6d8
962f513
5149fe9
528e6b2
27a9656
1ac7b65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } | ||
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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would consider using a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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))); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.