-
Notifications
You must be signed in to change notification settings - Fork 10
State transition exercises
Consider the following Obsidian code:
main contract Test1 {
int x;
Test1() {
this.x = 1;
}
transaction u(int y) {
->S1({z = y});
v();
}
transaction v(){
x = 17;
}
State S1 {
int z;
transaction v() {
x = z;
}
transaction w() {
x = 25;
}
}
}
Suppose you have an instance of the Test1 contract called tst1. What do think the result be of calling tst1.u(9)? What will the result be of calling tst1.w() after that?
Below are 3 options for possible ways to write state transitions in Obsidian. All of these example contracts behave identically; the only difference is the code syntax.
contract C {
state Start {
transaction a(int x) {
->S1(x)
}
}
state S1 {
int x1;
S1(int x) { // State constructor
x1 = x;
->S2(x1);
}
}
state S2 {
int x2;
S2(int x) { // State constructor
x2 = x;
}
}
}
contract C {
state Start {
transaction t(int x) {
this <- S1{x1 = x}
then toS2();
}
}
state S1 {
int x1;
transaction toS2() {
this <- S2{x2 = x1};
}
}
state S2 {
int x2;
}
}
contract C {
state Start {
transaction t(int x) {
->S1({x1 = x})
in S1 {
// Asserts that the state is S1 and either
// proceeds or throws an exception.
->S2({x2 = x1})
}
in S2 {
// Actually, we don't need to do anything in this case,
// but this is here as an example.
}
x = … // This code runs only if the current state is Start.
// This code happens to be unreachable
// (for which the compiler gives an error).
}
}
state S1 {
int x1;
}
state S2 {
int x2;
}
}
You are provided with an unfinished Obsidian contract below. Your task is to implement transactions 'a' and 'b' with the correct behavior, described in the comments of the transactions. Implement these transaction once using each style of state transition (so three times total). You can add additional code outside of transactions 'a' and 'b' if it is necessary. Places where you should add transitions are marked with //TODO.
main contract C {
state Start {
C() {
->Start;
}
state Start {
transaction a(int x, bool whichState) returns int {
/* Here is what this transaction should do:
* 1. Transitions to S1, with S1's x1 field equal to x,
* and whichState1 field equal to whichState.
* 2. Calls transaction 'b'.
* 3. - If you are in S2 after calling 'b',
* call transaction c and return the resulting int.
* - If you are in S3 after calling 'b',
* call transaction d and return the resulting int.
*/
//TODO
}
}
}
state S1 {
int x1;
bool whichState1;
transaction b() {
/* This transaction should transition to S2 if
* whichState1 is equal to true (with S2's x2 field equal to x1),
* and transition to S3 if whichState1 is equal to false
* (with S3's x3 field equal to x1).
*/
//TODO
}
}
state S2 {
int x2;
transaction c() returns int {
x2 = x2 * 2;
return x2;
}
}
state S3 {
int x3;
transaction d() returns int {
x3 = x3 * 3;
return x3;
}
}
}
Is there anything confusing about each of the styles of state transition? Which do you prefer, and why?
You are provided with an unfinished Obsidian contract for handling escrow agreements between two parties — in this case, the buyer and seller of a house. An agreement should work as follows:
- The buyer and seller work out the terms of the agreement - that is, how much the buyer will pay for the house and under what conditions the sale will go through. In this case, suppose that the only term of the agreement is that the house passes an inspection, and that we have a transaction called inspectHouse that returns true if the house passes the inspection, and false otherwise.
- The money is put in an escrow account. Once it is there, neither the buyer nor the seller can access that money until the agreement either succeeds or fails.
- If the conditions of the agreement are met, the money in the escrow contract is released to the seller. If the conditions of the contract are not met, the money in the escrow contract is released back to the buyer. We will assume that the buyer and seller both have accounts of some sort from which it is easy to withdraw and deposit money.
Your task is to implement the makeAgreement and checkAgreementConditions transactions with the correct behavior. Here is what these transactions should do:
- makeAgreement transitions to EscrowAccount, with EscrowAccount's m1 field equal to m.
- makeAgreement calls the transaction checkAgreement.
- If the conditions of the agreement are met (that is, if inspectHouse returns true), checkAgreement transitions to AgreementSuccess, with AgreementSuccess' m2 field equal to m1. Otherwise, checkAgreement transitions to AgreementFailure, with AgreementFailure's m3 field equal to m1.
- If you are in AgreementSuccess after calling checkAgreement, call paySeller. If you are in AgreementFailure after calling checkAgreement call payBuyer.
Implement these transaction once using each style of state transition (so three times total). You can add additional code outside of the transactions if it is necessary. Places where you should add code are marked with //TODO.
main contract Escrow {
Account buyer;
Account seller;
House house;
state Start {
transaction makeAgreement(Money m){
//TODO
}
}
state EscrowAccount {
Money m1;
transaction inspectHouse(House house) returns bool {
bool passInspection;
//This transaction sets passInspection to true if the house
//passes the inspection, and false otherwise.
return passInspection;
}
transaction checkAgreement() {
//TODO:
}
}
state AgreementSuccess {
Money m2;
transaction paySeller() {
seller.deposit(m2);
}
}
state AgreementFailure {
Money m3;
transaction payBuyer() {
buyer.deposit(m3);
}
}
}