Ex 6a. IOTA Rebased Contract - Sevens

unofficial site

- return to IOTA Rebased Useful Links

[1] Sevens Contract Development

The concept is a simple betting game of forecasting the seven 'heads/tails' combination that the contract produces. It uses the IOTA Rebased Clock to generate some level of randomness.

Basic contract preparation

In a folder called 'sevens' there is a 'source' folder containing sevens.move, and a Move.toml file.

First build ...


        % iota move build 

Then publish ...

   
iota client publish \
--gas-budget 60000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922
        

[2] Key contract parameters

Transaction Digest: 4AN3LyoURzPbgK2ReMLRAyxBkuP4B1GTgJ5nk23z2pnp
Sender: 0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a
PackageID: 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7
UpgradeCap: 0x7d171a1cd6f2a45c7ddafd610504d920a6975da33a0584e5f8a0c093fd349c32

Initialisation


iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function initialize \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7
    

After initialisation we get

Transaction Digest: BiuP2tPbXKPsCQMfZhoAoLzbcqvrbzDniUWuB64TMs6e
GlobalConfig: 0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692
(has fields: initialized, bet_size and test_mode)
AdminCap: 0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882
The AdminCap is an object owned by sender address. Move's ownership system enforces only owner can use their objects.

[3] Setup Call


iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function setup \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692"
    

Transaction Digest: 4jJ4AkLxQGYEpGT1F6QitfBvLCFAYnk7VzUEZd7yau5u
Treasury: 0x7f3adc8be31df99ee346e54f2765b40372d11c7f83a32f8908f431ca377ca95b

[4] Funding treasury


iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function add_funds \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x7f3adc8be31df99ee346e54f2765b40372d11c7f83a32f8908f431ca377ca95b" \
"0x88688bee8b3bbb26021d7ce9c3b865e7908961eb79fbf2e9f3010c11fa72a795"
    

Transaction Digest: D5wxZs4f9tgRRSNbCJyGopkZfDHWorD78yXM9BG3C7yd

[5] Put into Test Mode


iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function toggle_test_mode \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692"
    

Transaction Digest: F1qJbeyeomy3nFLeKDY2iKPp4CXkM7GAzzigv5mWrut7

Playing a round


iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function play \
--module game \
--package 0x6be8f571710d2c8f9729cab672e0ba9b1e587ed022ad50caee2f43378027c053 \
--args \
"0x2bcc0bb8debc70afed41230609a7bef61d7828a918237d3c95860e6cbfbaa3ba" \
"0x519c01c5974ca6bdd54afde788bd426fe957c7412ee9518da5403db7fff5cbac" \
"0x6" \
"true" "false" "true" "false" "true" "false" "true" \
"0x3ba7de8981bbaa90f8040579c1dea10ed93b96d35768734a0d49d3960a0f774c"

    

Transaction Digests: (test mode)
C7MhiFhwaVLwzbHmbaCLskf9nUpyvDbaTz5di8Ajbca3 (lose)
BcYYH2zvxofNs2iD14EYNo7vKaxbM4qu9ATHW7RaGdDv (lose)
AEBSrouA9JRGEtwFtQWEuJF4TYmxgBNpnCYN8xREzC6W (win 1 IOTA)

[6] A result from the call

This is Test Mode so need just three correct guesses, as in this example


{
  "id": {
    "txDigest": "AEBSrouA9JRGEtwFtQWEuJF4TYmxgBNpnCYN8xREzC6W",
    "eventSeq": "0"
  },
  "packageId": "0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7",
  "transactionModule": "game",
  "sender": "0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a",
  "type": "0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7::game::GameResult",
  "parsedJson": {
    "amount_wagered": "10000000",
    "clock_digits": [
      2,
      3,
      9,
      8,
      9,
      0,
      8
    ],
    "clock_value": "1735498098932",
    "contract_sequence": [
      72,
      72,
      84,
      84,
      84,
      84,
      72
    ],
    "matches": "3",
    "payout": "1000000000",
    "player": "0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a",
    "player_sequence": [
      72,
      72,
      84,
      84,
      72,
      84,
      72
    ],
    "random_digits": [
      2,
      9,
      6,
      3,
      0,
      7,
      4
    ]
  },
  "bcs": "29gpzUQbBnPVPkY7YoF4FDJzfX94ztFEYbfCXmAcy2djhF3An37QyoF5NC6F1ArPrnvEAhbedC97gFsDgAjctf7pwqTXosCfybwa3Dt731Bzgj2wPE16QEYF5iMHUvXtXWxP",
  "timestampMs": "1735498098932"
}

