|
|
# CALL指令
|
|
|
|
|
|
## 概述
|
|
|
## CALL
|
|
|
|
|
|
### 概述
|
|
|
|
|
|
CALL指令用于实现智能合约间的交互,即从当前智能合约调用另一个智能合约的函数。CALL指令从栈上弹出调用所需的操作数(如目标地址、调用的参数数据,eth amount等),然后执行目标智能合约的代码;CALL指令在执行结束后会向栈上写入一个状态码标识目标合约的函数执行成功或失败,成功时向栈上写入1否则写入0。Note:CALL指令不会引发回滚,即使调用失败当前智能合约仍会继续执行.
|
|
|
|
... | ... | @@ -17,8 +19,7 @@ CALL指令结束写入的操作数: |success| |
|
|
* 调用方/者:CALL指令的调用者合约
|
|
|
* 被调用方:CALL指令的被调用方合约(即栈上的操作数:addr)
|
|
|
|
|
|
|
|
|
## ZKEVM中CORE指令的设计
|
|
|
### ZKEVM中CORE指令的设计
|
|
|
|
|
|
在zkevm中CORE指令由多个gadget配合完成相应合约调用功能,每个gadget负责CORE指令的一部分逻辑,同时多个gadget之间有相应的执行顺序。
|
|
|
|
... | ... | @@ -40,8 +41,7 @@ CALL指令调用结束后|END_CALL|21 |
|
|
CALL指令调用结束后|POST_CALL_1|22
|
|
|
CALL指令调用结束后|POST_CALL_2|23
|
|
|
|
|
|
|
|
|
## 各gadget的电路布局以及负责的功能
|
|
|
### 各gadget的电路布局以及负责的功能
|
|
|
|
|
|
#### CORE_1 gadget
|
|
|
|
... | ... | @@ -243,3 +243,314 @@ core_5 用于CALL指令结束后回到调用方上下文执行环境时的操作 |
|
|
|
|
|
|
|
|
|
|
|
## 其他的CALL(2024/07/30)
|
|
|
|
|
|
### 概述
|
|
|
|
|
|
- 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的参数
|
|
|
|
|
|
```shell
|
|
|
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
|
|
|
```
|
|
|
|
|
|
指令行为比较
|
|
|
|
|
|
```shell
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
// 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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
|
|
|
```go
|
|
|
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
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
// 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
|
|
|
}
|
|
|
```
|
|
|
|