Skip to main content

Implementation details

MCP contract template

Satisfier Args in main stack = subscript (SS), MP, A, CD, EFGH, IJKLMNOP, SS_ARGS*, DUMMY

Constraint Args in altstack = C_ARGS

SS_ARGS* being SS_ARG_N, …, SS_ARG_1, FunctionIndex?, C_ARG_N, …, C_ARG_1 (inversed order)

C_ARGS being C_ARG_N, …, C_ARG_1 (inversed order)

Note, that satisfier args contain the same copy of constraint args. And constraint args in the altstack are effectively ignored, not used in the template body

Main Script in IDE

Loop script in IDE

SS, MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || C_ARGS

OP_DUP = Duplicates the subscript.

SS, SS, MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || C_ARGS

OP_TOALTSTACK = Move the subscript to the altstack ready for execution later.

SS, MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_HASH160 = Generates the leaf hash of the script.

H(SS), MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_TOALTSTACK = Move the hash to altstack to prepare op_exec params.

MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || H(SS), SS, C_ARGS

OP_PUSHDATA <LOOP_BYTECODE>

LOOP_BYTECODE, MP, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || H(SS), SS, C_ARGS

OP_SWAP = move LOOP_BYTECODE behind MP.

MP, LOOP_BYTECODE, A, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || H(SS), SS, C_ARGS

OP_ROT = rotate a to the top of the stack

A, MP, LOOP_BYTECODE, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || H(SS), SS, C_ARGS

OP_FROMALTSTACK = Get H(SS) back from alt stack. we now have proper (inversed) order of arguments for OP_EXEC

H(SS), A, MP, LOOP_BYTECODE, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_3 = Add exec M_Params value of 3.

3, H(SS), A, MP, LOOP_BYTECODE, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_3 = Add exec M_Returns value of 3.

3, 3, H(SS), A, MP, LOOP_BYTECODE, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_EXEC - execute the repeated part, note the produced stack in the loop context

----- Start of repeated section ------

MP, A, H(SS) ||

OP_DUP OP_TOALTSTACK = duplicate the merkle path and stash its copy to altstack

MP, A, H(SS) || MP

OP_2 OP_MOD = Gets the remainder of the merkle_path divided by 2.

0, A, H(SS) || MP

OP_NOTIF OP_SWAP OP_ENDIF = if remainder is 1, swap A and H(SS), otherwise do nothing

A, H(SS) || MP

OP_CAT = Concatenate subscript hash and first Merkle proof hash.

AH(SS) || MP

OP_HASH160 = Hash the concatenated subscript hash and the first merkle proof hash.

AB || MP

OP_ACTIVEBYTECODE = Get the LOOP_BYTECODE

LOOP_BYTECODE, AB || MP

OP_SWAP = swap bytecode with merkle branch

AB, LOOP_BYTECODE || MP

OP_FROMALTSTACK = get back merkle path from alt stack

MP, AB, LOOP_BYTECODE ||

OP_2 OP_DIV = divide merkle path by two (substitute for OP_RSHIFT, which only works with BIGNUMs)

MP’, AB, LOOP_BYTECODE ||

OP_SWAP = swap MP` with AB to prepare the next OP_EXEC argument order (inversed)

AB, MP’, LOOP_BYTECODE ||

----- End of repeated section ------

AB, MP’, LOOP_BYTECODE, CD, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS

OP_3 OP_ROLL = move CD to the top of the stack

CD, AB, MP’, LOOP_BYTECODE, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS

OP_3 OP_3 = Add exec M_Params value of 3, Add exec M_Returns value of 3.

3, 3, CD, AB, MP’, LOOP_BYTECODE, EFGH, IJKLMNOP, SS_ARGS, DUMMY || SS

// same as above

OP_EXEC

OP_3 OP_ROLL OP_3 OP_3

3, 3, EFGH, ABCD, MP’, LOOP_BYTECODE, IJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

// same as above

OP_EXEC

OP_3 OP_ROLL OP_3 OP_3

3, 3, IJKLMNOP, ABCDEFGH, MP’, LOOP_BYTECODE, SS_ARGS, DUMMY || SS, C_ARGS

// last exec

OP_EXEC

ABCDEFGHIJKLMNOP, MP’, LOOP_BYTECODE, SS_ARGS, DUMMY || SS, C_ARGS

OP_NIP OP_NIP = remove MP’ and LOOP_BYTECODE from the stack

ABCDEFGHIJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_PUSH20 = Push the predefined subscript hash to the stack.

<0x...> = ABCDEFGHIJKLMNOP i.e. the Merkle root hash.

<0x...>, ABCDEFGHIJKLMNOP, SS_ARGS, DUMMY || SS, C_ARGS

OP_EQUALVERIFY = Check if the generated Merkle root matches the defined Merkle root.

SS_ARGS, DUMMY || SS, C_ARGS

OP_FROMALTSTACK = Get subscript from altstack.

SS, SS_ARGS, DUMMY || C_ARGS

OP_1NEGATE = Push -1 to stack

<-1>, SS, SS_ARGS, DUMMY || C_ARGS

OP_PLACE = place SS at DUMMY

SS, SS_ARGS, SS || C_ARGS

OP_DROP = Drop SS from top of the stack

SS_ARGS, SS || C_ARGS

OP_DEPTH = get stack depth (N_Params + 1)

<N_Params+1>, SS_ARGS, SS || C_ARGS

OP_1SUB = get true N_Params value

<N_Params>, SS_ARGS, SS || C_ARGS

OP_0 = Add exec M_Returns value of 0.

<0>, <N_Params>, SS_ARGS, SS || C_ARGS

OP_EXEC = Execute subscript.

Example

<0>, <N_Params>, SS_ARG_3, SS_ARG_2, SS_ARG_1, FunctionIndex, C_ARG_3, C_ARG_2, C_ARG_1, SS || C_ARGS

if evaluated left to right, they are “pushed” in their appearance order to SS stack

Produced stack in SS: C_ARG_1, C_ARG_2, C_ARG_3, FunctionIndex, SS_ARG_1, SS_ARG_2, SS_ARG_3

which is correct ordering - (C_ARGS, SS_ARGS needed, low to high)

Because of this pre-formed stack layout, unused arguments should be dropped first.

I.e., this means that a mast contract using “visible unused” constraints should be declared like following: contract A(int visible unused a, int b)…

instead of the required ordering for normal contracts:

contract A(int a, int visible unused b)…