146 lines
5.2 KiB
Plaintext
146 lines
5.2 KiB
Plaintext
/**
|
|
* Example gym subscription contract
|
|
*
|
|
* Copyright (C) 2025, QPQ AG. All Rights Reserved.
|
|
*
|
|
* Owner can
|
|
* - run "tick" to collect monthly fees -> spent to owner
|
|
* - unsubscribe anyone
|
|
*
|
|
* Anyone can
|
|
* - subscribe for a period of N months (debit system - all money paid up front)
|
|
* - extend their subscription for K months
|
|
* - cancel their subscription and withdraw any future money
|
|
*
|
|
* Owner must issue new contract if he wants to change monthly fee; otherwise
|
|
* data structure is too fucking complicated.
|
|
*/
|
|
|
|
include "List.aes"
|
|
|
|
payable contract Gym =
|
|
type pucks = int
|
|
type n_months = int
|
|
record state = {owner : address,
|
|
members : map(address, n_months),
|
|
monthly_fee : pucks}
|
|
|
|
// SFP accessors
|
|
entrypoint
|
|
get_state : () => state
|
|
get_state() = state
|
|
stateful entrypoint
|
|
put_state : state => unit
|
|
put_state(s) = put(s)
|
|
|
|
entrypoint
|
|
is_member : address => bool
|
|
is_member(whomst) = Map.member(whomst, state.members)
|
|
|
|
entrypoint
|
|
owner : () => address
|
|
owner() = state.owner
|
|
|
|
entrypoint
|
|
members : () => map(address, pucks)
|
|
members() = state.members
|
|
|
|
entrypoint
|
|
monthly_fee : () => pucks
|
|
monthly_fee() = state.monthly_fee
|
|
|
|
|
|
// actual things
|
|
entrypoint
|
|
init : (pucks) => state
|
|
init(monthly_rate) =
|
|
require(monthly_rate >= 0, "monthly rate must be non-negative")
|
|
{owner = Call.caller,
|
|
members = {},
|
|
monthly_fee = monthly_rate}
|
|
|
|
//------------------------------------------------
|
|
// OWNER ENTRYPOINTS: tick/ban
|
|
//------------------------------------------------
|
|
|
|
stateful entrypoint
|
|
tick : () => unit
|
|
tick() | Call.caller != state.owner =
|
|
abort("you are not allowed to do ticks!")
|
|
tick() =
|
|
// collect fees
|
|
// 1 monthly charge per member
|
|
let total_charge = state.monthly_fee * Map.size(state.members)
|
|
Chain.spend(state.owner, total_charge)
|
|
|
|
// update members list (clean out everyone with just 1 month left)
|
|
let old_members_list : list(address * n_months) = Map.to_list(state.members)
|
|
let new_members_list : list(address * n_months) = List.foldl(deduct_month, [], old_members_list)
|
|
let new_members : map(address, n_months) = Map.from_list(new_members_list)
|
|
let new_state : state = state{members = new_members}
|
|
put(new_state)
|
|
|
|
function
|
|
deduct_month : (list(address*n_months), address*n_months) => list(address*n_months)
|
|
// n>1 months left -> subtract a month but keep in membership list
|
|
deduct_month(acc, (patron, months_left)) | months_left > 1 =
|
|
(patron, months_left-1) :: acc
|
|
// only 1 month left -> remove from membership list
|
|
deduct_month(acc, (patron, 1)) =
|
|
acc
|
|
|
|
stateful entrypoint
|
|
ban : (address) => unit
|
|
ban(_) | Call.caller != state.owner =
|
|
abort("you are not allowed to ban people!")
|
|
ban(patron) =
|
|
refund(patron)
|
|
|
|
|
|
//------------------------------------------------
|
|
// PUBLIC ENTRYPOINTS: subscribe/cancel
|
|
//------------------------------------------------
|
|
|
|
payable stateful entrypoint
|
|
subscribe : (n_months) => unit
|
|
subscribe(n) | n < 1 =
|
|
abort("must subscribe for at least 1 month")
|
|
subscribe(n) | Call.value < n*state.monthly_fee =
|
|
abort("not enough money to subscribe for that many months!")
|
|
subscribe(n) =
|
|
let charge: pucks = n * state.monthly_fee
|
|
// call will be successful
|
|
// refund caller extra money
|
|
let extra_money: pucks = Call.value - charge
|
|
Chain.spend(Call.caller, extra_money)
|
|
// update membership
|
|
let old_months : n_months = Map.lookup_default(Call.caller, state.members, 0)
|
|
let new_months : n_months = old_months + n
|
|
let new_members : map(address, n_months) = state.members{[Call.caller] = new_months}
|
|
let new_state : state = state{members = new_members}
|
|
put(new_state)
|
|
|
|
stateful entrypoint
|
|
cancel : () => unit
|
|
cancel() =
|
|
refund(Call.caller)
|
|
|
|
//------------------------------------------------
|
|
// INTERNAL: refund people
|
|
//------------------------------------------------
|
|
|
|
stateful function
|
|
refund : (address) => unit
|
|
refund(patron) =
|
|
let patron_months : n_months =
|
|
switch (Map.lookup(patron, state.members))
|
|
None => abort("already not a member!")
|
|
Some(remaining_months) => remaining_months
|
|
let patron_balance : pucks = patron_months * state.monthly_fee
|
|
// update membership
|
|
let new_members : map(address, n_months) = Map.delete(patron, state.members)
|
|
let new_state : state = state{members = new_members}
|
|
put(new_state)
|
|
// refund balance to patron
|
|
Chain.spend(patron, patron_balance)
|