Skip to content

Commit

Permalink
feat: add topup redeemer logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Waffle committed Apr 25, 2024
1 parent b744b0a commit 425006d
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 45 deletions.
81 changes: 54 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,93 @@

### Parameter - Game settings and contsraint

- `MaxPlayers`: Int 2 to Int 9
- `MinBuyIn`: AdaInt
- `BigBlind`: AdaInt
- `SmallBlind`: AdaInt
- `TableDealer`: Also fee collector address
- `IsTablePrivateOrOpen`: Bool
- `TableDealer`: PubKeyHash
The only pubKeyHash able to sign leave table transactions.

### Datum

- `ActiveGame`:
- `ActivePlayersList`: [Player]
- `ActiveOpenGame`:

- `ActivePlayersList`: [Address]
- `TotalDeposit`: Int
- `MaxPlayers`: Int
- `MinBuyInLovelace`: Int

- `ActivePrivateGame`:
- `ActivePlayersList`: [Address]
- `TotalDeposit`: Int
- `MaxPlayers`: Int
- `MinBuyInLovelace`: Int
- `Player`: PlayerAddress && Balance
- `AuthorizedPlayers`: [PubKeyHash]

### Redeemer

- `JoinTable`: xxxxxxxxxxxxxxxxx
- `LeaveTable`: xxxxxxxxxxxxxxxxx
- `TopUpBalance`: xxxxxxxxxxxxxxxx
- `JoinTable`:

- Address
- DepositAmount

- `LeaveTable`:

- Address
- WithdrawalAmount

- `TopUpBalance`:
- Address
- DepositAmount

### User Action

1. A player creates a table - Each table is a validator script

- Sets big/small blind
- Sets Minimum buy-in
- Sets maximum amount of players (minimum 2, up to 9)
- Sets private/open table
- If private, set which addresses are allowed to join
- Note:
- Sets big/small blind
- Sets Minimum buy-in
- Sets maximum amount of players (minimum 2, up to 9)
- Sets private/open table
- If private, set which addresses are allowed to join

2. A player join the table - Redeemer `JoinTable`

- Script Context, looking at Transaction:

- Only 1 input UTXO from current script address
- Only 1 output to current script address
- Update Datum ActivePlayersList and TotalDeposit
- Check input values and output matches with supposed change in TotalDeposit.

- Check whether table is private or not
- If private, is player address authorized to join
-
- Check player address and balance
- If private, check if member in `AuthorizedPlayers` in datum
- Check that table is not full
- Check player deposit > Minimum buy-in
- Deposit ADA in validator
- Update Datum ActivePlayersList

- Note:
- Check player address and balance
- Build Tx:
- Player signs Tx
- Deposit ADA in validator

3. A player tops up balance - Redeemer `TopUpBalance`

- Check balance at player address
- Check that top up > minimum buy-in
- Build Tx:
- Player signs Tx
- Deposit ADA in validator
- Update Datum ActivePlayersList
- Update Datum ActivePlayersList and TotalDeposit
- Update in-game balance

- Note: Check balance at player address

4. A player leaves the table - Redeemer `LeaveTable`
- Check that player in active player list
- Check that player in activePlayer list
- Check player in-game balance
- Match in-game balance with transaction validation
- Update Datum ActivePlayersList and TotalDeposit
- Build Tx:
- Player signs Tx
- Admin signs Tx
- Withdraw ADA
- Update Datum ActivePlayersList

Note: Player does not need to sign `LeaveTable` Tx. This way, we can control the flow and kick inactive players out

### Notable Points

Expand Down
4 changes: 2 additions & 2 deletions aiken.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ source = "github"

[[requirements]]
name = "sidan-lab/aiken-utils"
version = "0.0.1-beta"
version = "0.0.4-beta"
source = "github"

[[packages]]
Expand All @@ -19,7 +19,7 @@ source = "github"

[[packages]]
name = "sidan-lab/aiken-utils"
version = "0.0.1-beta"
version = "0.0.4-beta"
requirements = []
source = "github"

Expand Down
2 changes: 1 addition & 1 deletion aiken.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ source = "github"

