Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
136
0
0
4 I% M1 N8 w3 B/ }0 K3 o0 c
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。/ t) j) W' u5 J& z0 {
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。
abigen 可以以下 3 种方式运行:' V, ~' I6 q1 X8 A3 u
1- X0 F9 H! ~4 h: q9 G
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数% ~$ ^0 t( z6 A5 D
28 k( ]* [" k' W- V( K% t1 M
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约5 P+ X! }! Q* X$ P" X' g+ l: q; I; {% x; e9 K
3& t, [ r: ]1 r2 ]: ^; M/ O5 }
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约
与 Solidity 的区别是什么?
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。" t) Z6 d2 v j/ b
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。$ Q( `$ `- `" T, s& g
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。
- S' P& H3 D8 T
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。
* b0 d5 C1 W4 G0 i
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。9 o( G6 g/ B; f! N0 P+ o p( b
9 f/ G6 h5 Y4 q5 [3 O
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。# e; j7 B3 n' Y) |' N1 ^: S
) d) d* _4 b/ |% \
ABI 规范/ g7 J1 |1 X: F
0 M$ a. l5 H9 P! K- o; R5 |
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。) ?6 @- v" i' a: M
类似 ERC20 的接口示例:' }6 N5 J; L i9 ?3 ]" E' h) f
ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes7 p: b3 ?3 w' b. _0 [7 ^7 I
# this is a comment
selfBalance -> balance:uint644 u) r. n8 r. F# E
address:UniversalAddress balance:fn -> balance:uint64
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint643 ]. g/ E& N; @. `1 l0 g
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable! U7 p9 U* d8 V5 X% D7 ~
7 F- ?' E! ?' M( }) x1 k
也可以使用数组:
ArrayExample
#declares someFunction takes an array of 20 bytes exactly! i6 Q# S9 j" B! J: o, \! J9 _5 |
someData:uint8[20]:fixed someFunction:fn -> void( L7 C. { K# {# \- z8 z
#declares someFunctionDynamic that takes an array of no more than 20 bytes# n; p4 n% A; [3 j% o7 C
someData:uint8[20]:max someFunctionDynamic:fn -> void
支持的基本类型:
8 Z6 F# G! N+ B- }. G( q2 c
uint8
uint162 A1 H# y4 ~& x2 W4 E0 Z( @5 N
uint32
uint64& B3 ~6 i+ n$ W) H! F$ J, E6 C
int8
int16
int32
int647 Q% p* X0 V9 \9 B3 E
char( H( k7 }; w0 T: B
void -- 仅对返回数据有效。对应无返回数据 e- n" g8 I6 _
fn -- 特殊- u. ?" y: N P8 K' c. x, D0 Q
更高级的类型:
UniversalAddress/ J: s3 M5 A7 ]- ]2 m9 B4 ^
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。# w9 _( b4 V: E1 W' X/ `1 n
数组类型:9 E0 y! ~; d/ B0 t/ U1 |
fixed(默认) - 数据必须是指定的确切大小+ S2 m: U$ K4 i' y) X
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效- L5 D' @- R& E5 A' ?5 ~9 O1 X
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误( g8 c. Z; b* R# T0 ^; w1 Y
函数编号/ L j, M9 ]* c ^$ L$ C7 t0 X
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。
3 ~3 u7 Z# d1 U
内存分配
大于 256 字节的数组都使用堆分配而不是栈。
3 l; Q' e8 F& s: d- n7 m- C' l f1 l9 s, O
接口
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。9 N/ N( E9 g$ F1 `6 s) I
包括其他接口:
MyContract
:interfaces ERC20, ERC721, MyParentContract2 ^, {& E# V. d# U
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
语言6 T* }& {0 L: h2 g# p( l/ I
现在只支持 C. 之后会支持 Rust。
示例(手动生成) C代码:* B, P% T) g f+ G0 @
struct simpletoken_Send_Params{- G. M7 _+ T+ V% o( Y
UniversalAddressABI* address;. w7 E0 e& C; u
uint64_t value;2 l# N( \$ }! {0 W
};: `( n* H, A) R: H; g& t! q
struct simpletoken_Send_Returns{
uint64_t recvvalue;
uint64_t sendervalue;0 G6 b. q( P1 I4 V
};) g" ^3 e$ m s; r" X% E }
void decodeABI(){4 a9 v% t1 y( p7 a0 B/ }( b7 q
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint647 X$ r" J* v6 r! G
uint32_t function = 0; M0 Q1 N+ R, i# [7 U d3 l) W
if(qtumStackItemCount() == 0){0 v) w" x/ k( f5 p( D% Y
//fallback function...
}
QTUM_POP_VAL(function);: T: u4 M- w' ]) q) W, }$ N& Z
switch(function){
case CONTRACT_SELFBALANCE:
{
uint64_t resBalance;
selfBalance(&resBalance);# i E Q0 {" v: {9 Y0 w4 ]
QTUM_PUSH_VAL(resBalance);0 P; D9 g5 ]& }9 J3 ~) k: Y
return;& Z4 [) I( v' K/ a* h+ @$ o
}
case CONTRACT_BALANCE:5 w! c0 {% T* H6 z n
{
UniversalAddressABI address;; }( P1 f5 b8 x4 C3 T
QTUM_POP_VAL(address);3 x# t0 R/ q& U$ I) l9 }( g
uint64_t resBalance;
balance(&address, &resBalance);
QTUM_PUSH_VAL(resBalance);
return;
}
case CONTRACT_SEND:
{
struct simpletoken_Send_Params params;1 t' y- M" ?) z
UniversalAddressABI __tmp1;
params.address = &__tmp1;7 r* l; H$ K A0 D" }, a
QTUM_POP_VAL(params.value);
QTUM_POP_VAL(__tmp1);9 H. `' j) n) E: C" N/ e
struct simpletoken_Send_Returns returns;5 v. C a3 y/ s7 z& E
send(¶ms, &returns);- D% h. A: G5 J+ e& ^, m
QTUM_PUSH_VAL(returns.sendervalue);
QTUM_PUSH_VAL(returns.recvvalue);
return;
}4 x4 O9 q( @* s# m& M6 f& q j
default:
qtumError("Invalid function");
return;/ u, V- Z- l6 l2 H4 ?
}
}- X) i) R9 X* S1 e9 K* v8 `( Q
//format for this:' D7 K5 M' E+ O+ \/ _
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,, a5 A3 u% ^8 M) ^& f/ F0 m6 K
const struct simpletoken_Send_Params* params,
struct simpletoken_Send_Returns* returns
)
{
if(__gasLimit == 0){/ \& f8 k9 B3 {. @% q& F6 U
__gasLimit = QTUM_CALL_GASLIMIT;
}/ v* M9 r! O" N2 }1 H( \( x
qtumStackClear();& h& c0 d9 Q; |: g0 i/ ~$ b8 C
QTUM_PUSH_VAL(*params->address);
QTUM_PUSH_VAL(params->value);) B; N% }4 T; d$ k! Q; m# F1 l
uint32_t f = CONTRACT_SEND;1 [* N: W b( v+ B9 f& _5 P( k" m9 x
QTUM_PUSH_VAL(f);5 E" r. [, ~+ E( \) p8 \& c1 k" G5 a
struct QtumCallResultABI result;4 I6 h; c3 |5 ?$ y& ?8 t
qtumCallContract(__contract, __gasLimit, 0, &result);
if(result.errorCode != 0){
return result;( b2 P# J; {) K+ y& r
}
QTUM_POP_VAL(returns->recvvalue); ~0 J t7 v6 W. g w% D+ q
QTUM_POP_VAL(returns->sendervalue);
return result;
}! Y, i2 j( {7 d1 i1 c
其他/ c9 p9 ^3 n$ c6 t7 E* f
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人