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 all 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 basic/utility_token_service/Cargo.toml
@@ -0,0 +1,21 @@
[package]
name = "utility-token-service"
version = "0.1.0"
edition = "2018"

[dependencies]
sbor = { path = "../../../sbor" }
scrypto = { path = "../../../scrypto" }

[dev-dependencies]
radix-engine = { path = "../../../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"
80 changes: 80 additions & 0 deletions basic/utility_token_service/revup.json
@@ -0,0 +1,80 @@
{
"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",
"UtilityTokenFactory",
"new",
"Rock",
"RadQuilders",
"RGT",
"token of merit for RadGuild Members",
"5",
"50",
"25"
],
"envs": [
"badgeUTF",
"tokenRGT",
"utf_component"
]
}
]
}
4 changes: 4 additions & 0 deletions basic/utility_token_service/src/lib.rs
@@ -0,0 +1,4 @@
// Here we declare all of the modules that should be included into this package.

mod util_token_fac;
mod service_stub;
100 changes: 100 additions & 0 deletions basic/utility_token_service/src/service_stub.rs
@@ -0,0 +1,100 @@
use scrypto::prelude::*;

/*
This blueprint is an example of wrapping another component - UtilityTokenFactory.

No actual service is provided but two stub services (regular and premium) are demoed.
*/

use crate::util_token_fac::UtilityTokenFactory;

blueprint! {
struct ServiceStub {
utf: UtilityTokenFactory,
used_tokens: Vault,
simple_service_count: u32,
premium_service_count: u32
}

impl ServiceStub {

// Create a UtilityTokenFactory using the simulator and pass it into this constructor.
// See the README.md file for an example of how to do this.
//
pub fn new( comp:Address ) -> Component {
let my_utf: UtilityTokenFactory = UtilityTokenFactory::from(comp);
let my_utf_address = my_utf.address();
Self {
utf: my_utf,
used_tokens: Vault::new(ResourceDef::from(my_utf_address)),
simple_service_count: 0,
premium_service_count: 0
}
.instantiate()
}

// Send the collected UT coins out to be burned.
// Not required to do this often but letting the vault get too big is a mild safety concern.
//
fn maybe_redeem(&mut self) {
if self.used_tokens.amount() > 100.into() {
self.utf.redeem( self.used_tokens.take_all())
};
}

// Show how many services have been delivered.
//
pub fn show(&self) { // Ideally this function would be secured by a badge here.
info!("Simple Services performed: {}", self.simple_service_count);
info!("Premium Services performed: {}", self.premium_service_count);
// Ideally we could safely call show() on the UTF too.
}

// Now we define our services
pub fn simple_service(&mut self, payment: Bucket) -> Bucket {
scrypto_assert!(payment.resource_def().address() == self.utf.address(), "Simple service requires 1 util token");
scrypto_assert!(payment.amount() >= 1.into(), "Simple service requires 1 util token");
scrypto_assert!(self.utf.address() == self.used_tokens.resource_def().address(), "Mismatch in Vault setup.");
self.used_tokens.put(payment.take(1));
info!("Performing Simple Service now.");
self.simple_service_count += 1;
self.maybe_redeem();
payment // return the user's change (if any)
}

pub fn premium_service(&mut self, payment: Bucket) -> Bucket {
scrypto_assert!(payment.resource_def().address() == self.utf.address(), "Premium service requires 3 util tokens");
scrypto_assert!(payment.amount() >= 3.into(), "Premium service requires 3 util tokens");
self.used_tokens.put(payment.take(3));
info!("Performing Premium Service now.");
self.premium_service_count += 1;
self.maybe_redeem();
payment // return the user's change (if any)
}


// The original constructur shown below used these hard-coded values.
// const MAX_BUY: u32 = 100; // Limit users to purchasing 100 tokens at a time.
// const UTIL_TOKEN_NAME: &str = "My Service";
// const UTIL_TOKEN_SYMBOL: &str = "STUB";
// const UTIL_TOKEN_DESCRIPTION: &str = "Do nothing service";

/* This was the original constructor that was used to bootstrap this bluerint.
// It worked fine but it is not particularly flexible and was deemd to be inferior.
// Left here for the sake of posterity.
//
pub fn new() -> Component {
// The new func takes: name, symbol, description, price (in XRD), mint_size, max_buy
let my_utf: UtilityTokenFactory = UtilityTokenFactory::new(UTIL_TOKEN_NAME.to_string(), UTIL_TOKEN_SYMBOL.to_string(), UTIL_TOKEN_DESCRIPTION.to_string(), 5, 500, MAX_BUY).into();
let my_utf_address = my_utf.address();
Self {
utf: my_utf,
used_tokens: Vault::new(ResourceDef::from(my_utf_address)),
simple_service_count: 0,
premium_service_count: 0
}
.instantiate()
}
*/
}
}
117 changes: 117 additions & 0 deletions basic/utility_token_service/src/util_token_fac.rs
@@ -0,0 +1,117 @@
use scrypto::prelude::*;

/*
A blueprint for creating and managing a utility token.
*/

