Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
zkevm-circuits
zkevm-circuits
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 0
    • Issues 0
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge Requests 0
    • Merge Requests 0
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Package Registry
  • Analytics
    • Analytics
    • CI / CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar

新注册的用户请输入邮箱并保存,随后登录邮箱激活账号。后续可直接使用邮箱登录!

  • zkp
  • zkevm-circuitszkevm-circuits
  • Wiki
    • Zkevm docs
    • 4 core
  • core call

Last edited by geruiwang Aug 12, 2024
Page history

core call

CALL指令

详细设计

概述

CALL指令用于实现智能合约间的交互,即从当前智能合约调用另一个智能合约的函数。CALL指令从栈上弹出调用所需的操作数(如目标地址、调用的参数数据等),然后执行目标智能合约的代码;CALL指令在执行结束后会向栈上写入一个状态码标识目标合约的函数执行成功或失败,成功时向栈上写入1否则写入0。注: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指令调用前 CALL_5 5
CALL指令调用前 CALL_6 6
CALL指令调用前 CALL_7 7
CALL指令调用中 被调用合约OPCODE的gadget(如:ADD,PUSH,CODESIZE) 8
同上 ... 9
CALL指令调用结束 STOP/RETURN/REVERT 20
CALL指令调用结束后 END_CALL 21
CALL指令调用结束后 POST_CALL_1 22
CALL指令调用结束后 POST_CALL_2 23

各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

电路布局如下:

+-----+----------------+-------------------------+-------------------------+----------------+---------------------+------------------+-----------------+
| cnt |                |                         |                         |                |                     |                  |                 |
+-----+----------------+-------------------------+-------------------------+----------------+---------------------+------------------+-----------------+
| 2   | U64Div(2..6)   | MemoryExpansion(7..11)  | MemoryExpansion(12..16) | U64Div(17..21) | U64Div(22..26)      | args_len_inv(27) | ret_len_inv(28) |
| 1   | STATE0(0..7)   | STATE1(8..15)           | STATE2(16..23)          | STATE3(24..31) |                     |                  |                 |
| 0   | DYNAMIC(0..17) | AUX(18..24)             | STATE_STAMP_INIT(25)    | MEMORY_GAS(26) |                     |                  |                 |
+-----+----------------+-------------------------+-------------------------+----------------+---------------------+------------------+-----------------+

call_4用于计算call过程中的memory gas,过程中需要四个栈元素。第0行计算出来的MEMORY_GAS提前预置在cnt == 0, vers_26的位置,方便下一个状态call_5调用时可以直接获取到该值的位置。第1行分别为栈里四个值args_offset, args_length, ret_offset, ret_length。第2行为memory gas计算过程中所需要的算术电路。

CORE_5 gadget

电路布局:

+-----+-------------------+---------------------+---------------------+------------------------+---------------+
| cnt |                   |                     |                     |                        |               |
+-----+-------------------+---------------------+---------------------+------------------------|----------+----+
| 3   | STORAGE_READ(0..11)| STORAGE_WRITE(12..23)| value_inv(24)     | capped_gas_left(25)    |               |
| 2   | U64Div(2..6)      | U64Overflow(7..11)  | U64Overflow(12..16) | MemoryExpansion(17..21)|               |
| 1   | STATE0(0..7)      | STATE1(8..15)       | STATE2(16..23)      |                        |               |
| 0   | dynamic(0..17)    | AUX(18..24)         |   STAMP_INIT(25)    | TRACE_GAS(26) | TRACE_GAS_COST(27)     |
+-----+-------------------+---------------------+---------------------+---------------|-------------------+----+

call_5用于计算最终的call gas花费,在这一步计算出next_state的gas_left,以及call的gas_cost。第0行STAMP_INIT,TRACE_GAS,TRACE_GAS_COST为预留的位置,提供给call_6计算使用。第1行为计算需要的stack中的值,分别对应gas,addr, value。第2行为计算所需要的算术电路。第3行分别对应EIP2929中的is_warm read, write,value的乘法逆元,capped_gas_left为降低degree所需要的中间变量。

CORE_6 gadget

+-----+---------------------+---------------------+-----------------+
| cnt |                     |                     |                 |
+-----+---------------------+---------------------+-----------------+
| 1   | CALLCONTEXT_WRITE_0 | CALLCONTEXT_WRITE_1 |                 |
| 0   | DYNA_SELECTOR       | AUX                 | STAMP_INIT (25) |
+-----+---------------------+---------------------+-----------------+

