Qtum abigen


This is a lightweight ABI for the Qtum x86 contract. This ABI specification is called the Simple ABI.


SimpleABI only encodes flat values and Simple Arrays. It’s not the ultimate state of the smart contract ABI, but it’s very simple to implement and, most importantly, very comfortable to use.


Abigen can be run in the following three ways:

1

Dispatcher — Generates code to decode ABI data on the SCCS and call the appropriate functions

2

Caller — Generates code for a specified contract, and you can easily invoke external contracts using SimpleABI

3

Encoder – Generates data for a contract call with a series of parameters. One can simply invoke the SimpleABI contract with SendtoContract, etc


What is the difference with Solidity?


The Solidity ABI for calling external contracts is built directly into the language. Solidity is specifically for building smart contracts, so this design makes sense.

However, the x86 VM supports many different languages that are not specifically designed for smart contracts. This means we have to build the ABI on top of these existing languages. It might be possible to automatically build the appropriate ABI using complex language analysis libraries, etc., but it is difficult to use, restrictive, and not portable between languages.


The new VM we are designing should be able to call another C contract from your C++ contract, a Rust contract from your C++ contract, etc.


This ABI is much simpler than the Solidity ABI, but unlike Solidity it is called explicitly, that is, it can only call functions specified directly in the ABI file. As a result, this ABI tends to require more template code to handle simple things like calling functions or even just decoding the sent ABI data…… So, an ideal tool is code generation. We can use template code to generate functions so that developers can actually use them in a few lines of code.


Unlike EVM, which exposes “call data,” the x86 VM has a “Smart Contract Communication stack” (SCCS). We just need a stack to pass and return data between contracts, there is no need to parse a large flat byte array.


This greatly simplifies the implementation of smart contracts, and this ABI is designed to take advantage of this. Instead of decoding a chunk of data, an SCCS can treat each parameter as a subitem on the stack. This also makes it easier to invoke the contract because you no longer need to construct a large chunk of data. Large chunks of data often require allocating enough memory to fit all elements into a contiguous area of memory; Whereas SCCS can be implemented with many smaller memory implementations.


ABI specification


The ABI specification is simple, 1 line per function. It also acts as a table for the stack before and after the contract function. Even without Abigen, this is a useful specification for manually implementing stack operations that encode and decode contract data.

Examples of erC20-like interfaces:

ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes
# this is a comment
selfBalance -> balance:uint64
address:UniversalAddress balance:fn -> balance:uint64
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint64
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable

Copy the code

You can also use arrays:

ArrayExample
#declares someFunction takes an array of 20 bytes exactly
someData:uint8[20]:fixed someFunction:fn -> void
#declares someFunctionDynamic that takes an array of no more than 20 bytes
someData:uint8[20]:max someFunctionDynamic:fn -> voidCopy the code

Basic types supported:

  • uint8

  • uint16

  • uint32

  • uint64

  • int8

  • int16

  • int32

  • int64

  • char

  • Void — only valid for returned data. No data is returned

  • Fn – special

More advanced types:

  • UniversalAddress

Base types are used as values as possible. Advanced types pass references. Arrays are passed by reference and point to values, including advanced types.

Array type:

  • Fixed (default) – Data must be the exact size specified

  • Max (specify maximum) – Data cannot be larger than the specified size. If it is large, an error is triggered

  • Dynamic (dynamic) – any length is valid (use the former uint8[])

  • Clip (Truncate) – If the data is larger than the specified size, it will be truncated without any errors


Function code

Function numbers are constructed in a similar way to Solidity. The SHA256 hash consists of the function line and the interface name, truncated to the next four bytes as the function number.


Memory allocation

Arrays larger than 256 bytes use heap allocation instead of stack.


interface

A single contract can implement multiple interfaces. Each interface uses the interface name prefix to generate code. Multiple functions with the same name can exist in the same contract as long as they are defined by interfaces with different names.

Including other interfaces:

MyContract

:interfaces ERC20, ERC721, MyParentContract

Abigen automatically looks up ABI filenames in the current directory and implements methods to specify the global interface directory.


language

Only C is supported now. Rust will be supported later.

Example (manually generated) C code:

struct simpletoken_Send_Params{
   UniversalAddressABI* address;
   uint64_t value;
};

struct simpletoken_Send_Returns{
   uint64_t recvvalue;
   uint64_t sendervalue;
};

void decodeABI(){
   //format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
   //format: address:address BALANCE -> balance:uint6
   //format: SELFBALANCE -> balance:uint64
   uint32_t function = 0;
   if(qtumStackItemCount() == 0){
       //fallback function... 
   }
   QTUM_POP_VAL(function);
   switch(function) {case CONTRACT_SELFBALANCE:
       {
           uint64_t resBalance;
           selfBalance(&resBalance);
           QTUM_PUSH_VAL(resBalance);
           return;
       }
       case CONTRACT_BALANCE:
       {
           UniversalAddressABI address;
           QTUM_POP_VAL(address);

           uint64_t resBalance;
           balance(&address, &resBalance);
           QTUM_PUSH_VAL(resBalance);
           return;
       }
       case CONTRACT_SEND:
       {
           struct simpletoken_Send_Params params;
           UniversalAddressABI __tmp1;
           params.address = &__tmp1;


           QTUM_POP_VAL(params.value);
           QTUM_POP_VAL(__tmp1);
           struct simpletoken_Send_Returns returns;

           send(&params, &returns);
           QTUM_PUSH_VAL(returns.sendervalue);
           QTUM_PUSH_VAL(returns.recvvalue);
           return;
       }
       default:
           qtumError("Invalid function");
           return;
   }
}

//format for this:
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,
   const struct simpletoken_Send_Params* params,
   struct simpletoken_Send_Returns* returns
   )
{
   if(__gasLimit == 0){
       __gasLimit = QTUM_CALL_GASLIMIT;
   }
   qtumStackClear();
   QTUM_PUSH_VAL(*params->address);
   QTUM_PUSH_VAL(params->value);
   uint32_t f = CONTRACT_SEND;
   QTUM_PUSH_VAL(f);
   struct QtumCallResultABI result;
   qtumCallContract(__contract, __gasLimit, 0, &result);
   if(result.errorCode ! = 0) {return result;
   }
   QTUM_POP_VAL(returns->recvvalue);
   QTUM_POP_VAL(returns->sendervalue);
   return result;
}Copy the code


other

For languages that do not support built-in array sizes, arrays of indeterminable arrays also have a “length” argument exposed to the Caller and Dispatcher.