------------------------------ MODULE Indices ------------------------------
\* This is the model of Substrate Frame Indices from
\* https://github.com/paritytech/substrate/tree/master/frame/indices
\* at this commit: https://git.io/Ju5Jv
\*
\* An index is a short form of an address.
\* This module handles allocation of indices for a newly created accounts.
\*
\* This TLA+ model is created for demostrative purposes of MBT capabilities.
\* s2021 Andrey Kuprianov, Informal Systems
EXTENDS Integers, FiniteSets
CONSTANTS
\* @typeAlias: ADDR = Int;
\* @typeAlias: INDEX = Int;
\* @typeAlias: BALANCE = Int;
\* @typeAlias: INDEX_ENTRY = [ addr: ADDR, deposit: Int, perm: Bool ];
\* @typeAlias: INDICES = INDEX -> INDEX_ENTRY;
\* @typeAlias: RESERVES = ADDR -> BALANCE;
\* @typeAlias: ACTION = [type: Str, who: ADDR, new: ADDR, index: INDEX, freeze: Bool];
\* How many users and indices are there?
\* @type: Int;
NUM_USERS,
\* @type: Int;
NUM_INDICES
VARIABLES
\* @type: INDICES;
indices,
\* @type: RESERVES;
reserved,
\* @type: ACTION;
action,
\* @type: Str;
actionOutcome
Indices == 1..NUM_INDICES
Addresses == 0..NUM_USERS
\* A non-address
None == -1
\* A root address
Root == 0
\* Possible outcomes
\* Operation successful
OK == "OK"
\* The index was not already assigned.
NotAssigned == "NotAssigned"
\* The index is assigned to another account.
NotOwner == "NotOwner"
\* Only root can do this.
NotRoot == "NotRoot"
\* The index was not available.
InUse == "InUse"
\* The source and destination accounts are identical.
NotTransfer == "NotTransfer"
\* The index is permanent and may not be freed/changed.
Permanent == "Permanent"
ClaimOutcome(who, index) ==
IF indices[index].who /= None
THEN InUse
ELSE OK
\* @type: (ADDR, INDEX) => Bool;
Claim(who, index) ==
/\ indices' = [indices EXCEPT ![index] =
[who |-> who, deposit |-> 1, perm |-> FALSE] ]
/\ reserved' = [reserved EXCEPT ![who] = @ + 1]
\* @type: (ADDR, ADDR, INDEX) => Str;
TransferOutcome(who, new, index) ==
IF who = new
THEN NotTransfer
ELSE IF indices[index].who = None
THEN NotAssigned
ELSE IF indices[index].perm
THEN Permanent
ELSE IF indices[index].who /= who
THEN NotOwner
ELSE
OK
\* @type: (ADDR, ADDR, INDEX) => Bool;
Transfer(who, new, index)==
LET amount == indices[index].deposit IN
/\ indices' = [indices EXCEPT ![index] = [who |-> new, deposit |-> amount, perm |-> FALSE]]
/\ reserved' = [reserved EXCEPT ![who] = @ - amount, ![new] = @ + amount]
\* @type: (ADDR, INDEX) => Str;
FreeOutcome(who, index) ==
IF indices[index].who = None
THEN NotAssigned
ELSE IF indices[index].perm
THEN Permanent
ELSE IF indices[index].who /= who
THEN NotOwner
ELSE
OK
\* @type: (ADDR, INDEX) => Bool;
Free(who, index)==
/\ indices' = [indices EXCEPT ![index] = [who |-> None, deposit |-> 0, perm |-> FALSE]]
/\ reserved' = [reserved EXCEPT ![who] = @ - indices[index].deposit]
\* @type: (ADDR, ADDR, INDEX, Bool) => Str;
ForceTransferOutcome(who, new, index, freeze) ==
IF who /= Root
THEN NotRoot
ELSE
OK
\* @type: (ADDR, ADDR, INDEX, Bool) => Bool;
ForceTransfer(who, new, index, freeze)==
LET entry == indices[index] IN
/\ indices' = [indices EXCEPT ![index] = [who |-> new, deposit |-> 0, perm |-> freeze]]
/\ IF entry.who /= None THEN
reserved' = [reserved EXCEPT ![who] = @ - indices[index].deposit]
ELSE
UNCHANGED reserved
\* @type: (ADDR, INDEX) => Str;
FreezeOutcome(who, index) ==
IF indices[index].who = None
THEN NotAssigned
ELSE IF indices[index].perm
THEN Permanent
ELSE IF indices[index].who /= who
THEN NotOwner
ELSE OK
\* @type: (ADDR, INDEX) => Bool;
Freeze(who, index) ==
/\ indices' = [indices EXCEPT ![index] = [who |-> who, deposit |-> 0, perm |-> TRUE]]
/\ reserved' = [reserved EXCEPT ![who] = @ - indices[index].deposit]
\* Start with no occupied indices, and no reserved coins
Init ==
/\ indices = [ i \in Indices |-> [ who |-> None, deposit |-> 0, perm |-> FALSE ] ]
/\ reserved = [ a \in Addresses |-> 0 ]
/\ action = [ type |-> "None" ]
/\ actionOutcome = "None"
=============================================================================