[7] The code for sevens.move


module sevens::game {
    use iota::coin::{Self, Coin};
    use iota::event;
    use iota::balance::{Self, Balance};
    use iota::clock::{Self, Clock};
    use iota::iota::IOTA;

    // ======== Capability and Config ========
    public struct AdminCap has key, store {
        id: UID
    }

    public struct GlobalConfig has key {
        id: UID,
        initialized: bool,
        bet_size: u64,
        test_mode: bool
    }

    // ======== Treasury ========
    public struct Treasury has key, store {
        id: UID,
        funds: Balance
    }

    // ======== Game Result Event ========
    public struct GameResult has copy, drop {
        player: address,
        player_sequence: vector,
        contract_sequence: vector,
        clock_value: u64,
        clock_digits: vector,
        random_digits: vector,
        amount_wagered: u64,
        payout: u64,
        matches: u64
    }

    // ======== Error Codes ========
    const ALREADY_INITIALIZED: u64 = 1;
    const INSUFFICIENT_FUNDS: u64 = 2;
    const INVALID_SEQUENCE: u64 = 3;

    // ======== Constants ========
    const SEQUENCE_LENGTH: u64 = 7;
    const TEST_SEQUENCE_LENGTH: u64 = 3;
    const PERFECT_MATCH_MULTIPLIER: u64 = 100;
    const ZERO_MATCH_MULTIPLIER: u64 = 20;
    const DEFAULT_BET_SIZE: u64 = 10000000; // 0.01 IOTA

    // ======== Initialize System ========
    public entry fun initialize(ctx: &mut TxContext) {
        transfer::share_object(
            GlobalConfig {
                id: object::new(ctx),
                initialized: false,
                bet_size: DEFAULT_BET_SIZE,
                test_mode: false
            }
        );

        transfer::public_transfer(
            AdminCap {
                id: object::new(ctx)
            },
            tx_context::sender(ctx)
        );
    }

    // ======== Setup Game System ========
    public entry fun setup(
        _admin_cap: &AdminCap,
        config: &mut GlobalConfig,
        ctx: &mut TxContext
    ) {
        assert!(!config.initialized, ALREADY_INITIALIZED);
        
        let treasury = Treasury {
            id: object::new(ctx),
            funds: balance::zero()
        };

        config.initialized = true;
        transfer::share_object(treasury);
    }

    // ======== Admin Functions ========
    public entry fun set_bet_size(
        _admin_cap: &AdminCap,
        config: &mut GlobalConfig,
        new_bet_size: u64
    ) {
        config.bet_size = new_bet_size;
    }

    public entry fun toggle_test_mode(
        _admin_cap: &AdminCap,
        config: &mut GlobalConfig
    ) {
        config.test_mode = !config.test_mode;
    }

    public entry fun add_funds(
        _admin_cap: &AdminCap,
        treasury: &mut Treasury,
        coin: Coin,
        _ctx: &mut TxContext
    ) {
        balance::join(&mut treasury.funds, coin::into_balance(coin));
    }

    public entry fun withdraw_funds(
        _admin_cap: &AdminCap,
        treasury: &mut Treasury,
        amount: u64,
        ctx: &mut TxContext
    ) {
        let treasury_balance = balance::value(&treasury.funds);
        assert!(treasury_balance >= amount, INSUFFICIENT_FUNDS);
        
        let withdrawal = coin::from_balance(
            balance::split(&mut treasury.funds, amount),
            ctx
        );
        transfer::public_transfer(withdrawal, tx_context::sender(ctx));
    }