call_6用于在post_call时计算gas费用提供所需要的上下文信息。第0行STAMP_INIT为预留位置,供call_7使用。第1行分别代表trace.gas 和 trace.gas_cost的上下文写入数据操作。

CORE_7 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。

POST_CALL_1 gadget

+-----+---------------------+---------------------+---------------------+----------------------+
| cnt |                     |                     |                     |                      |
+-----+---------------------+---------------------+---------------------+----------------------+
| 1   | CALLCONTEXT_READ_0 | CALLCONTEXT_READ_1   |                     |                      |
| 0   | DYNA_SELECTOR       | AUX                 | RETURN_SUCCESS (25) | RETURNDATA_SIZE (27) |
+-----+---------------------+---------------------+---------------------+----------------------+

post_call_1用于处理之前在call_6写入的上下文信息,并计算post_call时的gas费用,第0行为预留位置给post_call_2使用,第1行分别为trace.gas和trace.gas_cost的读取操作。

POST_CALL_2 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区域的数据以及对应的属性。

其他类型的CALL

概述

  • CALL

    • callee合约对象中过存储的callerAddress为caller的address
    • callee合约对象的address为callee的address
    • callee合约对象中的code为callee的code(toAddress对应的code)
  • STATICCALL

    • callee合约对象中过存储的callerAddress为caller的address
    • callee合约对象的address为callee的address
    • callee合约对象中的code为callee的code(toAddress对应的code)
  • CALLCODE:

    • callee合约对象中过存储的callerAddress为caller的地址
    • callee合约对象的address为caller的address
    • callee合约对象中的code为callee的code(toAddress对应的code)
  • DELEGATECALL:

    • callee合约对象中过存储的callerAddress为caller.parent的地址
    • callee合约对象的address为caller的address
    • callee合约对象中的code为callee的code(toAddress对应的code)

因为每一次CALL的stack都是新的,memory是公用的,storage修改的是callee对象中的address对应的slot,所以CALLCODE和DELEGATECALL可以做到调用指定的合约但修改的是自己的存储状态数据

又因为DELEGATE中存储的callerAddress为caller.Parent.Address, 所以DELEGATE中操作msg.sender, msg.value和在caller中操作一样的效果

代码修改

  1. 添加opcode selector选择器
  2. CALL的stack操作数是7个,STATICCALL和DELEGATECALL的stack操作数是8个(需要修改栈的弹出位置以及stamp delta, stack pointer)
  3. DELEGATECALL中目标合约代码执行过程中的contract_addr为调用者的contract_addr, sender_addr为调用者的sender_addr
  4. LOG_TOPIC_NUM_ADDR中所使用的addr是contract_addr并不是code_addr

说明:

  • 除了DELEGATECALL和CALLCODE的其他操作中,contract_addr和code_addr是相等的, msg.sender为caller的addr

  • DELEGATECALL中contract_addr为caller.addr, sender为caller.sender

  • CALLCODE中contract_addr为caller.addr, sender为caller

注:并未实现CALLCODE,原因参考:(https://docs.soliditylang.org/en/v0.8.26/050-breaking-changes.html,https://docs.soliditylang.org/zh/v0.8.17/050-breaking-changes.html)

参数比较

STATICCALL和DELEGATECALL比着CALL和CALLCODE少了一个value的参数

CALL
|gas|addr|value|argsOffset|argsLength|retOffset|retLength	 返回值success

CALLCODE
|gas|addr|value|argsOffset|argsLength|retOffset|retLength	 返回值success

DELEGATECALL
|gas|addr|argsOffset|argsLength|retOffset|retLength	 返回值success

STATICCALL
|gas|addr|argsOffset|argsLength|retOffset|retLength	 返回值success

指令行为比较

CALL
	success, 
	memory[retOffset:retOffset+retLength] =
    address(addr).call.gas(gas).value(value)
      (memory[argsOffset:argsOffset+argsLength])
    
CALLCODE
	success, 
	memory[retOffset:retOffset+retLength] =
    address(addr).callcode.gas(gas).value(value)
        (memory[argsOffset:argsOffset+argsLength])
        
DELETGATECALL
   	success, 
   	memory[retOffset:retOffset+retLength] =
       address(addr).delegatecall.gas(gas)
        (memory[argsOffset:argsOffset+argsLength])

STATICCALL
	success, memory[retOffset:retOffset+retLength] =
    address(addr).staticcall.gas(gas)
        (memory[argsOffset:argsOffset+argsLength])

evm指令代码比较

四个CALL调用逻辑一模一样

CALL

func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	stack := scope.Stack
	// Pop gas. The actual gas in interpreter.evm.callGasTemp.
	// We can use this as a temporary value
	temp := stack.pop()
	gas := interpreter.evm.callGasTemp
	// Pop other call parameters.
	addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
	toAddr := common.Address(addr.Bytes20())
	// Get the arguments from the memory.
	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	if interpreter.readOnly && !value.IsZero() {
		return nil, ErrWriteProtection
	}
	if !value.IsZero() {
		gas += params.CallStipend
	}
	ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, &value)

	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.push(&temp)
	if err == nil || err == ErrExecutionReverted {
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.Contract.Gas += returnGas

	interpreter.returnData = ret
	return ret, nil
}

