Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
89
0
0
7 L; f: {' ?: H3 g& i0 P2 X
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。& `$ j9 S4 D% e$ _6 Q
k3 _8 d8 Y& T) r$ x
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。% k5 o" h: r8 o* Y/ c' n
abigen 可以以下 3 种方式运行:0 A% G- J3 e- z$ t, D
1
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数; R. N' A! W6 j. Q/ V
2+ {4 Z6 r, u: W5 v; v' P0 d& v
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约# E! \2 z& o6 q! o- _( L
3
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约
7 f) v& w% L, ?9 \8 H! f6 e
与 Solidity 的区别是什么?, q( E$ D; o6 q( D- v0 s
6 J( e9 c) d& U6 d8 _2 ^) q
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。$ [/ n$ U) [: X5 X1 |1 p( H9 {
8 y" R/ L4 J; O" c: {
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。! b, [4 ^7 z: V" @* g
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。
0 u) i* r& [. {! u% w5 e# G
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。9 ?) A* I" ]1 V; H* C5 N
ABI 规范
" c' d) k$ Y9 i
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。
类似 ERC20 的接口示例:
ERC20Interface( H& @6 M9 c! `8 g. @$ ]
# The first non-comment line is the name of the interface and used for all codegen prefixes
# this is a comment2 d; D4 y8 }8 w( _3 Y
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 -- payable6 _! n5 ~# P3 `" p3 z, q
: ]! t/ @( L( Q# \0 I5 s8 b/ Z$ p' `
也可以使用数组:
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. R7 R0 S" l6 k) i# @
someData:uint8[20]:max someFunctionDynamic:fn -> void1 ]7 c( {% p5 w8 ?" s' |, n' Z6 W
支持的基本类型: g/ |9 Z6 D! T" \& r) R
4 t Q M$ T) g; b, w4 o
uint8
uint16
uint32
uint64
int8
int16
int32
int645 `- N E( f* Z
char: `8 T& k) Y) O3 |) x* E. C
void -- 仅对返回数据有效。对应无返回数据
fn -- 特殊, F! `1 |6 O( {6 p/ S. A
更高级的类型:
UniversalAddress! K3 \, M S% j G
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。
数组类型:
fixed(默认) - 数据必须是指定的确切大小2 g! I6 r# Q6 c# p, r% Z* z2 u9 i* Y
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误3 L1 z7 m. }2 S& G2 r
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效+ l& M# G) A% Y' h
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误
函数编号& w) ^/ ?, f. R* ~& p1 X' x# A
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。% N& r& S: Z5 F3 K7 ~1 y
8 n9 U+ z2 A. Z/ B9 d* g' g- b H
内存分配) ~$ U$ B$ `$ j( k% n! ^
大于 256 字节的数组都使用堆分配而不是栈。' d1 g4 Y0 P( N
H4 _' k' K1 Z/ w' W2 z
接口! [) N! }8 v5 D+ g4 z
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。
包括其他接口:
MyContract
:interfaces ERC20, ERC721, MyParentContract* g) c& W. c2 O+ d3 l
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
语言
现在只支持 C. 之后会支持 Rust。
示例(手动生成) C代码:0 z2 w3 y9 x& A3 n5 \5 S
struct simpletoken_Send_Params{
UniversalAddressABI* address;
uint64_t value;
};. |. u2 q5 |, J
struct simpletoken_Send_Returns{
uint64_t recvvalue;
uint64_t sendervalue;' D8 M5 Q0 F- T5 m
};& t6 x) }1 y& \, i9 b
void decodeABI(){
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint645 f9 e/ C7 U7 o
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint645 x2 |' J4 p3 l/ y, g
uint32_t function = 0;
if(qtumStackItemCount() == 0){
//fallback function... & [. ], U1 j" K, p! Z( ?! D
}0 Y+ B" W7 M; r' V( ]* w7 g
QTUM_POP_VAL(function);
switch(function){/ k9 Q6 }2 W6 d& r8 i; o
case CONTRACT_SELFBALANCE:6 ~3 q# _& j8 {! q( A, X
{( Q& P5 q6 |% |7 `5 G0 P+ m; y h
uint64_t resBalance;
selfBalance(&resBalance);- J3 P9 Y/ \7 P- S7 J7 q9 R4 N
QTUM_PUSH_VAL(resBalance);8 ]6 Y2 _+ D; }' z2 H& x1 j
return;; h. n' Q7 g% Q
}
case CONTRACT_BALANCE:
{. y3 O& a$ q- D0 R, A/ J! h/ S
UniversalAddressABI address;0 _! X) I, M( e3 @$ c6 `0 D) V
QTUM_POP_VAL(address);% ]" b- R* t1 P
uint64_t resBalance;
balance(&address, &resBalance);
QTUM_PUSH_VAL(resBalance);" M* u! {* o. {5 ~( {6 e8 D) I, C# u! c+ I
return;
}0 G5 @7 Z/ b- r9 h! G6 @; z
case CONTRACT_SEND:
{
struct simpletoken_Send_Params params;
UniversalAddressABI __tmp1;% L2 F* N, H6 n2 o
params.address = &__tmp1;/ L$ Y+ B2 T9 r7 o
% e3 Z: R! D, N% M" w
QTUM_POP_VAL(params.value);
QTUM_POP_VAL(__tmp1);
struct simpletoken_Send_Returns returns;
send(¶ms, &returns);
QTUM_PUSH_VAL(returns.sendervalue);4 ~: q% t* w* w: U+ ? j( n
QTUM_PUSH_VAL(returns.recvvalue);) Q/ Z- @3 k* D- o; t
return;
}8 v- h4 t! \8 j3 {0 n. [& Z, R
default:
qtumError("Invalid function");8 R/ F, ]# E o+ I
return;3 G% \6 ~# R9 f/ o/ ~7 l' X
}1 U3 l& ]9 R2 I
}2 h: a% l8 G$ A& O9 c+ i$ l" N
//format for this:/ B O0 Y. z+ Z o! l
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint648 L! j. O+ t* y) e3 h8 ^0 Y* D
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,
const struct simpletoken_Send_Params* params,
struct simpletoken_Send_Returns* returns' |5 A6 d) t, Y! S" |
)1 x; V n7 b _3 }8 }
{! d/ n T: W, F+ O Q$ i9 g
if(__gasLimit == 0){
__gasLimit = QTUM_CALL_GASLIMIT;
}
qtumStackClear();8 T9 ^5 t) C) j: i2 S- J4 w
QTUM_PUSH_VAL(*params->address);
QTUM_PUSH_VAL(params->value);6 ?2 E- r) Q+ u
uint32_t f = CONTRACT_SEND;
QTUM_PUSH_VAL(f);( H- `) w. I9 ^# m
struct QtumCallResultABI result;$ X# N2 `: r8 {4 p6 x a
qtumCallContract(__contract, __gasLimit, 0, &result);! y2 h- M# f: o, ?% A
if(result.errorCode != 0){
return result;' x7 d! J$ D/ n3 b7 {% V; Z$ a+ c
}/ A) c# l" ~. V+ w# m! ~- s: _
QTUM_POP_VAL(returns->recvvalue);' S d$ [, r4 z; P* S
QTUM_POP_VAL(returns->sendervalue);
return result;
}
$ w n3 _% V/ z. `; v! n+ M# U
其他) i0 F, j4 }2 X! n7 n2 M
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人