Skip to content

Lottery ticket exercise

celestebarnaby edited this page Jul 19, 2017 · 26 revisions

Lottery Ticket

(See the Google Doc for the updated version of this exercise.)

Suppose we want to design a system that allows users to create and participate in lotteries. The rules of the lottery are as follows:

  • Someone creates a lottery with a secret "winning number" between 0 and 100.
  • Anyone can purchase a lottery ticket for a set amount of money. A lottery ticket also has a number (picked by the buyer) between 0 and 100. If the lottery ticket's number is equal to the lottery's number, that is a winning ticket.
  • You can check whether a ticket is the winning ticket and redeem a winning lottery ticket for a set amount of money.
  • You can only redeem a lottery ticket from the lottery where you bought the ticket. For instance, if you buy a lottery ticket from lottery1, you cannot redeem your ticket from lottery2, even if that ticket's number matches the winning number of lottery2.

Part 1a

Design, using pseudocode, a program to handle this lottery system. Do not worry about writing code that resembles any particular language - we want to see the kind of code you would want to be able to write. The goal here is to see how people naturally go about solving a problem like this.

It is critical to the system that a lottery ticket can be redeemed only from the lottery where it was purchased. You can assume that creator and players of the lottery have accounts of some sort from which money can be withdrawn and deposited. What would you call the relationship between a lottery and a lottery ticket?

Part 1b

(You can now look at the Obsidian tutorial.) In Obsidian, the type of one object can be dependent upon another object. In this case, the type of any given lottery ticket is dependent upon which lottery it came from - or, put another way, each instance of l: Lottery has its own type l.LotteryTicket. With this in mind, examine this code and implement the transactions checkIfTicketWon and redeemTicket. You can make the following assumptions: There is a simple way of withdrawing and depositing money from an Account. This is exhibited in the buyTicket transaction.

After a winning ticket is redeemed, prizeMoneyAmount is set to 0, in order to ensure that a lottery can only be won once.

contract Lottery {
    int winningNum;
    Account ownerAccount;
    int prizeMoneyAmount;
    int ticketCost;

    contract LotteryTicket {
        int ticketNum;
        Account ticketAccount;

        // LotteryTicket constructor
        LotteryTicket(int num, Account account) {
            ticketNum = num;
            ticketAccount = account;
        }
    }

    // Lottery constructor
    Lottery(int num, Account account, int amount, int cost) {
        winningNum = num;
        ownerAccount = account;
        prizeMoneyAmount = amount;
        ticketCost = cost;
    }
    
    transaction buyTicket(int num, Account account) returns LotteryTicket {
        LotteryTicket ticket = LotteryTicket(num, account);
        Money m = account.withdraw(this.ticketCost);
        ownerAccount.deposit(m);
        return ticket;
    }

    transaction checkIfWinningTicket(LotteryTicket ticket) returns bool {
        //TODO
    }

    transaction redeemTicket(LotteryTicket ticket) {
       //TODO
    }
}

Answer the following questions:

  • Suppose you have two instances of the Lottery contract, l1 and l2. What will happen if you buy a ticket from l1 and try to redeem it from l2?
  • Is there anything confusing or restricting about having the type of one object be dependent upon another?

Part 2

We want to place restrictions upon the creation and destruction of objects where there is a notion of ownership (e.g. money, or lottery tickets) in order to avoid cheating/fraud/etc. There should only be certain places where such an object can be created, and such an object should not be destroyed/lost accidentally. With this in mind, answer the following questions:

  • Where should lottery tickets be created?
  • What should happen to a lottery ticket after it is redeemed? Following from our intuitive notions of how lotteries work, you should only be able to redeem a ticket once.

Part 3a

We have added the condition that participants in the lottery may only have one lottery ticket at a time. We have begun to implement this in Obsidian below. However, this code is flawed, because the field ticket is lost forever when the removeTicket transaction is invoked. Modify this code so that the lottery player may remove their ticket and transition to the NoTicket state, without completely losing track of ticket. You may add code anywhere you think is necessary to accomplish this.

contract LotteryPlayer {
  state HasTicket {
    // In this state, the lottery player is not able to purchase a ticket.
    // They can get rid of their ticket either 
    // by redeeming it (if it is a winning ticket)
    // or removing it (in the transaction below.) 
    
    LotteryTicket ticket;

    transaction removeTicket() {
      ->NoTicket;
    }
  }

  state NoTicket {
    // In this state, the lottery player is able 
    // to purchase a ticket in exchange for money
    transaction buyTicket(Money m) {
      // The player obtains a lottery ticket called newTicket here. 
      // Suppose that their obtaining this ticket respects our 
      // restrictions on resource creation.
      ->HasTicket{ticket = newTicket};
    }
  }
}

Part 3b

Below are two options for avoiding the loss of ticket in this scenario.

  • Which option do you prefer, and why?
  • Is there anything confusing about either of these options? Can you foresee any problems arising with either of them?

Option 1

contract LotteryPlayer {
  state HasTicket {
    LotteryTicket ticket;

    transaction removeTicket() returns LotteryTicket {
      LotteryTicket removedTicket = this.ticket;
      ->NoTicket;
      return removedTicket;
    }
  }

  state NoTicket {
    transaction buyTicket(Money m) {
      //get a lottery ticket called newTicket
      ->HasTicket{ticket = newTicket};
    }
  }
}

In this system, reading from an owned field (like a lottery ticket), as we do in the line ticket = this.ticket, causes the field to become "missing" - that is, we cannot use the field again. Reading from a missing field causes an error. To perform a state transition from S1 to S2, all the owned fields of S1 (that are not in S2) must be missing.

Option 2

contract LotteryPlayer {
  state HasTicket {
    LotteryTicket ticket;

    transaction removeTicket() returns LotteryTicket {
      LotteryTicket ticket_ret;
      SPECIAL_BLOCK {
        ticket_ret = ticket;
      } ->NoTicket;
      return ticket_ret;
    }
  }

  state NoTicket {
    transaction buyTicket(Money m) {
      //get a lottery ticket called newTicket
      ->HasMoney(ticket = newTicket);
    }
  }
}

This option also necessitates that all owned fields of a state are missing before transitioning to another state; however, here, we explicitly have a block wherein owned fields are allowed to be missing. In general, this block works as follows:

SPECIAL_BLOCK {
  /* If [this] was in state [S1], and [S1] has fields [f_1, ..., f_n], then
   * these fields will be locally in scope for the duration of the block */
  /* statements go here */
} ->S2{g_1 = x_1, ..., g_n = x_n}
  • How would you describe what is happening in this block?
  • What would you call it?