CALL指令
概述
CALL指令用于实现智能合约间的交互,即从当前智能合约调用另一个智能合约的函数。CALL指令从栈上弹出调用所需的操作数(如目标地址、调用的参数数据,eth amount等),然后执行目标智能合约的代码;CALL指令在执行结束后会向栈上写入一个状态码标识目标合约的函数执行成功或失败,成功时向栈上写入1否则写入0。Note:CALL指令不会引发回滚,即使调用失败当前智能合约仍会继续执行.
堆栈示例
// 栈顶 ---------------------> 栈底
CALL指令执行需要的操作数: |retLength|retOffset|argsLength|argsOffset|value|addr|gas|
CALL指令结束写入的操作数: |success|
下面文档中涉及到几个术语:
- 调用方/者:CALL指令的调用者合约
- 被调用方:CALL指令的被调用方合约(即栈上的操作数:addr)
ZKEVM中CORE指令的设计
在zkevm中CORE指令由多个gadget配合完成相应合约调用功能,每个gadget负责CORE指令的一部分逻辑,同时多个gadget之间有相应的执行顺序。
下列表格展示了CALL指令不同阶段使用到的gadget,以及gadget的执行顺序。
执行阶段 | gadget名称 | 执行顺序 |
---|---|---|
CALL指令调用前 | CALL_1 | 1 |
CALL指令调用前 | CALL_2 | 2 |
CALL指令调用前 | CALL_3 | 3 |
CALL指令调用前 | CALL_4 | 4 |
CALL指令调用中 | 被调用合约OPCODE的gadget(如:ADD,PUSH,CODESIZE) | 5 |
同上 | ... | 6 |
CALL指令调用结束 | STOP/RETURN/REVERT | 20 |
CALL指令调用结束后 | END_CALL | 21 |
CALL指令调用结束后 | CALL_5 | 22 |
各gadget的电路布局以及负责的功能
CORE_1 gadget
电路布局如下,core电路中使用3行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 2 | COPY | |
| 1 | STATE1| STATE2| STATE3|LEN_INV(1)|
| 0 | DYNA_SELECTOR | AUX | STATE_STAMP_INIT(1) |
+---+-------+-------+-------+----------+
call_1 从栈中读取CALL指令需要的argsOffset,argsLength两个操作数;并为CALL生成call_id,call_id基于state_stamp计算得出,将当前的state_stamp+1,在core电路中记录CALL ID对应的参数长度(即操作数argsLength),将CALL指令需要的参数数据从memory copy至CALL调用的calldata区域;Note: 此时gadget仍处于调用者的环境。
因此电路布局中,第0行为gadget标识和辅助状态的记录,第1行依次写入的三个state状态分别:argsOffset,argsLength,call id与argsLength对应关系,第2行写入copy的数据记录。
CORE_2 gadget
电路布局如下,core电路中使用2行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 1 | STATE1| STATE2| |
| 0 | DYNA_SELECTOR | AUX | STATE_STAMP_INIT(1) |
+---+-------+-------+-------+----------+
call_2 从栈中读取CALL指令需要的value操作数,记录新生成的call_id与value的对应关系;Note: 此时gadget仍处于调用者的环境。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行依次写入2个state状态分别为:栈上弹出的操作数value,新生成的call_id与value的对应关系。
CORE_3 gadget
电路布局如下,core电路中使用2行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 1 | STATE1| STATE2| STATE3| STATE4 |
| 0 | DYNA_SELECTOR | AUX | STATE_STAMP_INIT(1) |
+---+-------+-------+-------+----------+
call_3负责存储新生成的call_id调用者环境以便call指令执行结束后可以恢复调用者状态,不从栈上读取CALL指令的操作数;Note: 此时gadget仍处于调用者的环境。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的4个state状态为新call_id与调用者不同状态的对应关系,依次为调用者的call_id、调用者的pc、调用者的栈指针、调用者的合约地址。
CORE_4 gadget
电路布局如下,core电路中使用2行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 1 | STATE1| STATE2| STATE3| STATE4 |
| 0 | DYNA_SELECTOR | AUX |
+---+-------+-------+-------+----------+
call_4 从栈中读取CALL指令需要的gas,addr操作数,记录新call_id与调用方地址、被调用方合约地址(即弹出的addr操作数)对应关系,将stack_pointer置0、call_id与code_addr更新为新call对应被调用方状态;Note: 此时gadget执行完成后开始进行call指令的调用,进入新call指令的执行环境。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的4个state状态依次为:栈上弹出的gas操作数、栈上弹出的addr操作数、新call_id与被调用合约地址的对应关系、新call_id与调用方地址的对应关系;
RETURN/REVERT gadget
电路布局如下,core电路中使用3行。
+---+-------+-------+---------+---------+
|cnt| 8 col | 8 col | 8 col | 8col |
+---+-------+-------+---------+---------+
| 2 | Copy(11) | |
| 1 | STATE | STATE | STATE | STATE |
| 0 | DYNA_SELECTOR | AUX | ReturnDataSize(1)|
+---+-------+-------+---------+---------+
return/revert 标识一个call指令执行结束,此时从栈上弹出两个操作数:offset、length,并从call指令内存区域的指定位置读取return_data数据,记录call_id与returndata数据以及长度的对应关系。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的4个state状态依次为:栈上弹出的offset操作数、length操作数、返回数据的returndata_call_id、call_id与returndata数据长度的对应关系;第二行记录从memory copy至returndata区域的数据。
STOP gadget
电路布局如下,core电路中使用2行。
+---+-------+-------+-------+---------+
|cnt| 8 col | 8 col | 8 col | 8col |
+---+-------+-------+-------+---------+
| 1 | STATE1| STATE2| |
| 0 | DYNA_SELECTOR | AUX |RETURNDATASIZE(1) |
+---+-------+-------+-------+---------+
stop为表示CALL指令执行结束的另一种evm opcode,与RETURN/REVERT区别在于没有returndata数据,栈上不需要offset、length操作数,只需记录call_id与returndata的数据长度对应关系。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的2个state状态依次为:返回数据的returndata_call_id、call_id与returndata数据长度的对应关系。
END_CALL gadget
电路布局如下,core电路中使用2行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 1 | STATE1| STATE2| STATE3| STATE4 |
| 0 | DYNA_SELECTOR | AUX |SUCCESS(1)| PARENT_CALL_ID_INV(1)| RETURNDATA_SIZE(1)|
+---+-------+-------+-------+----------+
end_call 用于在call指令结束后恢复它对应的调用方的状态,当call指令调用方的call_id为0时标识当前执行的call为root call(即交易的所有指令执行结束),非0位交易的中间call调用此时恢复调用方的状态:stack_pointer、call_id、contract addr。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的4个state状态依次为: call指令对应的调用方call_id、对应的调用方pc、对应的调用方stack_pointer、对应的调用方contract addr。
CORE_5 gadget
电路布局如下,core电路中使用3行。
+---+-------+-------+-------+----------+
|cnt| 8 col | 8 col | 8 col | 8 col |
+---+-------+-------+-------+----------+
| 2 | COPY(11) | COPY_LEN(1)| LEN_INV(1)| COPY_PADDING_LEN(1) |
| 1 | STATE1| STATE2| STATE3| STATE4 |
| 0 | DYNA_SELECTOR | AUX |
+---+-------+-------+-------+----------+
core_5 用于CALL指令结束后回到调用方上下文执行环境时的操作处理,首先从栈上读取CALL指令剩余的2个操作数retLength、retOffset,然后更新stack pointer将CALL指令的7个操作数弹出栈,将call指令的return data数据copy至调用方的memory区域。
因此在电路布局中,第0行为gadget标识和辅助状态的记录,第一行记录的4个state状态依次为: CALL指令的操作数retOffset、CALL指令的操作数retLength、CALL指令的returndata call_id、CALL指令的返回状态(0标识失败、1标识成功);第二行记录从returndata区域copy至memory区域的数据以及对应的属性。