Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
88
0
0
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。
! n5 K" ] o7 G* E3 M8 U9 Y; C
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。' a# l0 o+ l: {9 e; M
abigen 可以以下 3 种方式运行:
1
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数
2$ q0 G& n& W0 c3 A* @$ `5 d
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约1 C7 ~" D3 F7 T" Z
3 U0 S; g$ I- ^8 U' w) M' y
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约
8 {1 Q; u' K3 b3 b2 d0 ~' ^
与 Solidity 的区别是什么?2 V+ x/ P( p$ C2 @! I8 U
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。" }5 A" o/ G: } {
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。; J+ y3 M3 Z& w" \* r
9 m0 U* h( }7 ~* m
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。5 X9 m8 v6 E7 y
- @, e1 C' O# F$ D+ t
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。* A) c/ u# M+ Z' a7 \
! Q7 h& B/ f3 } Q
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。
' d" n( o% @% L& J2 ^
ABI 规范
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。* l. D h+ ?$ B6 Q# i
类似 ERC20 的接口示例:
ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes$ V/ o! b7 A/ s: i4 o
# this is a comment; n3 \/ v; ` O9 v
selfBalance -> balance:uint64
address:UniversalAddress balance:fn -> balance:uint64
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint647 M/ Z8 {8 Y7 X! e, s: e9 M+ s
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable; @& B) t; a! {# M! E7 S/ ?1 @9 I
! P/ N* \: ^4 @% J) d; ~+ Q
也可以使用数组:( g+ ^6 H" z) }7 ~% i
ArrayExample
#declares someFunction takes an array of 20 bytes exactly3 j" b" L2 n' w, ~2 a7 Q. r
someData:uint8[20]:fixed someFunction:fn -> void, |! G8 Y' K, K& a1 g) x6 x. m
#declares someFunctionDynamic that takes an array of no more than 20 bytes# [& J2 X1 K' r. I% [
someData:uint8[20]:max someFunctionDynamic:fn -> void
支持的基本类型:' H1 V9 {0 m- N( I5 A& B& N+ @8 D
) L- B( s5 t( B3 _# S+ [- _4 [+ W
uint89 k( V1 I# E1 ^- _# Q
uint16+ w) ^: I6 B( z' U
uint32, i: z0 p$ C: Z! n+ S
uint64
int8
int162 u% y( E; X) Y
int322 ]1 D7 F ?1 V. a& ?! j
int64
char
void -- 仅对返回数据有效。对应无返回数据& j9 l& {) ~9 y1 J- _) N- q" C
fn -- 特殊
更高级的类型:
UniversalAddress
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。
数组类型: K* q8 A p; @ u& b! R& w' b( |
fixed(默认) - 数据必须是指定的确切大小
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误$ g) R/ y; b7 V* U
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误
/ ^( ]' z& T4 \2 c% b$ A
函数编号
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。) f; y+ ]% x8 ^: Q/ s$ O1 q b8 @4 J Z
! f5 v/ p7 t9 x/ H! |) C: K, G# n
内存分配
大于 256 字节的数组都使用堆分配而不是栈。2 B: n$ v9 ]! K
接口
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。; d- P2 @) m2 O3 u" {) I7 e
包括其他接口:
MyContract# h, U1 j) o& Q2 r7 O6 s
:interfaces ERC20, ERC721, MyParentContract
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
G3 ]& \5 |1 I6 i+ q$ D
语言& Z8 j1 g E2 l& A; @
现在只支持 C. 之后会支持 Rust。
示例(手动生成) C代码:
struct simpletoken_Send_Params{( |8 F$ w U2 x5 S
UniversalAddressABI* address;! \ _- O8 z1 y% K
uint64_t value;8 X7 @6 K* r! S- }+ w# J$ n
};
struct simpletoken_Send_Returns{
uint64_t recvvalue;8 f4 L* K$ i" P" G" b
uint64_t sendervalue;
};
void decodeABI(){" J$ P8 N Q! W$ f) X0 N Q
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64& v; X+ i; s# p% N+ Y3 X$ j
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint64# w5 Y5 j3 T' A ~- H- e1 K! G' w( b/ q
uint32_t function = 0;
if(qtumStackItemCount() == 0){
//fallback function... ; f3 A" t! D' y& G( v6 e
}9 K5 v& q- n8 K. A
QTUM_POP_VAL(function);$ p2 U% r) G) Z0 z0 u
switch(function){7 ], C/ a2 G. {5 ?+ S& _, U5 N/ T
case CONTRACT_SELFBALANCE:
{! X9 Y- T( v% @' r+ n
uint64_t resBalance;
selfBalance(&resBalance);* k# p; z; m. X: k% _' ]7 o* _
QTUM_PUSH_VAL(resBalance);
return;# k5 t9 T9 u; Z0 z% ~% ?
}
case CONTRACT_BALANCE:9 J& z8 K1 y8 @+ f/ K6 ?2 F
{
UniversalAddressABI address;& g9 X6 j9 z% J! F
QTUM_POP_VAL(address);
uint64_t resBalance;& Q2 B* Z/ `" T% M1 ~ g* I
balance(&address, &resBalance);" ], w# b: D. U3 T- h, k \# e8 C: n
QTUM_PUSH_VAL(resBalance);
return;
}
case CONTRACT_SEND:
{
struct simpletoken_Send_Params params;
UniversalAddressABI __tmp1;. A# O3 H, w _5 u6 N; b9 V* S
params.address = &__tmp1;, j% o9 J5 f; r: ~5 D% N( b
QTUM_POP_VAL(params.value);
QTUM_POP_VAL(__tmp1);
struct simpletoken_Send_Returns returns;
send(¶ms, &returns);
QTUM_PUSH_VAL(returns.sendervalue);0 |3 X7 ~0 B; N2 L, o
QTUM_PUSH_VAL(returns.recvvalue);4 j( a7 T: T& n- @* U" A
return;- j* Y: j' v; f$ Y1 k; e
}7 N% q6 `3 X3 X
default:
qtumError("Invalid function");
return;
}
}8 O% z" n: p# d, G5 X5 G
//format for this:
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint646 K. q! z8 J) m- _
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,7 s2 s6 q/ ]: \
const struct simpletoken_Send_Params* params,
struct simpletoken_Send_Returns* returns
)
{
if(__gasLimit == 0){
__gasLimit = QTUM_CALL_GASLIMIT;
}
qtumStackClear();: |) y6 c4 `7 _' Z. [: u% {- g' X' e5 u
QTUM_PUSH_VAL(*params->address);
QTUM_PUSH_VAL(params->value); }% u" G( y7 g
uint32_t f = CONTRACT_SEND;
QTUM_PUSH_VAL(f);
struct QtumCallResultABI result;
qtumCallContract(__contract, __gasLimit, 0, &result);1 E2 \3 O s: L# @
if(result.errorCode != 0){1 a3 ^9 E6 _* a6 m1 Q: s
return result;) ]' E' ?/ ~* ]- {% I7 v1 E5 z& }
}: |! H8 ?" n$ @" D$ {% s. d% Y
QTUM_POP_VAL(returns->recvvalue);4 H g) O( d, j) l
QTUM_POP_VAL(returns->sendervalue);0 e* t7 k, X! I9 K
return result;0 @9 I+ |+ m5 U/ I- \% g/ Q
}7 g E6 f9 U* X- ?
其他) s( @9 z3 g: Z
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人