    // ======== Play Game ========
    public entry fun play(
        treasury: &mut Treasury,
        config: &GlobalConfig,
        clock: &Clock,
        pos1: bool, pos2: bool, pos3: bool, pos4: bool, pos5: bool, pos6: bool, pos7: bool,
        bet: Coin,
        ctx: &mut TxContext
    ) {
        let player = tx_context::sender(ctx);
        let bet_amount = coin::value(&bet);
        
        // Verify bet amount matches configured size
        assert!(bet_amount == config.bet_size, INVALID_SEQUENCE);
        
        // Convert boolean sequence to vector (H=72, T=84)
        let mut player_sequence = vector::empty();
        vector::push_back(&mut player_sequence, if (pos1) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos2) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos3) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos4) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos5) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos6) 72 else 84);
        vector::push_back(&mut player_sequence, if (pos7) 72 else 84);
        
        // Add bet to treasury
        balance::join(&mut treasury.funds, coin::into_balance(bet));
        
        // Generate contract sequence using new randomization method
        let clock_value = clock::timestamp_ms(clock);
        let (contract_sequence, clock_digits, random_digits) = generate_sequence(clock_value);
        
        // Count matches
        let matches = count_matches(&player_sequence, &contract_sequence, config.test_mode);
        
        // Calculate payout
        let payout = calculate_payout(matches, bet_amount, config.test_mode);
        
        // Process payout if any
        if (payout > 0) {
            let treasury_balance = balance::value(&treasury.funds);
            assert!(treasury_balance >= payout, INSUFFICIENT_FUNDS);
            
            let payment = coin::from_balance(
                balance::split(&mut treasury.funds, payout),
                ctx
            );
            transfer::public_transfer(payment, player);
        };
        
        // Emit game result event
        event::emit(GameResult {
            player,
            player_sequence,
            contract_sequence,
            clock_value,
            clock_digits,
            random_digits,
            amount_wagered: bet_amount,
            payout,
            matches
        });
    }

    // ======== Helper Functions ========
    fun generate_sequence(clock_value: u64): (vector, vector, vector) {
        // Extract last 7 digits from clock
        let mut clock_digits = vector::empty();
        let mut temp_clock = clock_value;
        let mut i = 0;
        while (i < SEQUENCE_LENGTH) {
            let digit = ((temp_clock % 10) as u8);
            vector::push_back(&mut clock_digits, digit);
            temp_clock = temp_clock / 10;
            i = i + 1;
        };
        
        // Generate random digits
        let seed = clock_value % 10000000;  // Last 7 digits for seed
        let mut random_digits = vector::empty();
        let mut i = 0;
        while (i < SEQUENCE_LENGTH) {
            let digit = (((seed + (i * 17)) % 10) as u8);
            vector::push_back(&mut random_digits, digit);
            i = i + 1;
        };
        
        // Generate sequence based on digit parity comparison
        let mut sequence = vector::empty();
        let mut i = 0;
        while (i < SEQUENCE_LENGTH) {
            let clock_digit = *vector::borrow(&clock_digits, i);
            let random_digit = *vector::borrow(&random_digits, i);
            
            let clock_even = (clock_digit % 2) == 0;
            let random_even = (random_digit % 2) == 0;
            let is_heads = clock_even == random_even;
            
            vector::push_back(&mut sequence, if (is_heads) 72 else 84);
            i = i + 1;
        };

        (sequence, clock_digits, random_digits)
    }

    fun count_matches(player_seq: &vector, contract_seq: &vector, test_mode: bool): u64 {
        let mut matches = 0;
        let length = if (test_mode) { TEST_SEQUENCE_LENGTH } else { SEQUENCE_LENGTH };
        let mut i = 0;
        while (i < length) {
            if (*vector::borrow(player_seq, i) == *vector::borrow(contract_seq, i)) {
                matches = matches + 1;
            };
            i = i + 1;
        };
        matches
    }

    fun calculate_payout(matches: u64, bet_amount: u64, test_mode: bool): u64 {
        let required_matches = if (test_mode) { TEST_SEQUENCE_LENGTH } else { SEQUENCE_LENGTH };
        
        if (matches == required_matches) {
            bet_amount * PERFECT_MATCH_MULTIPLIER
        } else if (matches == 0) {
            bet_amount * ZERO_MATCH_MULTIPLIER
        } else {
            0
        }
    }
}