Skip to content

Lottery ticket exercise

celestebarnaby edited this page Jul 11, 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 1a

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. What would you call the relationship between a lottery and a lottery ticket?

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 1b

A logical way of handling this situation is by designing a type system wherein 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. Supposing that we have such a type system implemented in Obsidian, and have implemented our lottery ticket program like this:

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

    contract LotteryTicket {
        int ticketNum;
        Account ticketAccount;
        Account ticketAccount;

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

    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 checkTicket(LotteryTicket ticket) returns bool {
        if (ticket.ticketNum == this.winningNum) return true else return false;
    }

    transaction redeemTicket(LotteryTicket ticket) {
        if (checkTicket(ticket)) {
            Money m = ownerAccount.withdraw(this.prizeMoneyAmount);
            ticketAccount.deposit(m);
            // Sets the prize money amount equal to 0 
            // so that a lottery can only be won once
            this.prizeMoneyAmount = 0;
        } else throw;
    }
}
  • 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?
  • Can you foresee any problems/weaknesses with this type system?

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. Describe how can we handle this notion of ownership in this program, and modify the code from part 1b 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

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 any of these options? Can you foresee any problems arising with either of them?

Option 1

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 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 discardTicket() 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?