Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/delegate call trace #2115

Merged
merged 10 commits into from
May 25, 2023
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ services:
zkevm-prover:
container_name: zkevm-prover
restart: unless-stopped
image: hermeznetwork/zkevm-prover:v1.1.2-RC1-fork.4
image: hermeznetwork/zkevm-prover:v1.1.3-RC2-fork.4
depends_on:
zkevm-state-db:
condition: service_healthy
Expand Down
12 changes: 6 additions & 6 deletions jsonrpc/endpoints_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,27 +216,27 @@ func (d *DebugEndpoints) buildStructLogs(stateStructLogs []instrumentation.Struc
RefundCounter: structLog.RefundCounter,
}

stack := make([]types.ArgBig, 0, len(structLog.Stack))
if !cfg.DisableStack && len(structLog.Stack) > 0 {
if !cfg.DisableStack {
stack := make([]types.ArgBig, 0, len(structLog.Stack))
for _, stackItem := range structLog.Stack {
if stackItem != nil {
stack = append(stack, types.ArgBig(*stackItem))
}
}
structLogRes.Stack = &stack
}
structLogRes.Stack = &stack

const memoryChunkSize = 32
memory := make([]string, 0, len(structLog.Memory))
if cfg.EnableMemory {
const memoryChunkSize = 32
memory := make([]string, 0, len(structLog.Memory))
for i := 0; i < len(structLog.Memory); i = i + memoryChunkSize {
slice32Bytes := make([]byte, memoryChunkSize)
copy(slice32Bytes, structLog.Memory[i:i+memoryChunkSize])
memoryStringItem := hex.EncodeToString(slice32Bytes)
memory = append(memory, memoryStringItem)
}
structLogRes.Memory = &memory
}
structLogRes.Memory = &memory

if !cfg.DisableStorage && len(structLog.Storage) > 0 {
storage := make(map[string]string, len(structLog.Storage))
Expand Down
87 changes: 72 additions & 15 deletions state/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,9 @@ func (s *State) DebugTransaction(ctx context.Context, transactionHash common.Has

// ParseTheTraceUsingTheTracer parses the given trace with the given tracer.
func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumentation.ExecutorTrace, tracer tracers.Tracer) (json.RawMessage, error) {
var previousDepth int
var previousStep instrumentation.Step
var previousOp, previousGas *big.Int
var previousOpcode string
var previousError error
var stateRoot []byte

contextGas, ok := new(big.Int).SetString(trace.Context.Gas, encoding.Base10)
Expand All @@ -452,7 +452,12 @@ func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumen
}

tracer.CaptureTxStart(contextGas.Uint64())
tracer.CaptureStart(evm, common.HexToAddress(trace.Context.From), common.HexToAddress(trace.Context.To), trace.Context.Type == "CREATE", common.Hex2Bytes(strings.TrimLeft(trace.Context.Input, "0x")), contextGas.Uint64(), value)
decodedInput, err := hex.DecodeHex(trace.Context.Input)
if err != nil {
log.Errorf("error while decoding context input from hex to bytes:, %v", err)
return nil, ErrParsingExecutorTrace
}
tracer.CaptureStart(evm, common.HexToAddress(trace.Context.From), common.HexToAddress(trace.Context.To), trace.Context.Type == "CREATE", decodedInput, contextGas.Uint64(), value)

bigStateRoot, ok := new(big.Int).SetString(trace.Context.OldStateRoot, 0)
if !ok {
Expand Down Expand Up @@ -526,7 +531,7 @@ func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumen
break
}

if previousOpcode == "CALL" && step.Pc != 0 {
if previousStep.OpCode == "CALL" && step.Pc != 0 {
tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError)
}

Expand All @@ -538,20 +543,43 @@ func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumen
}
}

if step.OpCode == "CALL" || step.OpCode == "CALLCODE" || step.OpCode == "DELEGATECALL" || step.OpCode == "STATICCALL" || step.OpCode == "SELFDESTRUCT" {
tracer.CaptureEnter(fakevm.OpCode(op.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input), gas.Uint64(), value)
if step.OpCode == "SELFDESTRUCT" {
tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError)
previousOpCodeCanBeSubCall := previousStep.OpCode == "CREATE" ||
previousStep.OpCode == "CREATE2" ||
previousStep.OpCode == "DELEGATECALL" ||
previousStep.OpCode == "CALL" ||
previousStep.OpCode == "STATICCALL" ||
// deprecated ones
previousStep.OpCode == "CALLCODE" ||
previousStep.OpCode == "SELFDESTRUCT"

// when a sub call or create is detected, the next step contains the contract updated
if previousOpCodeCanBeSubCall {
// shadowing "value" to override its value without compromising the external code
value := value
// value is not carried over when the capture enter handles STATIC CALL
if previousStep.OpCode == "STATICCALL" {
value = nil
}
}

// when a create2 is detected, the next step contains the contract updated
if previousOpcode == "CREATE" || previousOpcode == "CREATE2" {
tracer.CaptureEnter(fakevm.OpCode(previousOp.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input), previousGas.Uint64(), value)
// if the previous depth is the same as the current one, this means
// the sub call did not executed any other step and the
// context is back to the same level. This can happen with pre compiled executions.
if previousStep.Depth == step.Depth {
addr, input := s.getPreCompiledCallAddressAndInput(previousStep)
tracer.CaptureEnter(fakevm.OpCode(previousOp.Uint64()), common.HexToAddress(previousStep.Contract.Address), addr, input, previousGas.Uint64(), value)
previousStepGasCost, ok := new(big.Int).SetString(step.GasCost, encoding.Base10)
if !ok {
log.Debugf("error while parsing previous step gasCost")
return nil, ErrParsingExecutorTrace
}
tracer.CaptureExit(step.ReturnData, previousStepGasCost.Uint64(), previousError)
} else {
tracer.CaptureEnter(fakevm.OpCode(previousOp.Uint64()), common.HexToAddress(step.Contract.Caller), common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input), previousGas.Uint64(), value)
}
}

