Skip to content

Lottery ticket exercise

celestebarnaby edited this page Jul 7, 2017 · 26 revisions

Lottery Ticket

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 1

Design, using pseudocode, a program to handle this lottery system. 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.

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.

Part 2

Suppose we also have a concept of ownership in our lottery system. If you buy a lottery ticket, the ticket belongs to you. You should only be able to redeem a winning lottery ticket if it belongs to you, but anyone can check whether a lottery ticket won. Answer the following questions:

  • How can we handle this notion of ownership in this program?
  • What would you call the relationship between a lottery and a lottery ticket?

Modify your code from part 1 to account for these changes.

Part 3

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 4a

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 with the following code:

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 discarding it (in the transaction below.) 
    
    LotteryTicket ticket;
    transaction discardTicket() {
      ->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);
    }
  }
}

However, this code is flawed, because the field ticket is lost forever when the discardTicket transaction is invoked. Modify this code so that the lottery player may discard 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.

Part 4b

Provided below are several options for avoiding the loss of ticket in this scenario. Which option do you prefer, and why? Is there anything confusing about any of these options? Can you see any problems/confusing situations arising with any of them?

Option 1

contract LotteryPlayer {
  state HasTicket {
    LotteryTicket ticket;
    transaction discardTicket() returns LotteryTicket {
      Ticket ticket = ->NoTicket;
      return ticket;
    }
  }

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

Option 2

contract LotteryPlayer {
  state HasTicket {
    LotteryTicket ticket;

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

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

In this system, reading from a field that's already missing is 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 3

contract LotteryPlayer {
  state HasTicket {
    LotteryTicket ticket;
    transaction discardTicket() returns LotteryTicket {
      LotteryTicket ticket_ret;
      unpack {
        ticket_ret = ticket;
      } pack as NoTicket;
      return ticket_ret;
    }
  }

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

In general, a 'pack'/'unpack' statement works as follows:

unpack {
  /* 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 */
} pack as S2(g_1 = x_1, ..., g_n = x_n)