How to run your own lottery on a Libra-Blockchain network

Josip Ke
9 min readMay 5, 2020

--

Remark. The content of this article represents the personal view of the authors and does not necessarily represent the views of their respective employers.

Introduction

A couple of months ago during a hackathon, we had the chance to dedicate some of our time to the Libra Blockchain and how to run a custom Smart Contract — or a Move module, how it is called. We could find only little out there, besides the official Libra Docs example [2] and this very helpful article [5]. Thus, we wanted to share our experience with you.

Our experiment was solely focused on exploring the Libra Blockchain, Move modules and the newly developed programming language Move. It’s not related to Libra Coin, the digital coin the Libra Association is aiming for — which itself is just a Move module. We wanted to do our explorations by implementing our own use case.

Use Case

The use case we wanted to implement is a simple lottery game. We chose this one because it has some similarities with an insurance use case:

A user (a lottery player / a policy holder) bets a specific amount (lottery ticket prize / insurance fee) on a specific event (having the correct ticket / breaking your phone’s screen) in order to win if that event occurs (lottery drawing the winning ticket / someone breaking your phone).

The difference (among others) is, that in the lottery use case the occurring event of winning is generally way more positively associated than e.g. having a broken phone screen or your house burning down 😊

Preparation

Before we dive into the code you might consider following the Libra tutorials to create accounts and transactions [1], setup a local Libra network [2] and run Move modules [3, 4] to be familiar with the commands used in this article.

Run your Local Network

Once you’re set up, get your local Libra network running with:

cargo run -p libra-swarm -- -s

Set up the lottery Move module

The lottery Move module, which we are going to call LotteryTicket, needs to fulfil a few functions. It lets users enroll in the lottery by paying 20 Libra for a ticket to take part in one round of the lottery. Like in a real lottery the winners are not paid automatically, they have to claim the prize themselves. Our Move module will check the address of the claiming party to make sure it is really the one who bought the winning ticket. Lastly there’s a method that will pay the winner the total amounts of all bought tickets. Furthermore, the organization running the lottery need to be able to reset the lottery to start a new round with new participants.

We are going to create an account for the lottery organization. This account is going to hold

  • the LotteryTicket Move module
  • the lottery prize — a Libra amount being the sum of all bought tickets

Furthermore, we need a few scripts for being able to interact with this module:

  • lottery_init.mvir — to initialize / reset a lottery round
  • lottery_script.mvir — to enroll a participant in a lottery round
  • lottery_draw.mvir — to draw the winner of the current round
  • lottery_claimprize.mvir — to pay out the winner

Module code

Here’s the code of our LotteryTicket Move module:

module LotteryTicket {
import 0x0.LibraCoin;
import 0x0.LibraAccount;
import 0x0.Vector;
resource T {
coin: LibraCoin.T,
recipient: address
}
resource P {
participants: Vector.T<address>
}
public init() {
move_to_sender<P>(P{
participants: Vector.empty<address>()
});
return;
}
public getAllParticipatns(): Vector.T<address> acquires P {
let p: & Self.P;
let lottery_address: address;
let participants: Vector.T<address>;

lottery_address = 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d;
p = borrow_global<P>(copy(lottery_address));

participants = *&move(p).participants;
return copy(participants);
}
public enroll(): u64 acquires P {
let p: &mut Self.P;
let participants: &mut Vector.T<address>;
let sender: address;
let lottery_address: address;
let amount: u64;
let count: u64;
lottery_address = 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d;
amount = 20000000;
sender = get_txn_sender();
p = borrow_global_mut<P>(copy(lottery_address));
participants = &mut copy(p).participants;
count = Vector.length<address>(freeze(copy(participants)));
Vector.push_back<address>(move(participants), copy(sender));
LibraAccount.pay_from_sender(move(lottery_address), move(amount));
return move(count) + 1;
}
public pay_winner(winner: address) {
let totalPrize: u64;
let coin: LibraCoin.T;
let sender: address;
let t: Self.T;
let lottery_address: address;
lottery_address = 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d;
sender = get_txn_sender();
assert(copy(lottery_address) == copy(sender), 0);
totalPrize = LibraAccount.balance(copy(sender));
coin = LibraAccount.withdraw_from_sender(copy(totalPrize));
t = T {
coin: move(coin),
recipient: move(winner),
};
move_to_sender<T>(move(t));
return;
}
public claim_prize(earmarked_coin_address: address): Self.T acquires T {
let t: Self.T;
let t_ref: &Self.T;
let sender: address;
t = move_from<T>(move(earmarked_coin_address));t_ref = &t;
sender = get_txn_sender();

assert(*(&move(t_ref).recipient) == move(sender), 99);
return move(t);
}
public unwrap(t: Self.T): LibraCoin.T {
let coin: LibraCoin.T;
let recipient: address;
T { coin, recipient } = move(t);
return move(coin);
}
}

Create a lottery Libra account

Let’s assign this module to the account that’s running the lottery. First, we have to create an account and mint some Libras to persist the created account in the Libra Blockchain in order to be able to publish our module on this account.

Compile the LotteryTicket Move module

Now that we have the lottery account it’s time to compile and publish our module.

Unfortunately, errors at compiling are not very clear and sometimes it takes a trial and error approach to figure out what and where the error is located and how to fix it. Therefore, simple typos can take quite some time to locate with the current compiler messages. Syntactical errors don’t give a lot feedback on what is wrong, semantical issues are slightly more descriptive. We are sure that these kinds of problems will be fixed in the future, but for the time being it is a bit cumbersome.

Publishing the LotteryTicket Move module

For this step we take the previously output file path of the compiled module and publish it under the created lottery organization’s account.

