<Let's Move>我的SUI Move挑战合约——【Dacade】幸运咖啡馆(Lucky Cafe)

  • rzexin
  • 更新于 2024-03-29 10:35
  • 阅读 1660

Dacade平台我的SUI Move挑战合约——幸运咖啡馆(LuckyCafe)

Dacade平台我的SUI Move挑战合约——幸运咖啡馆(Lucky Cafe)

2024.03.02 rzexin

1 前言

本文是参加Dacade挑战提交的SUI Move合约的整体介绍,希望对SUI Move的初学者们了解开发和测试流程有所帮助。本人同样也是Move的初学者,合约中可能会存在错误,欢迎大家指正。

2 产品构思

通过一段时间对Move语言的学习,对Move合约开发有了初步的了解。但一直没有机会实际去练手。刚好通过Move中文社区,了解到Dacade平台有了SUI Move合约开发技术的学习资料和有奖挑战。于是想以此为契机,实战开发一下Move合约,加深对语法知识的掌握。

浏览了部分已经提交代码的挑战者合约,看到涉及各种场景,似乎挑战对提交的合约类型没有任何限制,可以随意发挥。于是我便打算胡造一个场景,打算尽可能多的把所学的Move语法知识都应用上,强化实际使用经验。

但到底写什么合约,一直没有确定。就在一天晚上,在给女儿读她们最喜欢的故事书《屁屁侦探》时,故事中有一个场景是“幸运猫咖啡馆”,于是创造一个 幸运咖啡馆(Lucky Cafe) 合约的想法便应运而生了。

3 方案设计

定了幸运咖啡馆这个合约主题后,接下来就是明确有哪些实体、实体之间的关系、以及经济模型如何。

