The help instructions of contract debugging

robbie wang
NewEconoLabs
Published in
11 min readDec 20, 2018

NEL

This article provides a guide for users on how to use the NEORAY contract debugging function.

Step 1: choose a transaction

When debugging a contract, you first need to choose which transaction to debug. Since the transaction has no name, we added the call time to make a distinction.

Step 2: View log/notify

Log/notify shows the result of calling the contract. There are four more important fields in it: vmstate: contract execution; gas_consumed: gas used to call the contract; stack: contract return; notify: notification of the contract throw.

· vmstate

First you need to observe the value of the vmstate field. If it is FAULT, BREAK, that means your contract has crashed and the entire call process has not been completed. If the vmstate field shows HALT, BREAK that means your contract execution completed successfully. Note that the completion of this execution simply means that no exception is thrown, and that the result is not as expected.

· gas_consumed

Only need to check if it exceeds 10gas. If you exceed 10 gas, you will need to pay an extra fee to call the contract, otherwise the call will fail. If it exceeds 10 gas, you can check out Careinfo to find out which operations are causing gas to exceed.

· stack

Our contract generally has two expected results: false and true. The stack field shows the return status of the contract. Generally, returning false will display 0, and true will display 1. If you don’t write return in your code, or return something else, there will be no value in the stack or display what you set.

· notify

In the contract code you can define notify to throw a notification to the chain, then these notify are presented in the notifications field. This is similar to the print log in the general code debugging.

Step 3: debugging AVM

Avm represents the execution steps of the NEO virtual machine. We map the execution steps of the virtual machine to the contract source and stack data, and we can see the order in which the code is executed in the virtual machine and how the data in the memory changes.

When debugging, we need to follow the steps in the AVM file one by one in the order of the virtual machine, to see what the source code corresponding to each step of the execution is, and whether the data has changed. For your convenience, you can quickly traverse by pressing the ↑ or ↓ arrow after selecting a step. Um… this is like a program is executing. Different from the general debugger, there is no automatic break point here, but only manual breakpoints — you just stop and any position is a breakpoint.

You may not understand the AVM, but it does not matter. In general you just have to stare at the source code, take a look at the data in the stack when needed. Once you are familiar with it, you may be able to see the problem from AVM at a glance. So we also provide an explanation of AVM. This is in the NEO source code, we just translated it, if you are interested, you can study it.

In addition, we have a valuetool widget that converts the data in the stack to string and decimal values, which you need to click on the number in the stack to see it. Enjoy it., but it does not matter, in general you just have to stare at the source code, take a look at the data in the stack when needed. Once you are familiar with it, you may be able to see the problem from AVM at a glance. So we also provide an explanation of avm. This is in the NEO source code, we just translated it, if you are interested, you can study it.

Optional step: View Careinfo

In log/notify you can see the total gas consumption, but you can’t know where gas is being consumed. In ENO’s smart contract system, every normal instruction consumes 0.001 gas, but some key operations consume a lot of gas. In Careinfo, it will display the operation that consumes a large amount of gas in your contract. You can adjust your code logic based on this to avoid exceeding the free credit of 10 gas.

Appendix 1 System Fee

Appendix 2 AVM Explanation

Constants

/// An empty array of bytes is pushed onto the stack.

PUSH0 = 0x00,

PUSHF = PUSH0,

/// 0x01–0x4B The next opcode bytes is data to be pushed onto the stack

PUSHBYTES1 = 0x01,

PUSHBYTES75 = 0x4B,

/// The next byte contains the number of bytes to be pushed onto the stack.

PUSHDATA1 = 0x4C,

/// The next two bytes contain the number of bytes to be pushed onto the stack.

PUSHDATA2 = 0x4D,

/// The next four bytes contain the number of bytes to be pushed onto the stack.

PUSHDATA4 = 0x4E,

/// The number -1 is pushed onto the stack.

PUSHM1 = 0x4F,

/// The number 1 is pushed onto the stack.

PUSH1 = 0x51,

PUSHT = PUSH1,

/// The number 2 is pushed onto the stack.

PUSH2 = 0x52,

/// The number 3 is pushed onto the stack.

PUSH3 = 0x53,

/// The number 4 is pushed onto the stack.

PUSH4 = 0x54,

/// The number 5 is pushed onto the stack.

PUSH5 = 0x55,

/// The number 6 is pushed onto the stack.

PUSH6 = 0x56,

/// The number 7 is pushed onto the stack.

PUSH7 = 0x57,

/// The number 8 is pushed onto the stack.

PUSH8 = 0x58,

/// The number 9 is pushed onto the stack.

PUSH9 = 0x59,

/// The number 10 is pushed onto the stack.