CALLCODE

func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
	stack := scope.Stack
	// We use it as a temporary value
	temp := stack.pop()
	gas := interpreter.evm.callGasTemp
	// Pop other call parameters.
	addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
	toAddr := common.Address(addr.Bytes20())
	// Get arguments from the memory.
	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	if !value.IsZero() {
		gas += params.CallStipend
	}

	ret, returnGas, err := interpreter.evm.CallCode(scope.Contract, toAddr, args, gas, &value)
	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.push(&temp)
	if err == nil || err == ErrExecutionReverted {
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.Contract.Gas += returnGas

	interpreter.returnData = ret
	return ret, nil
}

DELEGATECALL

func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	stack := scope.Stack
	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
	// We use it as a temporary value
	temp := stack.pop()
	gas := interpreter.evm.callGasTemp
	// Pop other call parameters.
	addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
	toAddr := common.Address(addr.Bytes20())
	// Get arguments from the memory.
	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract, toAddr, args, gas)
	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.push(&temp)
	if err == nil || err == ErrExecutionReverted {
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.Contract.Gas += returnGas

	interpreter.returnData = ret
	return ret, nil
}

STATICCALL

func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
	// Pop gas. The actual gas is in interpreter.evm.callGasTemp.
	stack := scope.Stack
	// We use it as a temporary value
	temp := stack.pop()
	gas := interpreter.evm.callGasTemp
	// Pop other call parameters.
	addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
	toAddr := common.Address(addr.Bytes20())
	// Get arguments from the memory.
	args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))

	ret, returnGas, err := interpreter.evm.StaticCall(scope.Contract, toAddr, args, gas)
	if err != nil {
		temp.Clear()
	} else {
		temp.SetOne()
	}
	stack.push(&temp)
	if err == nil || err == ErrExecutionReverted {
		scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
	}
	scope.Contract.Gas += returnGas

	interpreter.returnData = ret
	return ret, nil
}

evm调用代码比较

CALL

// callee的地址		  
addrCopy := addr
//  NewContract(caller地址, callee地址,value, gas)
//      c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas

CALLCODE

addrCopy := addr
//  NewContract(caller地址, callee地址,value, gas)
//      c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas

DELEGATECALL

addrCopy := addr
//  NewContract(caller地址, callee地址,value, gas)
//      c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}
//      c.CallerAddress = parent.CallerAddress
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, true)
gas = contract.Gas
// CallerAddress: 默认是调用者的地址,
func NewContract(caller ContractRef, object ContractRef, value *uint256.Int, gas uint64) *Contract {
	c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}

	if parent, ok := caller.(*Contract); ok {
		// Reuse JUMPDEST analysis from parent context if available.
		c.jumpdests = parent.jumpdests
	} else {
		c.jumpdests = make(map[common.Hash]bitvec)
	}

	// Gas should be a pointer so it can safely be reduced through the run
	// This pointer will be off the state transition
	c.Gas = gas
	// ensures a value is set
	c.value = value

	return c
}


func (c *Contract) AsDelegate() *Contract {
	// NOTE: caller must, at all times be a contract. It should never happen
	// that caller is something other than a Contract.
	parent := c.caller.(*Contract)
  // 被调用合约中记录的调用者的地址和调用者的调用者是同一个地址
 
	c.CallerAddress = parent.CallerAddress
  // value和调用者的value一致
	c.value = parent.value

	return c
}
Clone repository

Copyright © 2024 ChainWeaver Org. All Rights Reserved. 版权所有。

京ICP备2023035722号-3

京公网安备 11010802044225号