3.1 实体定义

  • 咖啡店(Cafe
  • 咖啡卡(Card
  • 咖啡(Coffee
  • 店主(OwnerAdmin
  • 顾客

3.2 实体关系

  • 任何用户都可以创建咖啡店(Cafe),成为店主(OwnerAdmin
  • 顾客可以购买咖啡卡(Card
  • 咖啡卡(Card)可以1:1兑换咖啡(Coffee

3.3 经济模型设计

  • 每5 GAS可购买一张咖啡卡,购买满两张送一张,即:10 GAS可以购买3张咖啡卡,以此类推
  • 一张咖啡卡可以兑换一杯咖啡,兑换咖啡后,这张咖啡卡失效
  • 每张咖啡卡都拥有一个唯一的幸运数字(Lucky Number),任何人都可以触发合约接口随机抽取一个幸运数字,若挑选的幸运数字对应的咖啡卡已经被使用,将可重新抽取幸运数字
  • 幸运数字对应咖啡卡的持有人,可以凭该幸运数字对应的咖啡卡,获取奖励。奖励就是使得自己手上咖啡卡的数量翻倍,单次奖励上限为10张咖啡卡。例如:Alice持有咖啡卡4张,其中一张咖啡卡的幸运数字是7,当随机挑选的幸运数字也是7时,那么好运发生,Alice可以销毁这张幸运咖啡卡,将手上持有咖啡卡的数量翻倍。
  • 随机幸运数字抽取后,直到被对应的咖啡卡兑换前,将不得再次抽取。但店主有管理员权限,可以将该随机产生的幸运数字删除,以便可以重新挑选新的幸运数字,避免有客户长时间不去领取奖励。

4 合约开发

4.1 创建工程

$ sui move new bityoume_sui_move_lucky_cafe

4.2 定义模型结构

(1)咖啡馆(Cafe

    // represents a cafe object
    struct Cafe has key, store {
        // uid of the cafe object
        id: UID,
        // balance of the cafe object
        sui: Balance&lt;SUI>,
        // number of participants in the cafe
        participants: u64,
        // owner of the cafe object
        owner: address,
        // lucky number of the winner of the cafe object
        winner_lucky_number: Option&lt;u64>,
        // base drand round of the cafe object
        base_drand_round: u64,
        // lucky number to owner mapping
        lucky_number_2_owner: VecMap&lt;u64, address>,
        // lucky number to card id mapping
        lucky_number_2_card_id: VecMap&lt;u64, ID>,
        // owner to card count mapping
        owner_2_card_count: VecMap&lt;address, u64>,
    }

(2)咖啡卡(Card

    //===============================
    //        Module Structs
    //===============================
    // one Card can only purchase one cup of coffee
    struct Card has key, store {
        // uid of the card object
        id: UID,
        // id of the cafe object
        cafe_id: ID,
        // lucky number of the card object
        lucky_number: u64,
    }

(3)咖啡(Coffee

    // represents a cup of coffee object
    struct Coffee has key, store {
        // uid of the coffee object
        id: UID,
        // id of the cafe object
        cafe_id: ID,
    }

(4)管理员权限(Admin

    // represents an admin capability object
    struct Admin has key {
        // uid of the admin object
        id: UID,
    }

4.3 定义事件结构

抽取到幸运数字时,会触发该事件,上层应用可以监听该事件,以便通知幸运咖啡卡的持有人去领取奖励

    //===============================
    //        Event Structs
    //===============================
    // event emitted when a lucky number is created
    struct WinnerLuckyNumberCreated has copy, drop {
        // id of the cafe object
        cafe_id: ID,
        // id of the card object
        card_id: ID,
        // lucky number of the card object
        lucky_number: u64,
        // owner of the card object
        owner: address,
    }

4.4 定义错误码

    //===============================
    //         Error codes
    //===============================
    // 购买咖啡卡时,传入了非法金额
    const EInvalidSuiAmount: u64 = 1;

    // 购买咖啡或领取奖励,使用了非当前咖啡馆的咖啡卡
    const EMismatchedCard: u64 = 2;

    // 已经抽取到了幸运数字,再次抽取将报这个错误
    const EAlreadyHasWinnerLuckyNumber: u64 = 3;

    // 非法轮次参数,抽取幸运数字的轮次需要递增
    const EInvalidDrandRound: u64 = 4;

    // 当前咖啡卡不是抽取的幸运咖啡卡
    const EInvalidWinnerLuckyNumber: u64 = 5;

    // 幸运咖啡卡尚未抽取
    const EEmptyWinnerLuckyNumber: u64 = 6;

4.5 定义常量

    //===============================
    //          Constants
    //===============================
    // 单词最大奖励咖啡卡数量
    const MAX_REWARD_CARD_COUNT: u64 = 10;

4.6 接口编写

(1)创建咖啡馆(create_cafe

    // create a new cafe object with the provided base_drand_round and initializes its fields.
    public entry fun create_cafe(base_drand_round: u64, ctx: &mut TxContext) {
        let cafe = Cafe {
            id: object::new(ctx),
            sui: balance::zero(),
            participants: 0,
            owner: tx_context::sender(ctx),
            lucky_number_2_owner: vec_map::empty(),
            lucky_number_2_card_id: vec_map::empty(),
            owner_2_card_count: vec_map::empty(),
            winner_lucky_number: option::none(),
            base_drand_round: base_drand_round,
        };

        transfer::public_share_object(cafe);

        let recipient= tx_context::sender(ctx); 
        transfer::transfer(
            Admin {
                id: object::new(ctx),
            }, recipient
        );
    }

(2)购买咖啡卡(buy_cafe_card

    // buy a card for the cafe object with the provided sui amount.
    public entry fun buy_cafe_card(cafe: &mut Cafe, sui: Coin&lt;SUI>, ctx: &mut TxContext) {
        let sui_amount = coin::value(&sui);
        assert!(sui_amount % 5 == 0 , EInvalidSuiAmount);

        let sui_balance = coin::into_balance(sui);

        // add SUI to the cafe's balance
        balance::join(&mut cafe.sui, sui_balance);

        let recipient= tx_context::sender(ctx); 

        let card_count = sui_amount / 5;
        let reward_card_count = card_count / 2;
        let i = 0_u64;
        let total_card_count = card_count + reward_card_count;
        vec_map::insert(&mut cafe.owner_2_card_count, recipient, total_card_count);
        while (i &lt; total_card_count ) {

            let card = Card {
                id: object::new(ctx),
                cafe_id:  object::uid_to_inner(&cafe.id),
                lucky_number: cafe.participants,
            };

            vec_map::insert(&mut cafe.lucky_number_2_owner, 
                    cafe.participants, recipient);

            vec_map::insert(&mut cafe.lucky_number_2_card_id, 
                    cafe.participants, object::uid_to_inner(&card.id));

            cafe.participants = cafe.participants + 1;

            transfer::transfer(card, recipient);

            i = i + 1;
        }
    }

(3)购买咖啡(buy_coffee

    // buy a coffee for the cafe object with the provided card object.
    public entry fun buy_coffee(cafe: &mut Cafe, card: Card, ctx: &mut TxContext) {

        let Card { id, cafe_id, lucky_number} = card;
        assert!(cafe_id == object::uid_to_inner(&cafe.id), EMismatchedCard);

        transfer::transfer(Coffee {
             id: object::new(ctx), cafe_id: cafe_id}, tx_context::sender(ctx));

         object::delete(id);

        vec_map::remove(&mut cafe.lucky_number_2_owner, &lucky_number);
        vec_map::remove(&mut cafe.lucky_number_2_card_id, &lucky_number);

        // cafe.participants = cafe.participants - 1;

        let card_count = vec_map::get_mut(&mut cafe.owner_2_card_count, 
            &tx_context::sender(ctx));
        *card_count = *card_count - 1;
    }

(4)抽取幸运数字(get_lucky_number

    // get the lucky number for the cafe object with the provided drand signature.
    public entry fun get_lucky_number(cafe: &mut Cafe, current_round: u64, drand_sig: vector&lt;u8>) {
        assert!(cafe.winner_lucky_number == 
            option::none(), EAlreadyHasWinnerLuckyNumber);

        assert!(cafe.base_drand_round &lt; current_round, EInvalidDrandRound);
        verify_drand_signature(drand_sig, current_round);

        cafe.base_drand_round = current_round;

        let digest = derive_randomness(drand_sig);
        cafe.winner_lucky_number= 
            option::some(safe_selection(cafe.participants, &digest));

        let lucky_number = option::borrow(&cafe.winner_lucky_number);

        assert!(vec_map::contains(&cafe.lucky_number_2_owner, lucky_number), 
            EInvalidWinnerLuckyNumber);

        let owner = vec_map::get(&cafe.lucky_number_2_owner, lucky_number);
        let card_id = vec_map::get(&cafe.lucky_number_2_card_id, lucky_number);

        event::emit(WinnerLuckyNumberCreated {
            cafe_id: object::uid_to_inner(&cafe.id),
            lucky_number: *lucky_number,
            owner: *owner,
            card_id: *card_id,
        });
    }

(5)领取奖励(get_reward_with_lucky_card

    // get the reward for the more cafe object with the provided lucky card object.
    public entry fun get_reward_with_lucky_card(cafe: &mut Cafe, card: Card, ctx: &mut TxContext) {
        let Card { id, cafe_id, lucky_number} = card;

        assert!(cafe_id == object::uid_to_inner(&cafe.id), EMismatchedCard);

        assert!(cafe.winner_lucky_number == 
            option::some(lucky_number), EInvalidWinnerLuckyNumber);

        vec_map::remove(&mut cafe.lucky_number_2_owner, &lucky_number);
        vec_map::remove(&mut cafe.lucky_number_2_card_id, &lucky_number);

        let recipient= tx_context::sender(ctx); 

        let card_count = vec_map::get(&cafe.owner_2_card_count, &recipient);

        let reward_card_count = math::min(*card_count, MAX_REWARD_CARD_COUNT);

        cafe.winner_lucky_number = option::none();
        object::delete(id);

        let card_count = vec_map::get_mut(&mut cafe.owner_2_card_count, 
            &tx_context::sender(ctx));
        *card_count = *card_count - 1;

        let i = 0_u64;
        while (i &lt; reward_card_count) {
            let card = Card {
                id: object::new(ctx),
                cafe_id:  object::uid_to_inner(&cafe.id),
                lucky_number: cafe.participants,
            };

            vec_map::insert(&mut cafe.lucky_number_2_owner, 
                    cafe.participants, recipient);

            vec_map::insert(&mut cafe.lucky_number_2_card_id, 
                    cafe.participants, object::uid_to_inner(&card.id));

            cafe.participants = cafe.participants + 1;

            *card_count = *card_count + 1;

            transfer::transfer(card, recipient);

            i = i + 1;
        }
    }

(6)删除幸运数字(remove_lucky_number)

    // remove the winner lucky number of the cafe object.
    public entry fun remove_lucky_number(_: &Admin, cafe: &mut Cafe) {
        assert!(cafe.winner_lucky_number != 
            option::none(), EEmptyWinnerLuckyNumber);

        cafe.winner_lucky_number = option::none();
    }

4.7 单测编写

#[test_only]
module bityoume::lucky_cafe_test {
    use bityoume::lucky_cafe::{Self, Cafe, Card};

    #[test]
    fun use_cafe_card_by_coffee_test() {
        use sui::test_scenario;
        use sui::test_utils::assert_eq;
        use sui::coin::mint_for_testing;

        let jason = @0x11;
        let alice = @0x22;
        let bob = @0x33;

        let scenario_val = test_scenario::begin(jason);
        let scenario = &mut scenario_val;

        // jason create a Cafe share object and got Admin object
        test_scenario::next_tx(scenario, jason);
        {
            lucky_cafe::init_for_testing(test_scenario::ctx(scenario));
        };

        test_scenario::next_tx(scenario, alice);
        {
            let cafe = test_scenario::take_shared&lt;Cafe>(scenario);
            let cafe_ref = &mut cafe;
            let coin = mint_for_testing(10, test_scenario::ctx(scenario));
            lucky_cafe::buy_cafe_card(cafe_ref, coin, test_scenario::ctx(scenario));

            let card_count = lucky_cafe::get_sender_card_count(cafe_ref, test_scenario::ctx(scenario));
            assert_eq(card_count, 3);

            test_scenario::return_shared(cafe);
        };

        test_scenario::next_tx(scenario, bob);
        {
            let cafe = test_scenario::take_shared&lt;Cafe>(scenario);
            let cafe_ref = &mut cafe;
            let coin = mint_for_testing(5, test_scenario::ctx(scenario));
            lucky_cafe::buy_cafe_card(cafe_ref, coin, test_scenario::ctx(scenario));

            let card_count = lucky_cafe::get_sender_card_count(cafe_ref, test_scenario::ctx(scenario));
            assert_eq(card_count, 1);

            test_scenario::return_shared(cafe);
        };

        test_scenario::next_tx(scenario, alice);
        {
            let cafe = test_scenario::take_shared&lt;Cafe>(scenario);
            let cafe_ref = &mut cafe;

            let card = test_scenario::take_from_sender&lt;Card>(scenario);

            lucky_cafe::buy_coffee(cafe_ref, card, test_scenario::ctx(scenario));

            let card_count = lucky_cafe::get_sender_card_count(cafe_ref, test_scenario::ctx(scenario));
            assert_eq(card_count, 2);

            test_scenario::return_shared(cafe);
        };

        test_scenario::end(scenario_val);
    }
}

5 部署测试

5.1 合约部署

切换到Jason帐号

  • 执行命令
$ sui client publish --gas-budget 100000000
  • 重要输出

image.png

  • 记录PackageID
export PACKAGE_ID=0xc0c00b50dd913b4137bfa078701b04a69f47eb87ac1b6cb6115f4042609657db

5.2 创建咖啡馆

  • 执行命令
# 获取drand随机源当前轮次
export BASE_ROUND=`curl -s https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest | jq .round`
echo $BASE_ROUND

export GAS_BUDGET=100000000
sui client call --function create_cafe --package $PACKAGE_ID --module lucky_cafe  --args $BASE_ROUND --gas-budget $GAS_BUDGET
  • 重要输出

image.png

  • 记录Cafe和Admin对象ID
export CAFE=0x8ed617b8bb176011023e63428de3d893e287da85390d8dabeb94d9b5588e5727

export ADMIN=0xc1c6278827259295249bf3d43ce0861c21960693a89b5677b175d9844eafc57c

5.3 购买咖啡卡

(1)顾客Alice购买

切换到Alice帐号

Alice花费10 GAS,获得2张,赠送1张,合计3张咖啡卡

  • 执行命令
# 切换到alice帐号
sui client switch --address alice

# 找alice名下2个大GAS对象,一个用于支付gas,一个用于拆分出指定GAS数量的coin对象
sui client gas --json | jq '.[] | select(.gasBalance > 100000) | .gasCoinId' -r > output.txt
GAS=$(sed -n '1p' output.txt)
SPLIT_COIN=$(sed -n '2p' output.txt)

# 拆分出10 GAS,用于购买咖啡卡
export COIN=`sui client split-coin --coin-id $SPLIT_COIN --amounts 10 --gas $GAS --gas-budget $GAS_BUDGET --json | jq -r '.objectChanges[] | select(.objectType=="0x2::coin::Coin&lt;0x2::sui::SUI>" and .type=="created") | .objectId'`

# 使用10GAS 购买咖啡卡
sui client call --function buy_cafe_card --package $PACKAGE_ID --module lucky_cafe --args $CAFE $COIN --gas-budget $GAS_BUDGET 
  • 重要输出

image.png

(2)顾客Bob购买

切换到Bob帐号

Bob花费5 GAS,获得1张咖啡卡

  • 执行命令
# 切换到bob帐号
sui client switch --address bob

# 找bob名下2个大GAS对象,一个用于支付gas,一个用于拆分出指定GAS数量的coin对象
sui client gas --json | jq '.[] | select(.gasBalance > 100000) | .gasCoinId' -r > output.txt
GAS=$(sed -n '1p' output.txt)
SPLIT_COIN=$(sed -n '2p' output.txt)

# 拆分出5 GAS,用于购买咖啡卡
export COIN=`sui client split-coin --coin-id $SPLIT_COIN --amounts 5 --gas $GAS --gas-budget $GAS_BUDGET --json | jq -r '.objectChanges[] | select(.objectType=="0x2::coin::Coin&lt;0x2::sui::SUI>" and .type=="created") | .objectId'`

# 使用5GAS 购买咖啡卡
sui client call --function buy_cafe_card --package $PACKAGE_ID --module lucky_cafe --args $CAFE $COIN --gas-budget $GAS_BUDGET 
  • 重要输出

image.png

(3)查看当前咖啡馆对象

$ sui client object $CAFE

在命令结果中可以获得如下信息:

  • 咖啡卡持有人与持有熟练量映射
      "owner_2_card_count": {
        "type": "0x2::vec_map::VecMap&lt;address, u64>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;address, u64>",
              "fields": {
                "key": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19",
                "value": "3"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;address, u64>",
              "fields": {
                "key": "0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0",
                "value": "1"
              }
            }
          ]
        }
      },
  • 咖啡卡幸运数字与持有人地址映射
      "lucky_number_2_owner": {
        "type": "0x2::vec_map::VecMap&lt;u64, address>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "0",
                "value": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "1",
                "value": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "2",
                "value": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "3",
                "value": "0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0"
              }
            }
          ]
        }
  • 幸运数字与咖啡卡编号映射
      "lucky_number_2_card_id": {
        "type": "0x2::vec_map::VecMap&lt;u64, 0x2::object::ID>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "0",
                "value": "0x8977e34301e7cca422fd3aa0a1f9a998798949fd325ff1786c7c120810a639e4"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "1",
                "value": "0xfba606e2c7c66488820823a246eacb3ea0cf0a8dda74e17c5713b9151762468d"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "2",
                "value": "0xf5c63e851050015040831c11897e7afff74f652c1c653f73abb3d27201b89cb6"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "3",
                "value": "0x4c5e031144be03246bf38a984981ff74ee9f25e94283d3fc094cdae113a199ff"
              }
            }
          ]
        }
      },

5.4 兑换咖啡

切换到Alice帐号,Alice使用幸运数字是1的咖啡卡兑换一杯咖啡

  • 执行命令
sui client switch --address alice

export CARD=0xfba606e2c7c66488820823a246eacb3ea0cf0a8dda74e17c5713b9151762468d

sui client call --function buy_coffee --package $PACKAGE_ID --module lucky_cafe --args $CAFE $CARD --gas-budget $GAS_BUDGET 
  • 重要输出

image.png

  • 得到Coffee对象

这个Coffee对象可以理解成实物咖啡,在链上的凭证,可以做成NFT的形式

image.png

  • 查看当前咖啡馆对象
    • 可见,幸运数字为1的咖啡卡已被销毁
    • Alice拥有咖啡卡的数量已经有3变为2
      "lucky_number_2_card_id": {
        "type": "0x2::vec_map::VecMap&lt;u64, 0x2::object::ID>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "0",
                "value": "0x8977e34301e7cca422fd3aa0a1f9a998798949fd325ff1786c7c120810a639e4"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "2",
                "value": "0xf5c63e851050015040831c11897e7afff74f652c1c653f73abb3d27201b89cb6"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, 0x2::object::ID>",
              "fields": {
                "key": "3",
                "value": "0x4c5e031144be03246bf38a984981ff74ee9f25e94283d3fc094cdae113a199ff"
              }
            }
          ]
        }
      },
      "lucky_number_2_owner": {
        "type": "0x2::vec_map::VecMap&lt;u64, address>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "0",
                "value": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "2",
                "value": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;u64, address>",
              "fields": {
                "key": "3",
                "value": "0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0"
              }
            }
          ]
        }
      },
      "owner": "0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a",
      "owner_2_card_count": {
        "type": "0x2::vec_map::VecMap&lt;address, u64>",
        "fields": {
          "contents": [
            {
              "type": "0x2::vec_map::Entry&lt;address, u64>",
              "fields": {
                "key": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19",
                "value": "2"
              }
            },
            {
              "type": "0x2::vec_map::Entry&lt;address, u64>",
              "fields": {
                "key": "0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0",
                "value": "1"
              }
            }
          ]
        }
      },

5.5 抽取幸运数字

如果幸运数字还未被抽取,或者已抽取了并已被领取,那么任何人都可以进行幸运数字的抽取。

  • 执行命令
# 获取当前轮次和随机数签名
curl -s https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest > output.txt
export CURRENT_ROUND=`jq '.round' output.txt`
export SIGNATURE=0x`jq -r '.signature' output.txt`

# 抽取幸运数字
sui client call --function get_lucky_number --package $PACKAGE_ID --module lucky_cafe --args $CAFE $CURRENT_ROUND $SIGNATURE --gas-budget $GAS_BUDGET 
  • 重要输出

若幸运数字成功抽取,将会抛出事件,应用端可以监听该事件,通知幸运咖啡卡的持有者来领取奖励。

image.png

  • 若幸运数字已经抽取,再次抽取将会报错,提示幸运数字已经抽取
Error executing transaction: Failure {
    error: "MoveAbort(MoveLocation { module: ModuleId { address: c0c00b50dd913b4137bfa078701b04a69f47eb87ac1b6cb6115f4042609657db, name: Identifier(\"lucky_cafe\") }, function: 3, instruction: 10, function_name: Some(\"get_lucky_number\") }, 3) in command 0",
}

5.6 领取奖励

抽中的幸运咖啡卡的持有人,可以凭借持有的咖啡卡,去领取奖励。当前奖励是使得持有的咖啡卡数量翻倍,但最大奖励上限为10

  • 执行命令
# 切换到Alice,因抽中的幸运数字为0,是Alice拥有的咖啡卡
sui client switch --address alice

export CARD=0x8977e34301e7cca422fd3aa0a1f9a998798949fd325ff1786c7c120810a639e4

# 使用持有的幸运咖啡卡,领取奖励
sui client call --function get_reward_with_lucky_card --package $PACKAGE_ID --module lucky_cafe --args $CAFE $CARD --gas-budget $GAS_BUDGET 
  • 重要输出

因Alice共持有2张咖啡卡,调用该接口会翻倍(新产生2张)咖啡卡,给到Alice

image.png

5.8 删除幸运数字

若抽中幸运咖啡卡的持有人,一直未领取奖励。咖啡店的创建人(具备Admin权限),可以删除已抽取到的幸运数字,以便可以让顾客再次去抽取。

  • 执行命令
sui client call --function remove_lucky_number --package $PACKAGE_ID --module lucky_cafe --args $ADMIN $CAFE --gas-budget $GAS_BUDGET 

6 总结

以上就是自己参加Dacade挑战提交合约的介绍,希望对大家有所帮助。

如希望更进一步学习SUI Move开发,欢迎关注微信公众号:Move中文,以及参与星航计划🚀,开启你的 Sui Move 之旅!

image.png

点赞 2
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
rzexin
rzexin
0x6Fa5...8165
江湖只有他的大名,没有他的介绍。