blueprint! {
struct UtilityTokenFactory {
ut_minter_vault: Vault,
ut_minter_badge: ResourceDef,
available_ut: Vault, // The available supply of utility tokens which are used to pay for some services.
ut_token_price: Decimal, // How many XRD the UT Tokens cost. (Fractional amounts are not recommended at this time.)
collected_xrd: Vault, // proceeds from selling UT tokens
ut_max_buy : u32, // Maximum number of UT tokens that can be purchased at a time.
ut_mint_size: u32, // How many UT tokens to mint in each batch.
total_claimed: Decimal, // Total XRD claimed during the contract lifetime.
total_minted: u32, // How many utility tokens have been minted.
total_redeemed: Decimal, // How many utility tokens have been redeemed and burned.
}

impl UtilityTokenFactory {

pub fn new( my_id: String, ut_name: String, ut_symbol: String, ut_description: String, price: u32, mint_size: u32, max_buy: u32 ) -> (Component, Bucket) {
let ut_minter_bucket = ResourceBuilder::new()
.metadata("name", my_id)
.new_badge_fixed(2);
let ut_minter_resource_def = ut_minter_bucket.resource_def();
let ut_minter_return_bucket: Bucket = ut_minter_bucket.take(1); // Return this badge to the caller

let ut_resource_def = ResourceBuilder::new()
.metadata("name", ut_name)
.metadata("symbol", ut_symbol)
.metadata("description", ut_description)
.new_token_mutable(ut_minter_bucket.resource_address());
let ut_tokens = ut_resource_def.mint(mint_size, ut_minter_bucket.borrow());

scrypto_assert!(mint_size > 0, "You must specify a non-zero number for the mint_size.");
scrypto_assert!(max_buy <= mint_size, "The single purchase max buy size should be less than or equal to the mint size.");
let component = Self {
ut_minter_vault: Vault::with_bucket(ut_minter_bucket),
ut_minter_badge: ut_minter_resource_def,

available_ut: Vault::with_bucket(ut_tokens),
ut_token_price: price.into(),
collected_xrd: Vault::new(RADIX_TOKEN),

ut_max_buy: max_buy,
ut_mint_size: mint_size,

total_claimed: 0.into(),
total_minted: mint_size,
total_redeemed: 0.into()
}
.instantiate();
(component, ut_minter_return_bucket)
}

// Convenience function returns the address of the Utility Token
//
pub fn address(&self) -> Address {
self.available_ut.resource_def().address()
}

// Purchase UT tokens
//
pub fn purchase(&mut self, number: u32, payment: Bucket) -> (Bucket, Bucket) {
scrypto_assert!(payment.resource_def() == RADIX_TOKEN.into(), "You must purchase the utility tokens with Radix (XRD).");
let ut_bucket = Bucket::new(self.address());
let mut num = number;
if num > self.ut_max_buy {
num = self.ut_max_buy; // 1,000 is the max allowable purchase for now.
info!("A max of {} tokens can be purcahsed at a time.", self.ut_max_buy);
}
if payment.amount() < self.ut_token_price * num {
info!("Insufficient funds. Required payment for {} UT tokens is {} XRD.", num, self.ut_token_price * num);
} else {
info!("Thank you!");
if self.available_ut.amount() < num.into() { // if they are needed, mint more UT tokens
let new_tokens = self.ut_minter_vault.authorize(|badge| {
self.available_ut.resource_def().mint(self.ut_mint_size, badge)
});
self.available_ut.put(new_tokens);
self.total_minted += self.ut_mint_size;
}
self.collected_xrd.put(payment.take(self.ut_token_price * num));
ut_bucket.put(self.available_ut.take(num));
}
(payment, ut_bucket)
}

#[auth(ut_minter_badge)]
pub fn show_bank(&self) {
let metadata = self.available_ut.resource_def().metadata();
info!("Available {}: {}", metadata["symbol"], self.available_ut.amount());
info!("Claimable XRD: {}", self.collected_xrd.amount());
info!("Total XRD Claimed: {}", self.total_claimed);
info!("Total {} Minted: {}", metadata["symbol"], self.total_minted);
info!("Total {} Redeemed: {}", metadata["symbol"], self.total_redeemed);
}

#[auth(ut_minter_badge)]
pub fn claim(&mut self) -> Bucket {
self.total_claimed += self.collected_xrd.amount();
self.collected_xrd.take_all()
}

pub fn redeem(&mut self, used_tokens: Bucket) {
if used_tokens.amount() > 0.into() {
scrypto_assert!(used_tokens.resource_def() == self.available_ut.resource_def(), "You can only redeem the expected utility tokens.");
self.total_redeemed += used_tokens.amount();
self.ut_minter_vault.authorize(|badge| {
used_tokens.burn(badge);
})
}
}
}
}
34 changes: 34 additions & 0 deletions basic/utility_token_service/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);
}
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 = "../../../sbor" }
scrypto = { path = "../../../scrypto" }

[dev-dependencies]
radix-engine = { path = "../../../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"