Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
86
0
0
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。
abigen 可以以下 3 种方式运行:0 C: X, q T2 X: r/ V. Y0 b
1
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数
26 u2 a. v. d. r) L3 c3 j* G: n
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约
33 u5 H8 X# J5 i* @3 T% d
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约1 v& Y+ M. C1 l( x0 z% f$ j
与 Solidity 的区别是什么?. d! c- U+ a% }' a9 j! O
8 j/ V- d8 G3 W3 \8 Y
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。, F0 C' U: y( j$ S, z6 u0 G
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。
" A" |8 x# t- ^. ?+ S4 A
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。# f+ x1 w6 r3 m& L
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。: M, l# A# R$ p* v9 M: P; j/ k, J4 O
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。' |- c$ }; E; M9 T
. [& a6 Y5 k, M
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。
ABI 规范* q% N: O; N: Y9 l( N) N' m5 M
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。
类似 ERC20 的接口示例:0 f& Y: b. r. R
ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes$ ]* j. `7 B% U& d
# this is a comment
selfBalance -> balance:uint64
address:UniversalAddress balance:fn -> balance:uint64
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint64" {& |$ m. `# j9 b7 n5 U
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable
# _6 c1 V9 A' ~
也可以使用数组:) ~$ a& b2 l: y/ K- s5 F
ArrayExample
#declares someFunction takes an array of 20 bytes exactly: ~# V4 j7 Z! P8 I+ ]! v) u2 D: o
someData:uint8[20]:fixed someFunction:fn -> void" y" T. k1 r8 H( j
#declares someFunctionDynamic that takes an array of no more than 20 bytes
someData:uint8[20]:max someFunctionDynamic:fn -> void4 n2 n O1 Y" h) O1 i4 C S9 r
支持的基本类型:" [$ `8 A) l3 b! B% {
1 ~5 P; p# z5 P4 B5 D' `5 T
uint8
uint167 v8 A1 j6 @* O! s
uint32. E' E3 O! A3 u9 `7 E" M+ ^' u
uint64
int8# j. U- p7 V( s3 J
int164 [1 T) T- Y3 ?4 ^" j1 O8 W
int32
int64
char. v- d, V1 r" B3 b+ A) ^; R
void -- 仅对返回数据有效。对应无返回数据9 R: _$ H( k. A- q, U7 Y) c$ b
fn -- 特殊
更高级的类型:
UniversalAddress
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。
数组类型:# s' |8 O& u: V( S2 V) H9 V
fixed(默认) - 数据必须是指定的确切大小
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效& s/ p% Z* T, {- D. d& N
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误% z5 ?3 k3 c* V
% N5 J8 c! J! l6 |' k% E" E6 q
函数编号$ t" O7 F0 W7 k/ r' a
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。
' c6 x, _" y( g! j) {, k) S2 C0 f' ]
内存分配: Y0 D2 z( W$ r1 T
大于 256 字节的数组都使用堆分配而不是栈。
# [ m2 u/ Q4 r. m' U
接口! e$ k$ ~) i! I T) k* \7 h
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。 {/ Q" ?8 ~3 A+ ]
包括其他接口:
MyContract( f# q$ X+ i& X7 \- ~9 a0 k
:interfaces ERC20, ERC721, MyParentContract
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
& q S* m' u* Z7 v1 ^
语言( y; ~$ i& o$ K1 q1 e; m
现在只支持 C. 之后会支持 Rust。
示例(手动生成) C代码:
struct simpletoken_Send_Params{
UniversalAddressABI* address;; t y% Q. z0 h# s9 F
uint64_t value;
};
struct simpletoken_Send_Returns{
uint64_t recvvalue;
uint64_t sendervalue;$ p" b9 t0 h3 d" H* L+ @+ v2 P
};% U! i$ c; i2 r$ o/ [" C
void decodeABI(){
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64% D$ R! d' w; z& D' a6 O0 `
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint64
uint32_t function = 0;) ]5 u* w! A+ K U9 x/ G1 Y; E
if(qtumStackItemCount() == 0){9 N. F2 f9 \; x9 W9 N
//fallback function... ) m/ W1 V u$ ] a# g; n( ?
}" X2 c: J& k% |, }7 {8 f# M2 C
QTUM_POP_VAL(function);
switch(function){
case CONTRACT_SELFBALANCE:
{
uint64_t resBalance;5 f+ w6 C' J, V/ b: T4 J$ N
selfBalance(&resBalance);* x$ B. g4 t6 P9 D
QTUM_PUSH_VAL(resBalance);
return;9 J( g3 \' B3 ^& ~, b1 m; P
}5 o8 P T, Z3 X, w( M: A
case CONTRACT_BALANCE:
{
UniversalAddressABI address;6 q3 d9 x5 ~6 F2 i
QTUM_POP_VAL(address);
uint64_t resBalance;1 J% I6 W/ L, B
balance(&address, &resBalance);
QTUM_PUSH_VAL(resBalance);
return;( M6 Y6 e8 Q& J& ] _2 @- o- G
}
case CONTRACT_SEND:
{7 |) w" Y- i+ A$ j, F/ G w
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(¶ms, &returns);5 Q* |: O6 R, K4 i% \: x
QTUM_PUSH_VAL(returns.sendervalue);
QTUM_PUSH_VAL(returns.recvvalue);! Y2 B/ I0 z. v% `0 ~. Y
return;
}" ^; ?( b; @* N6 j8 t1 V
default:. h r0 ?/ P: l3 [) T; b
qtumError("Invalid function");
return;! Y( t! `6 \3 ^3 x' n
}! S% R0 a3 r O* \1 q j9 ~$ E' Q
}
//format for this:
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64) Z) U+ U* A: k7 u
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit," U s$ y! U; D6 b" {: ^6 l
const struct simpletoken_Send_Params* params,
struct simpletoken_Send_Returns* returns9 H: M }( f3 n6 [6 m
); N2 t7 t5 E' A/ V8 K: ~
{
if(__gasLimit == 0){7 p- |: U ^. a4 E, L m- s0 @
__gasLimit = QTUM_CALL_GASLIMIT;; L0 z1 f% c2 L6 _: B# g
}
qtumStackClear();; H+ R3 {: B7 ]1 I9 b) L: {
QTUM_PUSH_VAL(*params->address);6 \5 n! u3 ^2 H- P/ x3 \' j
QTUM_PUSH_VAL(params->value);
uint32_t f = CONTRACT_SEND;- \2 K! O8 f2 a) b& ?3 y" Y* f
QTUM_PUSH_VAL(f);
struct QtumCallResultABI result;0 O. D5 @% Z! u# I3 D' F* V+ N( m, b
qtumCallContract(__contract, __gasLimit, 0, &result);
if(result.errorCode != 0){% r9 R3 F9 v: z& r; q; z
return result;" F% p' w; |! }2 f* Y
}
QTUM_POP_VAL(returns->recvvalue);# n3 E& u3 t2 S( {" O7 b- P
QTUM_POP_VAL(returns->sendervalue);
return result;7 B3 }% ]. i& Y3 K; n5 }- ~+ D
}& B' \: k/ D9 k( L
其他: h8 R" [- C/ h3 z# A
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人