## Smart Contract development: Buy ticket entrypoint​

LIGO concepts used in this part: with this second entrypoint, we will need to register players and map a raffle ticket to each player. Thus, we will learn how to use collections. It will also be the opportunity for you to review functions and checks

The second entrypoint can be freely called by everyone who wants to buy a ticket. In our use case, each address can only buy one ticket, which costs 1 Tez.

Two additional variables have to be stored:

1. who is taking part in the raffle
2. who owns a ticket

The storage has to be modified. Collections are going to come in handy for the modification of the storage

### LIGO concepts used in this part: collections​

#### Lists​

Lists are linear collections of elements of the same type. Linear means that in order to reach an element in a list, all the elements before have to be browsed (sequentially accessed). Elements can be repeated as only their order in the collection matters. The first element is called the head, and the sub-list after the head is called the tail.

Lists are used for returning operations from a smart contract's main function and to store the same values several times in a collection

For more details, see the Ligolang list documentation

#### Sets​

Sets are unordered collections of values of the same type (as opposed to lists which are ordered collections). Like the mathematical sets and lists, sets can be empty and, if not, elements of sets in LIGO are unique, even though they can be repeated in a list.

For more details, see the Ligolang set documentation

#### Maps​

A Map is a data structure that associates a value to a key, thus creating a key-value binding. All keys have the same type and all values have the same type. An additional requirement is that the type of the keys must be comparable.

Maps load their entries into the environment, which is fine for small maps, but for maps holding millions of entries, the cost of loading them would be too expensive. For this we use big_maps. Their syntax is the same as for regular maps, but they are optimized for a huge number of entries.

For more details, see the Ligolang map documentation and Ligolang big map documentation

### Customizing the Raffle storage​

Thanks to these collections, the second entrypoint of the Raffle smart contract can be implemented. A list of participants must be stored, as well as the ticket/owner pair.

Two new variables will be stored in the contract storage.

What collection should be used for:

1. the participants (who can only buy one ticket)?
2. the tickets and their owner?

For the first point, two collections could be used: a list and a set. Since the participants can only buy one ticket, a set is the right choice (since each element cannot appear twice).

For the second point, each ticket should be mapped to its owner. The number of participants is not limited: there might be millions of them, so a big map seems the right choice.

The set of participants should a have set of addresses, while the big map should map a ticket id (a nat) to an address. The new storage is:

type storage is record [    admin : address;    close_date : timestamp;    jackpot : tez;    description : string;    raffle_is_open : bool;    players : set (address);    sold_tickets : big_map (nat, address);  ]

The smart contract needs to expose an entrypoint to buy tickets. The method is the same as the one detailed for the first entrypoint:

1. Define the type parameter. This type should be unit since the buyer does not get to choose the ticket id:
type buyTicketParameter is unit
1. Adding the entrypoint in the variant:
type raffleEntrypoints is| OpenRaffle of openRaffleParameter| BuyTicket of buyTicketParameter
1. Handling the new entrypoint in the control flow:
function main (const action : raffleEntrypoints; const store : storage): returnType is  case action of [    | OpenRaffle (param) -> open_raffle (param.0, param.1, param.2, store)    | BuyTicket (param) -> buy_ticket(param, store)  ];

The last step is to implement the logic of this entrypoint. Just as for the first entrypoint, this logic will be implemented in a function, buy_ticket:

function buy_ticket (const param: unit; const store : storage) : returnType is {  skip} with ((nil : list (operation)), store)

Two variables have to be checked:

1. is the buyer sending the right amount of tez?

For the first point, this is the same check that is done for the first entrypoint. Checking if an address is calling the entrypoint for the first time (= a buyer cannot buy more than one ticket) means checking if the calling address is already in the payers set.

function buy_ticket (const param: unit; const store : storage) : returnType is {  if store.raffle_is_open then {    const ticket_price : tez = 1tez;    const current_player : address = Tezos.get_sender ();    if Tezos.get_amount () <> ticket_price    then failwith("The sender did not send the right tez amount.")    else {      if store.players contains current_player      then failwith("Each player can participate only once.")      else {        skip      }    }  } else {    failwith("The raffle is closed.")  }} with ((nil : list (operation)), store)

Once these two checks have been performed, the buyer can receive a ticket. For this, the entrypoint needs to:

1. register the address of a participant. The address must be added into the players set from the storage.
2. create a raffle ticket id. Since each participant can only buy a single ticket, the size of the participants set gives the new ticket id.
3. associate the ticket with its owner. The new ticket id will be a map to the buyer in the sold_tickets big map.

These three steps use the methods described in the collections section.

Note that const store has been replaced by var store because it is modified.

Note that const param has been replaced by const _param. The _ is used when a variable is unused.

function buy_ticket (const param: unit; var store : storage) : returnType is {  if store.raffle_is_open then {    const ticket_price : tez = 1tez;    const current_player : address = Tezos.get_sender ();    if Tezos.get_amount () =/= ticket_price    then failwith("The sender did not send the right tez amount.")    else {      if store.players contains current_player      then failwith("Each player can participate only once.")      else {        const ticket_id : nat = Set.size(store.players);        store.players := Set.add(current_player, store.players);        store.sold_tickets[ticket_id] := current_player;      }    }  } else {    failwith("The raffle is closed.")  }} with ((nil : list (operation)), store)

Our contract now is:

type openRaffleParameter is tez * timestamp * option(string)type buyTicketParameter is unittype raffleEntrypoints is  | OpenRaffle of openRaffleParameter  | BuyTicket of buyTicketParametertype storage is record [    admin : address;    close_date : timestamp;    jackpot : tez;    description : string;    raffle_is_open : bool;    players : set (address);    sold_tickets : big_map (nat, address);  ]type returnType is list (operation) * storagefunction open_raffle (const jackpot_amount : tez; const close_date : timestamp; const description : option (string); var store : storage) : returnType is {  if Tezos.get_source () =/= store.admin  then failwith ("Administrator not recognized.")  else {    if not store.raffle_is_open then {      if Tezos.get_amount () < jackpot_amount      then failwith ("The administrator does not own enough tz.")      else {        const today : timestamp = Tezos.get_now ();        const seven_day : int = 7 * 86400;        const in_7_day : timestamp = today + seven_day;        const is_close_date_not_valid : bool = close_date < in_7_day;        if is_close_date_not_valid        then failwith("The raffle must remain open for at least 7 days.")        else {          patch store with record [            jackpot = jackpot_amount;            close_date = close_date;            raffle_is_open = True;          ];          case description of [          | Some(d) -> patch store with record [description=d]          | None -> {skip}          ]        }      }    }    else {      failwith ("A raffle is already open.")    }  }} with ((nil : list (operation)), store)function buy_ticket (const _param: unit; var store : storage) : returnType is {  if store.raffle_is_open then {    const ticket_price : tez = 1tez;    const current_player : address = Tezos.get_sender ();    if Tezos.get_amount () =/= ticket_price    then failwith("The sender did not send the right tez amount.")    else {      if store.players contains current_player      then failwith("Each player can participate only once.")      else {        const ticket_id : nat = Set.size(store.players);        store.players := Set.add(current_player, store.players);        store.sold_tickets[ticket_id] := current_player;      }    }  } else {    failwith("The raffle is closed.")  }} with ((nil : list (operation)), store)function main (const action : raffleEntrypoints; const store : storage): returnType is  case action of [    | OpenRaffle (param) -> open_raffle (param.0, param.1, param.2, store)    | BuyTicket (param) -> buy_ticket(param, store)  ]