// returning from a call or create
if previousDepth > step.Depth {
if previousStep.Depth > step.Depth {
tracer.CaptureExit(step.ReturnData, gasCost.Uint64(), stepError)
}

Expand All @@ -560,10 +588,10 @@ func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumen
evm.StateDB.SetStateRoot(stateRoot)

// set previous step values
previousDepth = step.Depth
previousStep = step
previousOp = op
previousGas = gas
previousOpcode = step.OpCode
previousError = stepError
}

gasUsed, ok := new(big.Int).SetString(trace.Context.GasUsed, encoding.Base10)
Expand All @@ -579,6 +607,35 @@ func (s *State) ParseTheTraceUsingTheTracer(evm *fakevm.FakeEVM, trace instrumen
return tracer.GetResult()
}

func (s *State) getPreCompiledCallAddressAndInput(step instrumentation.Step) (common.Address, []byte) {
if step.OpCode == "DELEGATECALL" || step.OpCode == "CALL" || step.OpCode == "STATICCALL" || step.OpCode == "CALLCODE" {
addrPos := len(step.Stack) - 1 - 1
argsOffsetPos := addrPos - 1
argsSizePos := argsOffsetPos - 1

// if the stack has the tx value, we skip it
stackHasValue := step.OpCode == "CALL" || step.OpCode == "CALLCODE"
if stackHasValue {
argsOffsetPos--
argsSizePos--
}

addrEncoded := step.Stack[addrPos]
addr := common.HexToAddress("0x" + addrEncoded)

argsOffsetEncoded := step.Stack[argsOffsetPos]
argsOffset := hex.DecodeUint64(argsOffsetEncoded)

argsSizeEncoded := step.Stack[argsSizePos]
argsSize := hex.DecodeUint64(argsSizeEncoded)
input := make([]byte, argsSize)
copy(input[0:argsSize], step.Memory[argsOffset:argsOffset+argsSize])
return addr, input
} else {
return common.HexToAddress(step.Contract.Address), []byte(step.Contract.Input)
}
}

