/** * 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)