[[dependencies]]
name = "sidan-lab/aiken-utils"
version = "0.0.1-beta"
version = "0.0.4-beta"
source = "github"
210 changes: 195 additions & 15 deletions validators/poker_game.ak
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
use aiken/transaction.{ScriptContext, Spend}
use aiken/list
use aiken/transaction.{InlineDatum, ScriptContext, Spend, Transaction, find_input}
use aiken/transaction/credential.{Address}
use sidan_utils/inputs.{inputs_at}
use sidan_utils/outputs.{outputs_at}
use sidan_utils/value.{value_geq} as sidan_value
use aiken/transaction/value.{Value, add, lovelace_of}

type GameDatum {
ActiveGame {
ActiveOpenGame {
active_players_list: List<Address>,
total_deposit: Int,
max_players: Int,
min_buy_in_lovelace: Int,
}

ActivePrivateGame {
active_players_list: List<Address>,
authorized_players_list: List<Address>,
total_deposit: Int,
max_players: Int,
min_buy_in_lovelace: Int,
big_blind: Int,
small_blind: Int,
table_dealer: Address,
current_players_and_balance: List<Address>,
}
}

Expand All @@ -25,23 +36,192 @@ validator(fee_collector: Address) {
context: ScriptContext,
) {
let ScriptContext { purpose, transaction } = context
expect Spend(_) = purpose
let Transaction {inputs, reference_inputs, outputs, fee, redeemers, datums, ..} = transaction
expect Spend(utxo) = purpose
when redeemer is {

JoinTable { new_player } -> {
let is_min_buy_in_met = False
let is_table_full = False
let is_player_already_at_table = False
let is_datum_updated = False
expect Some(own_input) = find_input(inputs, utxo)
let own_address = own_input.output.address
when (inputs_at(inputs, own_address), outputs_at(outputs, own_address)) is {
([only_input_from_script], [only_output_to_script]) -> {
let input_value = only_input_from_script.output.value
let output_value = only_output_to_script.value

when datum is {
ActiveOpenGame {active_players_list, total_deposit, max_players, min_buy_in_lovelace} -> {

expect InlineDatum(output_inline_datum) = only_output_to_script.datum
expect ActiveOpenGame { .. } : GameDatum = output_inline_datum
expect parsed_output_datum : GameDatum = output_inline_datum
// let lovelace_input_value : Int = lovelace_of(input_value)
let lovelace_output_value : Int = lovelace_of(output_value)
// All conditions that must be satisfied
and {
does_table_have_seats(active_players_list, max_players),
is_player_already_at_table(active_players_list, new_player),
is_min_buy_in_met(min_buy_in_lovelace, input_value, output_value),
is_datum_updated_open(datum, parsed_output_datum, lovelace_output_value, new_player)
}
}

is_min_buy_in_met && is_room_having_vacancy && is_player_already_at_table
ActivePrivateGame{active_players_list, authorized_players_list, total_deposit, max_players, min_buy_in_lovelace} -> {
expect InlineDatum(output_inline_datum) = only_output_to_script.datum
expect ActivePrivateGame { .. } : GameDatum = output_inline_datum
expect parsed_output_datum : GameDatum = output_inline_datum
// let lovelace_input_value : Int = lovelace_of(input_value)
let lovelace_output_value : Int = lovelace_of(output_value)

// All conditions that must be satisfied
and {
is_player_authorized(authorized_players_list, new_player),
does_table_have_seats(active_players_list, max_players),
is_player_already_at_table(active_players_list, new_player),
is_min_buy_in_met(min_buy_in_lovelace, input_value, output_value),
is_datum_updated_private(datum, parsed_output_datum, lovelace_output_value, new_player)
}
}
}
}
_ -> False
}
}



TopUp { new_balance } -> {
expect Some(own_input) = find_input(inputs, utxo)
let own_address = own_input.output.address
when (inputs_at(inputs, own_address), outputs_at(outputs, own_address)) is {
([input], [output]) -> {
let input_value = input.output.value
let output_value = output.value

when datum is {
ActiveOpenGame {min_buy_in_lovelace, ..} -> {

expect InlineDatum(output_inline_datum) = output.datum
expect ActiveOpenGame { .. } : GameDatum = output_inline_datum
expect parsed_output_datum : GameDatum = output_inline_datum
// let lovelace_input_value : Int = lovelace_of(input_value)
let lovelace_output_value : Int = lovelace_of(output_value)
// All conditions that must be satisfied
and {
is_min_buy_in_met(min_buy_in_lovelace, input_value, output_value),
is_datum_updated_topup_open(datum, parsed_output_datum, lovelace_output_value, new_balance)
}
}

ActivePrivateGame{min_buy_in_lovelace, ..} -> {
expect InlineDatum(output_inline_datum) = output.datum
expect ActivePrivateGame { .. } : GameDatum = output_inline_datum
expect parsed_output_datum : GameDatum = output_inline_datum
// let lovelace_input_value : Int = lovelace_of(input_value)
let lovelace_output_value : Int = lovelace_of(output_value)

// All conditions that must be satisfied
and {
is_min_buy_in_met(min_buy_in_lovelace, input_value, output_value),
is_datum_updated_topup_private(datum, parsed_output_datum, lovelace_output_value, new_balance)
}
}
}
}
_ -> False
}
}


LeaveTable { player_to_remove } -> {
let is_player_removed = False
let is_money_returned = False
// let is_player_removed = False
// let is_money_returned = False

is_player_removed && is_money_returned
// is_player_removed && is_money_returned
False
}
}
}
}




