Qtum:x86 SimpleABI 协议与 abigen 工具
杨小公子君莫邪
发表于 2022-12-5 05:48:51
157
0
0
这是 Qtum x86 合约的一个轻量级的 ABI。这个 ABI 规范称为 Simple ABI。
SimpleABI 只编码字段值(flat values)和简单数组(simple arrays)。它不是智能合约 ABI 的终极状态,只是实现起来非常简单,最重要的是使用起来非常顺手。, N9 z, ]) p# _
abigen 可以以下 3 种方式运行:
1; o: {% M; E2 e' r |
Dispatcher -- 生成代码,用于解码 SCCS 上的 ABI 数据并调用适当的函数
2
Caller -- 为指定的合约生成代码,可使用 SimpleABI 轻松调用外部合约
3
Encoder -- 用一系列参数生成合约调用的数据。人们可以简单地用 sendtocontract 等即可调用 SimpleABI 合约4 V3 E1 M: J0 V- U2 R! v$ B4 Y
& H" E0 s \* t
与 Solidity 的区别是什么?
) t# u6 A& {4 s! }* e
Solidity 调用外部合约的 ABI 直接内建在语言中。Solidity 专门用于构建智能合约,因此这么设计是有一定道理的。0 f3 z: D# M m7 D4 s" A
但是,x86 VM 支持多种不同的语言,这些语言不是专门为智能合约而设计的。这意味着我们必须在这些现有语言之上构建 ABI。使用复杂的语言分析库等或许可以自动构建适当的 ABI,但很难用,限制大,且不能在语言之间移植。
我们正在设计的这个新VM,应该是可以从你的 C 语言合约调用另一个 C 语言合约,也可以从你的 C++ 合约调用一个 Rust 合约等等。
0 F' ?; ]1 P' o
这个 ABI 比 Solidity 的 ABI 简单得多,但与 Solidity 不同,它是显式调用的,即它只能调用在 ABI 文件中直接指定的函数。因此,这个 ABI 往往需要更多的模板代码来处理简单的事情,比如调用函数,甚至只是解码发送的 ABI 数据......所以,一个理想的工具是代码生成。我们可以使用模板代码来生成函数,这样开发人员实际中使用函数只要几行代码。' Y& \+ k/ h' Z5 ]* ]4 _# H( y; O B
与 EVM 暴露“调用数据”的方式不一样的是,x86 VM具有“智能合约通信堆栈”(SCCS,smart contract communication stack)。我们只需要一个在合约之间传递和返回数据的栈,没必要解析一个大的扁平字节数组。: o* G& f9 ]! r1 s
这大大简化了智能合约的实现,这个 ABI 就是为了利用这一点而设计的。SCCS 可以将每个参数视为栈上的一个子项,而不需要解码一大块数据。这也使得调用合约变得更容易,因为不再需要构造一大块数据。大块数据往往需要分配足够内存,将所有元素放入一个连续的内存区域;而 SCCS 可以使用许多较小的内存实现。
, z! ~' z" C! M; C) i
ABI 规范
8 j9 f6 M5 K( v+ ]
ABI 规范很简单,每个函数 1 行。它还充当合约函数前后堆栈的表。即使不使用abigen,这也是一种有用的规范,可以手动实现对合约数据的编码解码的堆栈操作。
类似 ERC20 的接口示例:( C/ f# [$ o! e4 ~
ERC20Interface
# The first non-comment line is the name of the interface and used for all codegen prefixes
# this is a comment3 J- H1 s5 f0 d7 B$ S
selfBalance -> balance:uint64
address:UniversalAddress balance:fn -> balance:uint64/ p/ a: ~% |5 ]3 C& @! e/ y
addressTo:UniversalAddress value:uint64 send:fn -> newFromBalance:uint64 newToBalance:uint64' C* |) g; z4 p& o& ~7 H* h
address:UniversalAddress buyTokens:fn -> newBalance:uint64 -- payable
9 {2 S/ K- f8 d7 {2 h/ D7 m
也可以使用数组:7 D8 k' d9 g9 V" n
ArrayExample8 D9 T( X$ U: u8 F! n$ X: ^% R) ?
#declares someFunction takes an array of 20 bytes exactly. \9 g# _$ O2 I; _
someData:uint8[20]:fixed someFunction:fn -> void# d( l: h' Y, @6 }/ a0 m
#declares someFunctionDynamic that takes an array of no more than 20 bytes
someData:uint8[20]:max someFunctionDynamic:fn -> void% @5 j* m6 V2 S( @+ a2 ?
支持的基本类型:
: `5 w: Y! c; i( R
uint8/ P1 S5 M+ c" l9 A0 q' _. ]# m" N
uint16
uint32
uint64
int8
int161 ]( Y* p, i2 f9 l
int32
int64
char9 P8 N' `# W# N h/ @4 f7 F
void -- 仅对返回数据有效。对应无返回数据
fn -- 特殊8 x& H0 `: l) v9 h' U
更高级的类型:
UniversalAddress$ p: {$ k( A8 y
基本类型尽可能传值使用。高级类型传引用。数组传引用,并指向值,值包括高级类型。& t6 J# j4 {# |0 H" Y
数组类型:2 a F1 x; J. O# i; E F9 z% P
fixed(默认) - 数据必须是指定的确切大小
max(指定最大) -数据不能大于指定的大小。如果它较大,则会触发错误
dynamic(动态) - 任何长度都有效(使用前 uint8[])有效( B+ K& ^, n! n8 V2 {1 d
clip(截断) - 如果数据大于指定的大小,那么它会被截断,不会触发任何错误
9 v$ z, H Z# E: `6 b' C; y7 V; I
函数编号
函数编号的构造方式与 Solidity 类似。 sha256 哈希由函数行和接口名称组成,哈希值截断到后面 4 个字节作为函数号。
内存分配
大于 256 字节的数组都使用堆分配而不是栈。
接口9 y% P& j7 t- G
一个合约可以实现多个接口。每个接口使用该接口名称前缀来生成代码。对于具有相同名称的多个函数,只要它们由不同名称的接口定义,就可以同时存在于一个合约内。# t( E& ?6 i2 j8 j
包括其他接口:4 u) {& O3 b, S+ Q: Z7 Q' F- X# a! R5 q
MyContract& U7 ` E5 v- h" ?, C2 E* x
:interfaces ERC20, ERC721, MyParentContract
abigen 自动查找当前目录中的 ABI 文件名,并实现指定全局接口目录的方法。
& ]4 L# v' \: b" z9 E- @3 V
语言
现在只支持 C. 之后会支持 Rust。4 x* z2 V+ h2 o5 |9 F5 s0 G
示例(手动生成) C代码:
struct simpletoken_Send_Params{
UniversalAddressABI* address;! n6 _# L d. O
uint64_t value;7 d; G* Q& H* Z, a4 m
};
struct simpletoken_Send_Returns{
uint64_t recvvalue;
uint64_t sendervalue;4 P+ K7 R+ t. }" V' C' H% a% U
};
void decodeABI(){- m" T/ `3 u% u* \9 j
//format: address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64& R% s* U, @ H5 p5 y5 D# `
//format: address:address BALANCE -> balance:uint6
//format: SELFBALANCE -> balance:uint64
uint32_t function = 0;6 F! F) E! x% e) B
if(qtumStackItemCount() == 0){
//fallback function...
}
QTUM_POP_VAL(function);
switch(function){8 n. a5 B# N g4 G* r
case CONTRACT_SELFBALANCE:
{) C( ]" Q2 ~# k
uint64_t resBalance;1 f4 E7 I4 l" X2 w/ Y x
selfBalance(&resBalance);2 a3 J! K( ^% J* m7 Q7 }
QTUM_PUSH_VAL(resBalance);
return;
}9 u# w* z( }" \& x8 _
case CONTRACT_BALANCE:# ^/ A, i6 z1 c" a, z
{+ h# |" N6 B8 T$ `: I
UniversalAddressABI address;
QTUM_POP_VAL(address);! [9 I5 b" M" c' x' p
uint64_t resBalance;, S9 A; u: ?' A* G8 m9 e
balance(&address, &resBalance);' }( f: O4 x6 s2 H$ j C
QTUM_PUSH_VAL(resBalance);' x# ~2 o. `4 ?/ T$ X
return;( F3 ^' D& g% r2 x9 h6 Q0 `; O
}
case CONTRACT_SEND:! z- h2 [) V* @/ Q3 ?% z
{
struct simpletoken_Send_Params params;
UniversalAddressABI __tmp1;
params.address = &__tmp1;7 v5 D1 V% q7 |. i0 \/ y9 H0 W0 x
QTUM_POP_VAL(params.value);
QTUM_POP_VAL(__tmp1);& K2 ]4 d4 y: ~. T5 [2 w. F
struct simpletoken_Send_Returns returns;
send(¶ms, &returns);7 O# B/ k1 q3 {; ^, q
QTUM_PUSH_VAL(returns.sendervalue);
QTUM_PUSH_VAL(returns.recvvalue);
return;( K4 H, w$ c/ w; z
}
default:
qtumError("Invalid function");
return;
}; p( s7 E5 `* P( W( ~
}
//format for this:
//address:address value:uint64 SEND -> sendervalue:uint64 recvvalue:uint64% N! C9 Y v" ~: c
struct QtumCallResultABI simpletoken_Send(const UniversalAddressABI* __contract, uint64_t __gasLimit,
const struct simpletoken_Send_Params* params,# h3 b5 k# ^# J0 l7 t) a
struct simpletoken_Send_Returns* returns& q" y" j+ i6 p+ o. i' m
)8 J v/ i3 u) A1 @& u) @8 j
{2 P2 }. G1 n& A, [
if(__gasLimit == 0){ M9 [4 U3 H( W3 F: y
__gasLimit = QTUM_CALL_GASLIMIT;
}2 a; S! {! v- [% l% P2 a8 v
qtumStackClear();
QTUM_PUSH_VAL(*params->address);
QTUM_PUSH_VAL(params->value);- h" N4 E- Y# P( E! A" _$ {
uint32_t f = CONTRACT_SEND;+ U/ ?: C4 ^( E- A+ q- G
QTUM_PUSH_VAL(f);- K* A+ d- w5 U1 A8 B! T0 `& t6 K3 p
struct QtumCallResultABI result;/ ?/ t% c( \7 u) {. O! |
qtumCallContract(__contract, __gasLimit, 0, &result);2 ^3 s! J4 x4 R. K7 X- f: T1 `9 }
if(result.errorCode != 0){# c$ x+ I1 u: i
return result;
}# u$ M5 n8 o: H, x- R6 Q+ @+ f
QTUM_POP_VAL(returns->recvvalue);8 w8 D+ K: R2 K) a2 Z3 E# |
QTUM_POP_VAL(returns->sendervalue);& ~+ q3 k; c& h3 j' ^' J" y
return result;$ H+ I1 O* J2 B+ q2 x9 p& Y
}
0 u6 ^! ~% `0 ^" N
其他- E/ w* _- M7 V% N3 f0 ^% K! P
对于不支持内置数组大小的语言,不定长数组数组也会有个 "length" 参数暴露给 Caller 和 Dispatcher。
成为第一个吐槽的人