Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
161
0
0
" l1 g/ G. O3 j# X! j9 {" w- q
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。2 n1 Q. T1 H; ~: L& e
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。$ G- d! O/ _0 d
. L. [9 t- Q. B
abigen 可以以下 3 种方式运行:
19 {' k9 q2 Z5 G# h% F0 X
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数
26 o% L: R& O6 b
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约: v, o- ]( J9 m% n3 H
3! G8 @) W. n$ a2 A. j6 F0 \0 R7 |
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约
$ j5 [4 T$ U+ w5 f0 Z8 r$ V" `
与 Solidity 的区别是什么?
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。
9 h, v- m9 O/ J. ?" W
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。! s7 f' u2 u3 A: z% P
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。6 v& l* R1 g* r' E6 v4 w0 a
& o; t# ]/ Q6 v8 e: ^
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。
% ^5 E" ~6 C6 }2 J1 w( E! ?9 E7 D
ABI 规范
+ E Y' x) k! R$ b6 S' A! J
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。
类似 ERC20 的接口示例:
ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes7 j' E5 b) W' b- I& p" ^6 k0 K
# this is a comment9 c1 {2 y; a( o) U5 V0 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 -- payable
也可以使用数组:9 Q( ^6 @: O4 Q8 c
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 bytes2 e& V& z7 i1 s% [3 l8 S9 ^
someData:uint8[20]:max someFunctionDynamic:fn -> void$ n* O' g P: L# r8 @6 s0 K: x4 x, ~
支持的基本类型:6 I& {6 o. x# _+ Q# _$ L, `( W( q
3 m2 w1 X& n& V3 c* N
uint8+ H6 M; @5 T, b# j8 s# R. S' Q9 R
uint16
uint32' E" b* {0 K8 e+ F/ N! X9 H
uint64% {: m- |% W+ N* a3 y
int8, G9 x- l- w, v9 X; h; |4 x. I
int16
int32; U3 b: e$ |/ @
int64: d6 |3 P4 H3 B5 x4 L5 ~
char4 w5 K/ r$ E3 V F7 ~" s/ f5 w
void -- 仅对返回数据有效。对应无返回数据. b2 F. @0 A* U. `$ U6 w
fn -- 特殊
更高级的类型:
UniversalAddress; Y- Y' Z3 w& w' l5 l4 I, b" u
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。* n4 Z1 c9 I- }4 A9 j7 H; S
数组类型:
fixed(默认) - 数据必须是指定的确切大小5 R' }/ d- w( ]+ ]% F0 m
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误0 J" Q6 k9 o6 ?" a# D
& `* }9 S% K6 c; W4 U
函数编号
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。
内存分配
大于 256 字节的数组都使用堆分配而不是栈。
接口2 g: w$ S6 e) ]6 x
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。
包括其他接口:
MyContract
:interfaces ERC20, ERC721, MyParentContract3 e2 \2 v& j+ J b" z- c' ^9 x
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
& S+ K7 p7 s0 O
语言
现在只支持 C. 之后会支持 Rust。) b& v9 F7 ], h% M
示例(手动生成) C代码:& w# l5 `& M0 m4 @
struct simpletoken_Send_Params{/ ` u6 p( j7 Y
UniversalAddressABI* address;
uint64_t value;6 {1 J: z2 f0 ?$ x% ]6 G
};
struct simpletoken_Send_Returns{
uint64_t recvvalue;. y% `8 G7 [* T. M
uint64_t sendervalue;1 H' p/ _; p; C5 K t; D
};5 t% x; t+ q: s+ b+ `6 J1 W
void decodeABI(){
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint646 p8 \, C5 J& V1 V
uint32_t function = 0;+ p7 o; c8 g+ V! |: G9 ~
if(qtumStackItemCount() == 0){: Y( p, v6 b3 d
//fallback function... ) P# u L; g& I
}
QTUM_POP_VAL(function);
switch(function){3 a, J9 W' `* @8 [& @% P: u+ n
case CONTRACT_SELFBALANCE:% V" |% G# p { \6 {& K, P
{
uint64_t resBalance;# w( y. I7 `: s7 }. S
selfBalance(&resBalance);; X" s+ @0 O- R5 | y
QTUM_PUSH_VAL(resBalance);
return;
}% O/ S( }3 Q5 F, a# [3 \
case CONTRACT_BALANCE:6 f' ]0 E" U' W3 ^9 t
{
UniversalAddressABI address;
QTUM_POP_VAL(address);
uint64_t resBalance;
balance(&address, &resBalance);
QTUM_PUSH_VAL(resBalance);
return;2 B6 V' B) l( V) X$ k* b
}
case CONTRACT_SEND:
{1 R" M, i1 H- ^
struct simpletoken_Send_Params params;
UniversalAddressABI __tmp1;
params.address = &__tmp1;
5 n" P8 P" {4 h2 C
QTUM_POP_VAL(params.value);* N# Z ?- t3 v0 w! j; x
QTUM_POP_VAL(__tmp1);; {& \6 s7 L( E. c6 W7 v
struct simpletoken_Send_Returns returns;- ]* c/ `4 g& v
send(¶ms, &returns);/ |% p5 r3 a( { N) _5 ?$ v
QTUM_PUSH_VAL(returns.sendervalue);
QTUM_PUSH_VAL(returns.recvvalue);
return;
}
default:
qtumError("Invalid function");- O7 r/ Y! n" l$ O6 ]
return;* @( Z- J3 t2 n( v) a
}
}9 x4 ~) v$ k% D3 h. `
//format for this:6 X3 p# z Z: \; S1 Z7 {, m3 k: r
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,
const struct simpletoken_Send_Params* params,. I6 o5 w4 y8 x; u7 V Z6 [# V) q
struct simpletoken_Send_Returns* returns
)
{4 s! P' V+ n1 L0 _- ^
if(__gasLimit == 0){ o" J/ w8 `7 N5 G" }1 U! m8 n
__gasLimit = QTUM_CALL_GASLIMIT;
}
qtumStackClear();1 q. f0 T( o# o3 O S( G
QTUM_PUSH_VAL(*params->address); H+ _3 f! i y+ m5 B- Z8 h
QTUM_PUSH_VAL(params->value);% T/ G7 L6 F+ c9 g- ]5 C
uint32_t f = CONTRACT_SEND;
QTUM_PUSH_VAL(f);
struct QtumCallResultABI result;
qtumCallContract(__contract, __gasLimit, 0, &result);0 Q" W# x$ T+ b3 d8 b
if(result.errorCode != 0){
return result;. P/ l' l- b2 r) N- H
}. J3 H: }! Y9 I( E! n$ K% c
QTUM_POP_VAL(returns->recvvalue);
QTUM_POP_VAL(returns->sendervalue);/ v3 t3 x! v2 b& [) X
return result;; h4 ~- f4 @6 b& d4 J
}
& [/ Y0 [* u9 \& x, Y+ L4 k
其他. o* t. T6 D' I( h- \# ^
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人