Now the module is published under account 0!

It’s important to understand that a module is published once — and only once — under the specified account. In our experiment, we had learned it the hard way that once a module is published it cannot be changed. You may try to change the code of the module and re-publish it under the same account, which will result in a ‘transaction successful’ message in the CLI but won’t have the effect you wish for.

That’s also mentioned in the Libra Docs [4]:

Subsequent modules published under <sender_address> must not be named MyModule. Each account may hold at most one module with a given name.

This is something to be very careful about during development. After updating the code, you can either restart your local network or you can publish the new module version under a newly created account.

Initialize a lottery round

Now we need to start the lottery using the lottery_init.mvir script.

import 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d.LotteryTicket;main() {
LotteryTicket.init();
return;
}

That script will initialize the lottery game on the lottery organization’s account (= account 0) in order to enable participants (= other accounts) to enroll in the lottery.

Scripts are slightly different to handle than modules. Instead of being compiled and published, they are compiled and executed. Each execution of the script will increase the sequence of the account it is executed for.

Play the lottery

Enroll participants

Now it’s time to create a participating account and mint some coins for it.

Newly created accounts can enroll in the lottery with the lottery_script.mvir. The import in the very beginning of the script needs to specify the correct account address, which is the lottery organization’s address under we previously published our lottery module.

import 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d.LotteryTicket;main() {
let count : u64;
count = LotteryTicket.enroll();
return;
}

Once again, we have to compile and execute a script. This time it is done within the context of each participant’s own account address. After the participant executed the script and is enrolled in the lottery, let’s check that the participants account balance decreased by the lottery ticket prize (= 20 Libras):

At this point we can choose to create many more participating accounts and enroll them in the lottery. For our purpose, we are going to create and enroll one further participant in order to have two participants — two potential winners — in total:

Check the prize

Before we draw the winner, we can check that the money of the bought lottery tickets was successfully transferred to the lottery account. As we have two participants there should be a total prize of 40 Libras. If you exactly followed our steps until now, you’ll see 41 Libras. This is because in the very beginning we minted 1 Libra into the lottery organization’s account in order to persist that account on the Libra Blockchain.

Draw the winner

We can now initiate the draw with lottery_draw.mvir script.

import 0x0.Vector;
import 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d.LotteryTicket;
main() {
let winner: address;
let participants: Vector.T<address>;
let participants_ref: & Vector.T<address>;
let count: u64;
participants = LotteryTicket.getAllParticipatns();
participants_ref = & participants;
count = Vector.length<address>(copy(participants_ref));
assert(copy(count) > 0, 0);
winner = Vector.get<address>(copy(participants_ref), 0);
LotteryTicket.pay_winner(move(winner));
return;
}

In this script we chose to use a very sophisticated random process to determine the lottery winner:

We select the first enrolled account as the winner. Nothing rigged here!

Once again, it’s important to specify the correct lottery account address import in the beginning of the script, compile it and run it within the context of the lottery account. Running this script from another account is not going to work as the pay_winner function of the LotteryTicket module checks the sender address … which, of course, needs to be the lottery account address.

The lottery_draw.mvir script chose the winning account address and reserved the lottery prize for being able to be claimed by exactly that address. When we check the Libra balance of the lottery organization’s account, we see that it is 0. That’s because the prize (= the Libra balance at the time of the draw) was moved to a new resource under the lottery organization’s account. Thus, the Libras are not paid to the winner yet, but they’re are gone from the lottery account’s Libra balance, as they are not available for regular Libra transfers by the lottery any longer.

Claim the prize

Winners need to claim the prize their selves. Therefore, a winner needs to compile and execute the lottery_claimprize.mvir script in the context of their account.

import 0x0.LibraAccount;
import 0x0.LibraCoin;
import 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d.LotteryTicket;
main() {
let lottery: address;
let prize: LotteryTicket.T;
let coin: LibraCoin.T;
let sender: address;
lottery = 0xd149cb9356096b990fecbcde664c876fa63f0c499377d2bf4a7f25ae9f1daf5d;
sender = {{sender}};
prize = LotteryTicket.claim_prize(copy(lottery));
coin = LotteryTicket.unwrap(move(prize));
LibraAccount.deposit(move(sender), move(coin));
return;
}

In case someone, who is not the winner, tries to claim the prize nothing is going to happen — balances stay as they are. When the real winner claims the prize, then the reserved Libra amount under the lottery organization’s account is transferred to the winner’s account.

Conclusion

In our little experiment we were able to reach our goal and explore Move modules / Smart Contracts on the Libra Blockchain as well as the newly developed language Move. The implementation of our own use case in a Move module — a simple lottery game — appeared to be more challenging than expected, though. Move is a new coding language with some interesting language concepts. Unfortunately, there is not a lot of documentation available and clearly there need to be some improvements towards developer experience. To be fair on that, the Libra Association itself states that for now Move is suitable for prototyping but it’s not particularly user-friendly. Furthermore, developing and deploying Move smart contracts is only possible on a private network and it is not foreseen to be made possible on the official Libra Network anytime soon.

To us, the Move language is a quite interesting aspect and we are looking forward to seeing the improvements here.

Thanks,

Reinhard Freiler / Josip Kenjeric / Patrick Vonk

References

[1] https://developers.libra.org/docs/my-first-transaction
[2] https://developers.libra.org/docs/run-local-network
[3] https://developers.libra.org/docs/move-overview
[4] https://developers.libra.org/docs/run-move-locally
[5] https://medium.com/featly/move-language-for-libra-blockchain-how-to-run-tests-and-compile-scripts-modules-a8b466ee5621

--

--