Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
116
0
0
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。
& ~# o% V9 H: }6 y u, F( B9 {
abigen 可以以下 3 种方式运行:
1
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数; k2 O: e, b& u' t% M6 z" r
26 Y$ J$ d. j# z
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约, O( `6 q; Z x' T+ v
3
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约1 w: v' F" l% T) Q$ _7 I
与 Solidity 的区别是什么?
- C, D6 m3 K5 G9 i
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。# ^. |8 P: S# } `: n
: f; Y; E! R6 B+ x+ l% Y
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。
' K( P7 Y! h5 `, n
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。
% ]& q n$ L j
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。 E2 k, I9 q* z2 y* {8 U* Z
: N% S2 }: W: J1 E* W
ABI 规范
/ k5 h6 j6 j2 `: a: O( i( @
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。
类似 ERC20 的接口示例:1 {- N4 X4 }3 A
ERC20Interface1 o0 M, Z, w& j$ d+ O
# The first non-comment line is the name of the interface and used for all codegen prefixes
# this is a comment% w* |8 G5 m$ T1 t3 ]9 s
selfBalance -> balance:uint647 J5 @: E3 n) P; i( U2 |; k! D, M
address:UniversalAddress balance:fn -> balance:uint64$ G, ~2 ]. _% L5 C
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint64
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable
也可以使用数组:
ArrayExample
#declares someFunction takes an array of 20 bytes exactly1 S, d# p* M z* @
someData:uint8[20]:fixed someFunction:fn -> void9 v) @! {% b( s. y
#declares someFunctionDynamic that takes an array of no more than 20 bytes
someData:uint8[20]:max someFunctionDynamic:fn -> void& |4 `9 m" J/ d3 W- D4 t
支持的基本类型:5 S' |$ b; [0 P% r# p, H& {0 Y6 t
@$ g+ t& C# w* w8 ?: q
uint8
uint16
uint321 R+ ?) _4 q' R
uint64% b% s+ |: b0 t2 @
int8* ^8 F/ A9 _: Z* a6 Y) H9 Q
int16# M0 m0 U: x' l7 {, k0 z
int32
int64' n) r/ j; f$ u+ C
char+ i# F) x, B1 J. W* c
void -- 仅对返回数据有效。对应无返回数据) ^# s% w' v3 H3 T% Q: F/ E2 T
fn -- 特殊- e1 t$ c6 R4 j' p6 @. i
更高级的类型:1 H3 T: m" B8 o% i R q: m# p
UniversalAddress
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。
数组类型:
fixed(默认) - 数据必须是指定的确切大小$ G: B; r6 g+ C" N
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误7 ?$ R2 c+ x: \5 S% f' M
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误, @8 Z% ~3 W7 Z* N/ i9 i" J
! b& G9 v+ C) s& @# t
函数编号+ I b" V8 l: d
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。4 s2 a# t( T/ j# @. ~
6 B' s+ R* Q& w: y( P9 W2 ^- l; r* v& g
内存分配" [0 h0 T& z% o3 c p
大于 256 字节的数组都使用堆分配而不是栈。
接口
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。2 `1 j) c# B# k" _$ W
包括其他接口:
MyContract
:interfaces ERC20, ERC721, MyParentContract
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。! ]* T! m- J1 {/ C- T& O+ Z! a
语言
现在只支持 C. 之后会支持 Rust。
示例(手动生成) C代码:
struct simpletoken_Send_Params{4 Y/ B8 ^) x- Y4 C
UniversalAddressABI* address;, L" `, A" F* e- f# t% X% j
uint64_t value;
};: W' i3 y# c7 W7 I
struct simpletoken_Send_Returns{
uint64_t recvvalue;
uint64_t sendervalue;1 l/ R* j6 P4 Z* J/ `1 F; F
};
void decodeABI(){
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint64+ ]4 X' n4 s( B, h# K$ m
uint32_t function = 0; s( n0 C4 f. h# t
if(qtumStackItemCount() == 0){$ ^3 a {# n) S, _8 {
//fallback function... # t9 i" M( y' w# ^
}; s( r! B" e- q( C C! p4 J; g
QTUM_POP_VAL(function);, Q. |. s) w* x, F
switch(function){: C j Z! Z: y! |9 P
case CONTRACT_SELFBALANCE:
{
uint64_t resBalance;( \" B* C+ C. Y
selfBalance(&resBalance);
QTUM_PUSH_VAL(resBalance);
return;
}; X) [9 J. O, u. Z
case CONTRACT_BALANCE:
{% i8 I' d# P4 F3 u- n* J# a, A
UniversalAddressABI address;% O7 O" R, `8 p/ C
QTUM_POP_VAL(address);$ [! o/ H1 x, h P+ h
uint64_t resBalance;+ d; J; Z- ?5 `& s4 l2 b. o
balance(&address, &resBalance);' V1 C- p+ O+ K( s2 m
QTUM_PUSH_VAL(resBalance);3 |- r, o' R1 I& e4 m" u2 ?% V+ {
return;
}
case CONTRACT_SEND:. u7 f7 X( k* M( k; _5 E* p# K) E
{
struct simpletoken_Send_Params params;* T* X- s* ^- w3 S/ J/ {
UniversalAddressABI __tmp1;
params.address = &__tmp1;
QTUM_POP_VAL(params.value);
QTUM_POP_VAL(__tmp1);& D b. p5 E- ?) _% E1 }+ ?6 W( W5 N: M
struct simpletoken_Send_Returns returns;9 b( b6 j4 E) {) C( R7 K, D6 _
send(¶ms, &returns);
QTUM_PUSH_VAL(returns.sendervalue);
QTUM_PUSH_VAL(returns.recvvalue);
return;1 {$ `- d1 v( F0 q2 [) ^' y5 y5 S
}: t. N; {% R6 I
default:1 f3 v% ?& [4 R# q- C. v
qtumError("Invalid function");
return;
}
}. I }0 |4 s1 c ]7 \! J0 |1 {. Q- A _
//format for this:
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64; @& a' \' F& }2 v: }2 R' L/ U! q
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,
const struct simpletoken_Send_Params* params,7 t+ O4 z# P6 o' G; A2 o9 L
struct simpletoken_Send_Returns* returns
)
{
if(__gasLimit == 0){
__gasLimit = QTUM_CALL_GASLIMIT;% |2 p: E }$ `- J" f7 n, g' g
}2 j& L I8 h$ J- o) _4 w
qtumStackClear();
QTUM_PUSH_VAL(*params->address);
QTUM_PUSH_VAL(params->value);. ?! S; Y, Q b' e+ p
uint32_t f = CONTRACT_SEND;
QTUM_PUSH_VAL(f);
struct QtumCallResultABI result;
qtumCallContract(__contract, __gasLimit, 0, &result);% P l ?# [; [- L) a
if(result.errorCode != 0){. D) ~- D! d; D$ o4 q1 a' v- f* b& x
return result;
}. t( E% W4 X0 ^& a( p* q5 B2 B
QTUM_POP_VAL(returns->recvvalue);' A! U8 L# G# k) r
QTUM_POP_VAL(returns->sendervalue);1 S$ D8 ~% ^6 I* u X6 {8 x
return result;; Q1 Q( j. j; B) \0 ^% r
}6 j6 [* V9 R- `; A* }' r" ^
其他7 X$ ]* m) ]" M6 A" @5 h( r
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人