Perhaps the key advantage of Mastercoin over the raw Bitcoin protocol is the potential to include much more advanced transaction types, including transactions that specify behavior based on future information well off into the future. For example, Mastercoin joins Ripple in being one of the only two major cryptocurrency networks that include the ability for users to make binding exchange offers as a type of transaction. From there, the Mastercoin Foundation intends to integrate even more complex contracts, including bets, contracts for difference and on-blockchain dice rolls. However, up until this point Mastercoin has been taking a relatively unstructured process in developing these idea, essentially treating each one as a separate "feature" with its own transaction code and rules. This document outlines an alternative way of specifying Mastercoin contracts which follows an open-ended philosophy, specifying only the basic data and arithmetic building blocks and allowing anyone to craft arbitrarily complex Mastercoin contracts to suit their own needs, including needs which we may not even anticipate.
The underlying idea behind this specification is to allow anyone to create a contract which pays out according to an arbitrary formula. The formula will be defined in a Bitcoin-like stack-based scripting language, consisting of numbers and opcodes.
The evaluation algorithm is as follows:
dataStack = []
opStack = script
while len(opStack) > 0:
var op = opStack.pop()
if typeof(op) == 'opcode': eval(dataStack,op)
else: dataStack.push(op)
return dataStack.pop()
Where eval is defined for each opcode below. Any error (eg. division by zero) will make the script return FAIL, and result in the entire transaction being treated as invalid by the Mastercoin network. All variables will be signed 64-bit integers, and all arithmetic operations wrap around (that is, if the underlying arithmetic operation returns R, the value pushed is ((R + 2^63) % 2^64) - 2^63.
For simplicity, we define ds[-1] to be the topmost value on the dataStack, ds[-2] the second topmost, etc. The opcodes are in two categories: arithmetic operations and data operations. Arithmetic operations are:
SWAP: Swaps ds[-1] and ds[-2] in placeDUP: Pushes a copy of ds[-1] onto the stack, thereby duplicating itDROP: Pops ds[-1] and does nothingADD: Pops ds[-1] and ds[-2] and pushes ds[-1] + ds[-2]SUB: Pops ds[-1] and ds[-2] and pushes ds[-1] - ds[-2]MUL: Pops ds[-1] and ds[-2] and pushes ds[-1] * ds[-2]DIV: Pops ds[-1] and ds[-2] and pushes ds[-1] / ds[-2]MOD: Pops ds[-1] and ds[-2] and pushes ds[-1] % ds[-2] (using Python's definition of modulo if either or both are negative)POW: Pops ds[-1] and ds[-2] and pushes ds[-1] ^ ds[-2]XOR: Pops ds[-1] and ds[-2] and pushes ds[-1] XOR ds[-2] bitwiseNEG: Pops ds[-1] and pushes -ds[-1]SIGN: Pops ds[-1] and pushes -1 if ds[-1] < 0 else 1 if ds[-1] > 0 else 0EQ: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] == ds[-2] else 0LT: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] == ds[-2] else 0GT: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] == ds[-2] else 0LE: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] == ds[-2] else 0GE: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] == ds[-2] else 0IS: Pops ds[-1] and pushes 1 if ds[-1] != 0 else 0 NOT: Pops ds[-1] and pushes 1 if ds[-1] == 0 else 0 OR: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] != 0 or ds[-2] != 0 else 0AND: Pops ds[-1] and ds[-2] and pushes 1 if ds[-1] != 0 and ds[-2] != 0 else 0SHA256: Pops ds[-1] and then the ds[-1] values after that, which it registers as D, and pushes SHA256(D) as four 64-bit integersIf a block which does not yet exist is passed into any of these commands, then the script returns FAIL; similarly if BLOCK_POST is called on a time where no block exists yet or BLOCK_PRE on any time before the Genesis block.
CURBLOCK: Pushes the block number in which the transaction is confirmedCURTIME: Pushes the timestamp of the block in which the transaction is confirmedCREATION_BLOCK: Pushes the block number in which the contract was createdACCEPTANCE_BLOCK: Pushes the block number in which the contract was acceptedTIME: Pops ds[-1] and pushes the Unix timestamp of block ds[-1]BLOCK_PRE: Pops ds[-1] and pushes the block number of the last block with timestamp < ds[-1]BLOCK_POST: Pops ds[-1] and pushes the block number of the first block with timestamp >= ds[-1]BLOCK_HASH: Pops ds[-1] and pushes the block hash of block ds[-1] as four 64-bit integersPRICE_FEED: Pops ds[-1] and ds[-2] and returns the value of the price feed identified by ds[-1] at block ds[-2]SPECIAL_FEED: Pops ds[-1] and ds[-2] and returns the value of the special feed identified by ds[-1] at block ds[-2]HASINPUT: Pops ds[-1], ds[-2] and ds[-3], calculates ADDR = base58check(ds[-1] * 2^128 + ds[-2] * 2^64 + ds[-3]), pushes 1 if the transaction has ADDR as an input address, otherwise 0HASINPUT_CREATOR: Pushes 1 if the transaction has the contract creator as an input address, otherwise 0HASINPUT_COUNTERPARTY: Pushes 1 if the transaction has the contract counterparty as an input address, otherwise 0There is also a special operation:
CHECK_NONZERO: if zero is at the top of the stack, returns FAIL, otherwise pops the zeroA Mastercoin contract transaction will have the following form:
MAKE_CONTRACT_OFFER(PRIVKEY,CURRENCY,X,Y,[FORMULA_0, FORMULA_1 ...])
Where FORMULA_k for all k is a scripts. This will take X units of CURRENCY from the address controlled by PRIVKEY and put it in escrow. Anyone else will then be able to create a transaction of the form:
ACCEPT_CONTRACT_OFFER(PRIVKEY,TXID)
And put in Y units of currency to activate the contract. Then, to complete the contract anyone will be able to run:
CLAIM_CONTRACT(TXID,FORMULA_INDEX)
Suppose FORMULA_INDEX is k. Then, the scripting engine will run FORMULA_k. If FORMULA_k fails, the transaction will be meaningless. If it succeeds, say with output N, it will then close the script and automatically give N units of CURRENCY to the contract maker, and X + Y - N to the acceptor (coercing N to inside the range [0 ... X + Y] if necessary).
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
1000000000,
1000000000,
1400000000 BLOCK_POST <MSC_USD> PRICE_FEED 40 SUB 100000000 MUL 1000000000 ADD
)
Pseudo-decompilation of the script:
1000000000 + 100000000 * ( PRICE_FEED(MSC_USD, BLOCK_POST(1400000000)) - 40)
This is a contract for difference, where Bob pays in 10 MSC, asks someone else to pay 10 MSC, and which allows either party to claim the contract to pay Bob 10 MSC plus 1 MSC for every dollar that the Mastercoin price falls below 40 by Unix time 1400000000 (May 2014). Note however a full contract for difference would be more complex, including a "force liquidation" clause for both parties:
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
1000000000,
1000000000,
CURBLOCK <MSC_USD> PRICE 30 GE CHECK_NONZERO 2000000000,
CURBLOCK <MSC_USD> PRICE 50 LE CHECK_NONZERO 0,
1400000000 BLOCK_POST <MSC_USD> PRICE_FEED 40 SUB 100000000 MUL 1000000000 ADD
)
Pseudo-decompilation of the first sceipt:
CHECK_NONZERO ( 30 >= PRICE_FEED(MSC_USD, CURBLOCK) ) 2000000000
Note that it would be the responsibility of each party to make a liquidation claim. The reason why this was done was for efficiency; we do not want to have to evaluate every open contract every block.
Will the price be above 40 or below it at time 1400000000?
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
100000000,
100000000,
1400000000 BLOCK_POST <MSC_USD> PRICE_FEED 40 LE 200000000 MUL
)
Data feeds do not just need to be just about prices. Suppose that you are a Farmer in Iowa and you want to hedge against the possibility of a drought:
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
100000000,
100000000,
1400000000 BLOCK_POST <PRECIPITATION_IN_IOWA> SPECIAL_FEED 2000000000 SUB
)
Pseudo-decompilation:
2000000000 - SPECIAL_FEED(<PRECIPITATION_IN_IOWA>, BLOCK_POST(1400000000))
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
100000000,
100000000,
ACCEPTANCE_BLOCK 2 ADD BLOCK_HASH DROP DROP DROP 2 SWAP MOD 2000000000 MUL
)
Pseudo-decompilation:
(BLOCK_HASH(ACCEPTANCE_BLOCK + 2)[0:64] % 2) * 2000000000
Fairly self-explanatory. Note that this does assume that neither party has substantial mining power (specifically, enough to be able to send out three blocks at once)
MAKE_CONTRACT_OFFER(
Bobs_privkey,
MSC,
5000000,
1000000000,
< judge's address[0:64] > < judge's address[64:128] > < judge's address[128:160] > HASINPUT CHECK_NONZERO 1005000000,
1400000000 CURTIME GT CHECK 0
)
The judge can force the policy to pay out to Bob at any time, but if the judge does nothing it eventually expires, sending all funds to the insurer.