Part 1: Ownership and Assets

Our new programming language is object-oriented. It includes contracts, which are like classes, which can have fields. In addition, of the many variables or fields that reference objects, one of them can own the object. For example:


contract Money {
}

contract Wallet {
    Money@Owned m; // @Owned indicates that m owns the object it refers to
}

The compiler tracks ownership of each variable every time the variable is used. This information is part of the type of the variable. For example, the type of m is Money@Owned. Information about ownership is NOT available at runtime; it is only available during compilation.

Assets

Some owning references are to things that should not be accidentally lost. To prevent accidental loss, we can declare contracts with the keyword asset. Let's do this for Money:


asset contract Money {
}

Now, if we accidentally lose track of an owning reference to a Money object (by letting it go out of scope without transferring ownership to somewhere else, such as a field), the compiler will give an error:


transaction test() {
    Money m = ...; [m@Owned]; // OK, m is Owned here
    // ERROR: cannot drop reference to owned asset m
}

We can fix this by (for example) returning m, assigning it to an owning field, or passing it as an argument to an appropriate transaction. For example:

transaction test() returns Money@Owned {
    Money m = ...; [m@Owned]; // OK, m is Owned here
    return m; // gives ownership of m to the caller of test()
}

Note that non-owning references to Money are not restricted; the compiler gives no errors when they go out of scope.

Method return types

When transactions return objects, the types must be annotated. For example:


transaction withdraw() returns Money@Owned {
    // body not shown
}

Method parameters

When a reference is passed to a transaction as an argument, the transaction's declaration specifies initial and final ownership with >>. For example:


transaction spend(Money@Owned >> Unowned m) { // m is Owned initially but must be Unowned at the end.
	// implementation not shown
};

transaction testSpend(Money@Owned >> Unowned m) {
	spend(m);
	// m is now of type Money@Unowned due to specification on spend() declaration.
}

If a transaction expects an argument that is Unowned, this means that the transaction cannot take ownership. As a result, it is safe to pass an Owned reference as an argument to a transaction that expects an Unowned argument. After the transaction returns, the caller still holds ownership.

If a transaction parameter lacks >>, then ownership will not change. For example, transaction test(Money@Owned m) is equivalent to transaction test(Money@Owned >> Owned m). transaction foo(Money@Unowned m) can accept a Money reference with any ownership and the caller maintains whatever ownership it had initially when it called that transaction.

Ownership checks

Ownership can be documented and checked by writing the ownership in brackets. For example, [m@Owned] indicates that m is an owning reference at that particular point in the code. The compiler will generate an error if this might not be the case. For example, with spend as above:


transaction testSpend(Money@Owned >> Unowned  m) {
	spend(m); [m@Unowned]; // OK
	[m@Owned]; // COMPILE ERROR: m is Unowned here
}

Ownership checks in [] never change ownership; they only document and check what is known.

Assignment

Assignment (=) transfers ownership. That is, the variable on the left becomes the new owner, and the variable on the right loses ownership. For example:


Money m = ...; [m@Owned];
Money newMoney = m; [m@Unowned];
// Now m is of type Money@Unowned, not Money@Owned.

Invoking transactions

The compiler checks each invocation to make sure it is permitted according to the ownership of the transaction arguments. For example:


transaction spend(Money@Owned >> Unowned m) {
	// implementation not shown
};
transaction print(Money@Unowned m) {
    // implementation not shown
}

transaction test() {
	Money m = ... // assume m is now owned.
	print(m); [m@Owned];
	spend(m); [m@Unowned];
	spend(m); // COMPILE ERROR: spend() requires Owned input, but m is Unowned here
}

Local variables

Local variables are declared without any ownership specified; instead, you can optionally write compiler ownership checks [] to specify their ownership. The compiler will output an error if the ownership does not match the check. For example:

transaction withdraw() returns Money@Owned {
    // body not shown
}

transaction test() {
    Money m = withdraw(); [m@Owned];
    spend(m); [m@Unowned];
    [m@Owned]; // COMPILE ERROR: m is Unowned here

Getting rid of ownership

If ownership is no longer desired, disown can be used to relinquish ownership. For example:


contract Money {
    int amount;

    transaction merge(Money@Owned >> Unowned mergeFrom) {
        amount += mergeFrom.amount;
        disown mergeFrom; // We absorbed the value of mergeFrom, so the owner doesn't own it anymore.
    }
}

Method receivers (this)

Sometimes the ownership of this needs to change in a transaction. That fact can be specified by adding this as the first argument in the transaction declaration. For example:


contract Money {
    transaction discard(Money@Owned >> Unowned this) {
        disown this;
    }
}

contract Wallet {
    transaction throwAwayMoney(Money @ Owned >> Unowned money) {
        money.discard(); // 'this' argument is implicit; do not include it in transaction call.
    }
}

Fields

Field declarations MUST include ownership specifications. These mean that at the END of each transaction, the state of the object the field references MUST match the annotation on the field. For example:



contract Wallet {
	Money@Owned money; // Note that this annotation is on a field declaration!

	// exchange removes (and returns) the current contents of the Wallet, replacing it with the input Money.
	transaction exchange(Money@Owned >> Unowned otherMoney) returns Money@Owned {
        Money temporaryMoney = money; [money@Unowned]; // OK temporarily...
        money = otherMoney; [money@Owned]; // as long as money@Owned is restored by the end of the transaction.
        return temporaryMoney;
	}
}