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:
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@Unowned]; // OK
[m@Owned]; // COMPILE ERROR: m is Unowned here
}
Ownership checks in []
never change ownership; they only document and check what is known.
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.
}
}
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 is still Owned
spend(m); // m is now Unowned
spend(m); // COMPILE ERROR: spend() requires Owned input, but m is Unowned here
}
Errors can be flagged with revert
. A description of the error can be provided as well. An example of usage is given below.
transaction checkAmount(Money@Unowned m) {
if (m.getAmount() < 0) {
revert("Money must have an amount greater than 0");
}
}