///// Supporting functions for JoinTable redeemer

// Static checking

fn is_player_authorized(authorized_players_list, new_player) -> Bool {
list.has(authorized_players_list, new_player)
}

fn is_player_already_at_table(active_players_list, new_player) -> Bool {
list.has(active_players_list, new_player)
}

fn does_table_have_seats(active_players_list, max_players) -> Bool {
list.length(active_players_list) < max_players
}


// Dynamic checking
fn is_min_buy_in_met(min_buy_in_lovelace : Int, input_value: Value, output_value: Value) -> Bool {
let is_buy_in_provided = value_geq(output_value |> add("", "", min_buy_in_lovelace), input_value)
is_buy_in_provided
}

fn is_datum_updated_open(input_datum: GameDatum, output_datum: GameDatum, output_value: Int, new_player: Address) -> Bool {
// We are only updating the active_players_list and total_deposit
expect ActiveOpenGame {active_players_list: input_active_players_list, .. } : GameDatum = input_datum
expect ActiveOpenGame {active_players_list: output_active_players_list, total_deposit: output_total_deposit, ..} : GameDatum = output_datum


let is_new_player_added = list.length(input_active_players_list) + 1 == list.length(output_active_players_list)
&& list.has(output_active_players_list, new_player)

let is_total_deposit_updated = output_total_deposit == output_value
// && output_total_deposit - input_total_deposit == output_value - input_value

is_new_player_added && is_total_deposit_updated
}

fn is_datum_updated_private(input_datum: GameDatum, output_datum: GameDatum, output_value: Int, new_player: Address) -> Bool {
// We are only updating the active_players_list and total_deposit
expect ActivePrivateGame {active_players_list: input_active_players_list, ..} : GameDatum = input_datum
expect ActivePrivateGame {active_players_list: output_active_players_list, total_deposit: output_total_deposit, ..} : GameDatum = output_datum


let is_new_player_added = list.length(input_active_players_list) + 1 == list.length(output_active_players_list)
&& list.has(output_active_players_list, new_player)

let is_total_deposit_updated = output_total_deposit == output_value
// && output_total_deposit - input_total_deposit == output_value - input_value

is_new_player_added && is_total_deposit_updated
}


///// Supporting functions for TopUp redeemer
fn is_datum_updated_topup_open(input_datum: GameDatum, output_datum: GameDatum, output_value: Int, new_balance: Int) {
expect ActiveOpenGame { total_deposit: input_total_deposit, ..} : GameDatum = input_datum
expect ActiveOpenGame { total_deposit: output_total_deposit, ..} : GameDatum = output_datum

let is_total_deposit_updated = output_total_deposit == output_value
// && output_total_deposit - input_total_deposit == output_value - input_value
// && new_balance - output_value == input_value

is_total_deposit_updated
}

fn is_datum_updated_topup_private(input_datum: GameDatum, output_datum: GameDatum, output_value: Int, new_balance: Int) {
expect ActivePrivateGame {total_deposit: input_total_deposit, ..} : GameDatum = input_datum
expect ActivePrivateGame {total_deposit: output_total_deposit, ..} : GameDatum = output_datum

let is_total_deposit_updated = output_total_deposit == output_value
// && output_total_deposit - input_total_deposit == output_value - input_value
// && new_balance - output_value == input_value

is_total_deposit_updated
}


///// Supporting functions for LeaveTable redeemer

0 comments on commit 425006d

Please sign in to comment.