// PreProcessTransaction processes the transaction in order to calculate its zkCounters before adding it to the pool
func (s *State) PreProcessTransaction(ctx context.Context, tx *types.Transaction, dbTx pgx.Tx) (*ProcessBatchResponse, error) {
sender, err := GetSender(*tx)
Expand Down
6 changes: 3 additions & 3 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ test-e2e-group-5: stop ## Runs group 5 e2e tests checking race conditions
$(RUNZKPROVER)
docker ps -a
docker logs $(DOCKERCOMPOSEZKPROVER)
trap '$(STOP)' EXIT; MallocNanoZone=0 go test -count=1 -race -v -p 1 -timeout 600s ../ci/e2e-group5/...
trap '$(STOP)' EXIT; MallocNanoZone=0 go test -count=1 -race -v -p 1 -timeout 1200s ../ci/e2e-group5/...

.PHONY: test-e2e-group-6
test-e2e-group-6: stop ## Runs group 6 e2e tests checking race conditions
Expand Down Expand Up @@ -181,11 +181,11 @@ test-e2e-group-8: stop ## Runs group 8 e2e tests checking race conditions
$(RUNZKPROVER)
docker ps -a
docker logs $(DOCKERCOMPOSEZKPROVER)
trap '$(STOP)' EXIT; MallocNanoZone=0 go test -count=1 -race -v -p 1 -timeout 600s ../ci/e2e-group8/...
trap '$(STOP)' EXIT; MallocNanoZone=0 go test -count=1 -race -v -p 1 -timeout 1200s ../ci/e2e-group8/...


.PHONY: test-e2e-group-9
test-e2e-group-9: stop ## Runs group 8 e2e tests checking race conditions
test-e2e-group-9: stop ## Runs group 9 e2e tests checking race conditions
$(RUNSTATEDB)
$(RUNPOOLDB)
$(RUNEVENTDB)
Expand Down
30 changes: 30 additions & 0 deletions test/contracts/auto/Called.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Called {
uint256 num;
address sender;
uint256 value;

function setVars(uint256 _num) public payable {
num = _num;
sender = msg.sender;
value = msg.value;
}

function setVarsViaCall(uint256 _num) public payable {
bool ok;
(ok, ) = address(this).call(
abi.encodeWithSignature("setVars(uint256)", _num)
);
require(ok, "failed to perform call");
}

function getVars() public view returns (uint256, address, uint256) {
return (num, sender, value);
}

function getVarsAndVariable(uint256 _num) public view returns (uint256, address, uint256, uint256) {
return (num, sender, value, _num);
}
}
74 changes: 74 additions & 0 deletions test/contracts/auto/Caller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Caller {
function call(address _contract, uint _num) public payable {
bool ok;
(ok, ) = _contract.call(
abi.encodeWithSignature("setVars(uint256)", _num)
);
require(ok, "failed to perform call");
}

function delegateCall(address _contract, uint _num) public payable {
bool ok;
(ok, ) = _contract.delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
require(ok, "failed to perform delegate call");
}

function staticCall(address _contract) public payable {
bool ok;
bytes memory result;
(ok, result) = _contract.staticcall(
abi.encodeWithSignature("getVars()")
);
require(ok, "failed to perform static call");

uint256 num;
address sender;
uint256 value;

(num, sender, value) = abi.decode(result, (uint256, address, uint256));
}

function invalidStaticCallMoreParameters(address _contract) public {
bool ok;
(ok,) = _contract.staticcall(
abi.encodeWithSignature("getVarsAndVariable(uint256)", 1, 2)
);
require(!ok, "static call was supposed to fail with more parameters");
}

function invalidStaticCallLessParameters(address _contract) public {
bool ok;
(ok,) = _contract.staticcall(
abi.encodeWithSignature("getVarsAndVariable(uint256)")
);
require(!ok, "static call was supposed to fail with less parameters");
}

function invalidStaticCallWithInnerCall(address _contract) public {
bool ok;
(ok,) = _contract.staticcall(
abi.encodeWithSignature("getVarsAndVariable(uint256)")
);
require(!ok, "static call was supposed to fail with less parameters");
}

function multiCall(address _contract, uint _num) public payable {
call(_contract, _num);
delegateCall(_contract, _num);
staticCall(_contract);
}

function preEcrecover_0() public {
bytes32 messHash = 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3;
uint8 v = 28;
bytes32 r = 0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608;
bytes32 s = 0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada;

ecrecover(messHash, v, r, s);
}
}
26 changes: 26 additions & 0 deletions test/contracts/auto/ChainCallLevel1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract ChainCallLevel1 {
function exec(address level2Addr, address level3Addr, address level4Addr) public payable {
bool ok;
(ok, ) = level2Addr.call(
abi.encodeWithSignature("exec(address,address)", level3Addr, level4Addr)
);
require(ok, "failed to perform call to level 2");

(ok, ) = level2Addr.delegatecall(
abi.encodeWithSignature("exec(address,address)", level3Addr, level4Addr)
);
require(ok, "failed to perform delegate call to level 2");

bytes memory result;
(ok, result) = level2Addr.staticcall(
abi.encodeWithSignature("get(address,address)", level3Addr, level4Addr)
);
require(ok, "failed to perform static call to level 2");

string memory t;
(t) = abi.decode(result, (string));
}
}
27 changes: 27 additions & 0 deletions test/contracts/auto/ChainCallLevel2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract ChainCallLevel2 {
function exec(address level3Addr, address level4Addr) public payable {
bool ok;
(ok, ) = level3Addr.call(abi.encodeWithSignature("exec(address)", level4Addr));
require(ok, "failed to perform call to level 3");

(ok, ) = level3Addr.delegatecall(abi.encodeWithSignature("exec(address)", level4Addr));
require(ok, "failed to perform delegate call to level 3");
}

function get(address level3Addr, address level4Addr) public view returns (string memory t) {
bool ok;
bytes memory result;
(ok, result) = level3Addr.staticcall(abi.encodeWithSignature("get(address)", level4Addr));
require(ok, "failed to perform static call to level 3");

t = abi.decode(result, (string));

(ok, result) = level4Addr.staticcall(abi.encodeWithSignature("get()"));
require(ok, "failed to perform static call to level 4 from level 2");

t = abi.decode(result, (string));
}
}
23 changes: 23 additions & 0 deletions test/contracts/auto/ChainCallLevel3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract ChainCallLevel3 {
function exec(address level4Addr) public payable {
bool ok;
(ok, ) = level4Addr.call(abi.encodeWithSignature("exec()"));
require(ok, "failed to perform call to level 4");

(ok, ) = level4Addr.delegatecall(abi.encodeWithSignature("exec()"));
require(ok, "failed to perform delegate call to level 4");
}

function get(address level4Addr) public view returns (string memory t) {
bool ok;
bytes memory result;

(ok, result) = level4Addr.staticcall(abi.encodeWithSignature("get()"));
require(ok, "failed to perform static call to level 4");

t = abi.decode(result, (string));
}
}
Loading