PUSH10 = 0x5A,

/// The number 11 is pushed onto the stack.

PUSH11 = 0x5B,

/// The number 12 is pushed onto the stack.

PUSH12 = 0x5C,

/// The number 13 is pushed onto the stack.

PUSH13 = 0x5D,

/// The number 14 is pushed onto the stack.

PUSH14 = 0x5E,

/// The number 15 is pushed onto the stack.

PUSH15 = 0x5F,

/// The number 16 is pushed onto the stack.

PUSH16 = 0x60,

Flow control

/// Does nothing.

NOP = 0x61,

/// Reads a 2-byte value n and a jump is performed to relative position n-3.

JMP = 0x62,

/// A boolean value b is taken from main stack and reads a 2-byte value n, if b is True then a jump is performed to relative position n-3.

JMPIF = 0x63,

/// A boolean value b is taken from main stack and reads a 2-byte value n, if b is False then a jump is performed to relative position n-3.

JMPIFNOT = 0x64,

/// Current context is copied to the invocation stack. Reads a 2-byte value n and a jump is performed to relative position n-3.

CALL = 0x65,

/// Stops the execution if invocation stack is empty.

RET = 0x66,

/// Reads a scripthash and executes the corresponding contract.

APPCALL = 0x67,

/// Reads a string and executes the corresponding operation.

SYSCALL = 0x68,

/// Reads a scripthash and executes the corresponding contract. Disposes the top item on invocation stack.

TAILCALL = 0x69,

Stack

/// Duplicates the item on top of alt stack and put it on top of main stack.

DUPFROMALTSTACK = 0x6A,

/// Puts the input onto the top of the alt stack. Removes it from the main stack.

TOALTSTACK = 0x6B,

/// Puts the input onto the top of the main stack. Removes it from the alt stack.

FROMALTSTACK = 0x6C,

/// The item n back in the main stack is removed.

XDROP = 0x6D,

/// The item n back in the main stack in swapped with top stack item.

XSWAP = 0x72,

/// The item on top of the main stack is copied and inserted to the position n in the main stack.

XTUCK = 0x73,

/// Puts the number of stack items onto the stack.

DEPTH = 0x74,

/// Removes the top stack item.

DROP = 0x75,

/// Duplicates the top stack item.

DUP = 0x76,

/// Removes the second-to-top stack item.

NIP = 0x77,

/// Copies the second-to-top stack item to the top.

OVER = 0x78,

/// The item n back in the stack is copied to the top.

PICK = 0x79,

/// The item n back in the stack is moved to the top.

ROLL = 0x7A,

/// The top three items on the stack are rotated to the left.

ROT = 0x7B,

/// The top two items on the stack are swapped.

SWAP = 0x7C,

/// The item at the top of the stack is copied and inserted before the second-to-top item.

TUCK = 0x7D,

Splice

/// Concatenates two strings.

CAT = 0x7E,

/// Returns a section of a string.

SUBSTR = 0x7F,

/// Keeps only characters left of the specified point in a string.

LEFT = 0x80,

/// Keeps only characters right of the specified point in a string.

RIGHT = 0x81,

/// Returns the length of the input string.

SIZE = 0x82,

Bitwise logic

/// Flips all of the bits in the input.

INVERT = 0x83,

/// Boolean and between each bit in the inputs.

AND = 0x84,

/// Boolean or between each bit in the inputs.

OR = 0x85,

/// Boolean exclusive or between each bit in the inputs.

XOR = 0x86,

/// Returns 1 if the inputs are exactly equal, 0 otherwise.

EQUAL = 0x87,

//OP_EQUALVERIFY = 0x88, // Same as OP_EQUAL, but runs OP_VERIFY afterward.

//OP_RESERVED1 = 0x89, // Transaction is invalid unless occuring in an unexecuted OP_IF branch

//OP_RESERVED2 = 0x8A, // Transaction is invalid unless occuring in an unexecuted OP_IF branch

Arithmetic

// Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output.

/// 1 is added to the input.

INC = 0x8B,

/// 1 is subtracted from the input.

DEC = 0x8C,

/// Puts the sign of top stack item on top of the main stack. If value is negative, put -1; if positive, put 1; if value is zero, put 0.

SIGN = 0x8D,

/// The sign of the input is flipped.

NEGATE = 0x8F,

/// The input is made positive.

ABS = 0x90,

/// If the input is 0 or 1, it is flipped. Otherwise the output will be 0.

NOT = 0x91,

/// Returns 0 if the input is 0. 1 otherwise.

NZ = 0x92,

/// a is added to b.

ADD = 0x93,

/// b is subtracted from a.

SUB = 0x94,

/// a is multiplied by b.

MUL = 0x95,

/// a is divided by b.

DIV = 0x96,

/// Returns the remainder after dividing a by b.

MOD = 0x97,

/// Shifts a left b bits, preserving sign.

SHL = 0x98,

/// Shifts a right b bits, preserving sign.

SHR = 0x99,

/// If both a and b are not 0, the output is 1. Otherwise 0.

BOOLAND = 0x9A,

/// If a or b is not 0, the output is 1. Otherwise 0.

BOOLOR = 0x9B,

/// Returns 1 if the numbers are equal, 0 otherwise.

NUMEQUAL = 0x9C,

/// Returns 1 if the numbers are not equal, 0 otherwise.

NUMNOTEQUAL = 0x9E,

/// Returns 1 if a is less than b, 0 otherwise.

LT = 0x9F,

/// Returns 1 if a is greater than b, 0 otherwise.

GT = 0xA0,

/// Returns 1 if a is less than or equal to b, 0 otherwise.

LTE = 0xA1,

/// Returns 1 if a is greater than or equal to b, 0 otherwise.

GTE = 0xA2,

/// Returns the smaller of a and b.

MIN = 0xA3,

/// Returns the larger of a and b.

MAX = 0xA4,

/// Returns 1 if x is within the specified range (left-inclusive), 0 otherwise.

WITHIN = 0xA5,

Crypto

//RIPEMD160 = 0xA6, // The input is hashed using RIPEMD-160.

/// The input is hashed using SHA-1.

SHA1 = 0xA7,

/// The input is hashed using SHA-256.

SHA256 = 0xA8,

/// The input is hashed using Hash160: first with SHA-256 and then with RIPEMD-160.

HASH160 = 0xA9,

/// The input is hashed using Hash256: twice with SHA-256.

HASH256 = 0xAA,

/// The publickey and signature are taken from main stack. Verifies if transaction was signed by given publickey and a boolean output is put on top of the main stack.

CHECKSIG = 0xAC,

/// The publickey, signature and message are taken from main stack. Verifies if given message was signed by given publickey and a boolean output is put on top of the main stack.

VERIFY = 0xAD,

/// A set of n public keys (an array or value n followed by n pubkeys) is validated against a set of m signatures (an array or value m followed by m signatures). Verify transaction as multisig and a boolean output is put on top of the main stack.

CHECKMULTISIG = 0xAE,

Array

/// An array is removed from top of the main stack. Its size is put on top of the main stack.

ARRAYSIZE = 0xC0,

/// A value n is taken from top of main stack. The next n items on main stack are removed, put inside n-sized array and this array is put on top of the main stack.

PACK = 0xC1,

/// An array is removed from top of the main stack. Its elements are put on top of the main stack (in reverse order) and the array size is also put on main stack.

UNPACK = 0xC2,

/// An input index n (or key) and an array (or map) are taken from main stack. Element array[n] (or map[n]) is put on top of the main stack.

PICKITEM = 0xC3,

/// A value v, index n (or key) and an array (or map) are taken from main stack. Attribution array[n]=v (or map[n]=v) is performed.

SETITEM = 0xC4,

///用作引用類型 en: A value n is taken from top of main stack. A zero-filled array type with size n is put on top of the main stack.

NEWARRAY = 0xC5,

///用作值類型 en: A value n is taken from top of main stack. A zero-filled struct type with size n is put on top of the main stack.

NEWSTRUCT = 0xC6,

/// A Map is created and put on top of the main stack.

NEWMAP = 0xC7,

/// The item on top of main stack is removed and appended to the second item on top of the main stack.

APPEND = 0xC8,

/// An array is removed from the top of the main stack and its elements are reversed.

REVERSE = 0xC9,

/// An input index n (or key) and an array (or map) are removed from the top of the main stack. Element array[n] (or map[n]) is removed.

REMOVE = 0xCA,

/// An input index n (or key) and an array (or map) are removed from the top of the main stack. Puts True on top of main stack if array[n] (or map[n]) exist, and False otherwise.

HASKEY = 0xCB,

/// A map is taken from top of the main stack. The keys of this map are put on top of the main stack.

KEYS = 0xCC,

/// A map is taken from top of the main stack. The values of this map are put on top of the main stack.

VALUES = 0xCD,

// Stack isolation

CALL_I = 0xE0,

CALL_E = 0xE1,

CALL_ED = 0xE2,

CALL_ET = 0xE3,

CALL_EDT = 0xE4,

Exceptions

/// Halts the execution of the vm by setting VMState.FAULT.

THROW = 0xF0,

/// Removes top stack item n, and halts the execution of the vm by setting VMState.FAULT only if n is False.

THROWIFNOT = 0xF1

--

--