Skip to content

Commit

Permalink
905 rpc debug tracetransaction is missing error and revert reason for…
Browse files Browse the repository at this point in the history
… reverted txs (#945)

* Added contracts and stream trace modification

* added panic revert handling

* added correct contract for reproducing
  • Loading branch information
V-Staykov authored Aug 12, 2024
1 parent 20b0712 commit 7ee5ef4
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 15 deletions.
62 changes: 53 additions & 9 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io"
"math/big"

libcommon "github.com/gateway-fm/cdk-erigon-lib/common"

Expand Down Expand Up @@ -263,8 +264,31 @@ func (abi *ABI) HasReceive() bool {
return abi.Receive.Type == Receive
}

// revertSelector is a special function selector for revert reason unpacking.
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
var (
// revertSelector is a special function selector for revert reason unpacking.
revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]

// panicSelector is a special function selector for panic reason unpacking.
panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]

// panicReasons map is for readable panic codes
// see this linkage for the details
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
// the reason string list is copied from ether.js
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
panicReasons = map[uint64]string{
0x00: "generic panic",
0x01: "assert(false)",
0x11: "arithmetic underflow or overflow",
0x12: "division or modulo by zero",
0x21: "enum overflow",
0x22: "invalid encoded storage byte array accessed",
0x31: "out-of-bounds array access; popping on an empty array",
0x32: "out-of-bounds access of an array or bytesN",
0x41: "out of memory",
0x51: "uninitialized function",
}
)

// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
Expand All @@ -274,13 +298,33 @@ func UnpackRevert(data []byte) (string, error) {
if len(data) < 4 {
return "", errors.New("invalid data for unpacking")
}
if !bytes.Equal(data[:4], revertSelector) {
switch {
case bytes.Equal(data[:4], revertSelector):
typ, _ := NewType("string", "", nil)
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
return "", err
}
return unpacked[0].(string), nil
case bytes.Equal(data[:4], panicSelector):
typ, err := NewType("uint256", "", nil)
if err != nil {
return "", err
}
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
return "", err
}
pCode := unpacked[0].(*big.Int)
// uint64 safety check for future
// but the code is not bigger than MAX(uint64) now
if pCode.IsUint64() {
if reason, ok := panicReasons[pCode.Uint64()]; ok {
return reason, nil
}
}
return fmt.Sprintf("unknown panic code: %#x", pCode), nil
default:
return "", errors.New("invalid data for unpacking")
}
typ, _ := NewType("string", "", nil)
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
if err != nil {
return "", err
}
return unpacked[0].(string), nil
}
25 changes: 20 additions & 5 deletions eth/tracers/logger/json_stream_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type JsonStreamLogger_ZkEvm struct {
env vm.VMInterface

counterCollector *vm.CounterCollector
stateClosed bool
}

// NewStructLogger returns a new logger
Expand All @@ -42,6 +43,7 @@ func NewJsonStreamLogger_ZkEvm(cfg *LogConfig, ctx context.Context, stream *json
storage: make(map[libcommon.Address]Storage),
firstCapture: true,
counterCollector: counterCollector,
stateClosed: true,
}
if cfg != nil {
logger.cfg = *cfg
Expand Down Expand Up @@ -82,6 +84,12 @@ func (l *JsonStreamLogger_ZkEvm) CaptureState(pc uint64, op vm.OpCode, gas, cost
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return
}

if !l.stateClosed {
l.stream.WriteObjectEnd()
_ = l.stream.Flush()
l.stateClosed = true
}
if !l.firstCapture {
l.stream.WriteMore()
} else {
Expand All @@ -90,6 +98,8 @@ func (l *JsonStreamLogger_ZkEvm) CaptureState(pc uint64, op vm.OpCode, gas, cost

outputStorage := l.prepareStorage(scope, scope.Contract, op)

l.stream.WriteObjectStart()
l.stateClosed = false
l.writeOpSnapshot(pc, op, gas, cost, depth, err)

l.writeError(err)
Expand All @@ -106,8 +116,8 @@ func (l *JsonStreamLogger_ZkEvm) CaptureState(pc uint64, op vm.OpCode, gas, cost

l.writeCounters()

l.stream.WriteObjectEnd()
_ = l.stream.Flush()
// l.stream.WriteObjectEnd()
// _ = l.stream.Flush()
}

func (l *JsonStreamLogger_ZkEvm) prepareStorage(scope *vm.ScopeContext, contract *vm.Contract, op vm.OpCode) bool {
Expand Down Expand Up @@ -145,7 +155,6 @@ func (l *JsonStreamLogger_ZkEvm) prepareStorage(scope *vm.ScopeContext, contract

func (l *JsonStreamLogger_ZkEvm) writeOpSnapshot(pc uint64, op vm.OpCode, gas, cost uint64, depth int, err error) {
// create a new snapshot of the EVM.
l.stream.WriteObjectStart()
l.stream.WriteObjectField("pc")
l.stream.WriteUint64(pc)
l.stream.WriteMore()
Expand Down Expand Up @@ -278,13 +287,19 @@ func (l *JsonStreamLogger_ZkEvm) writeCounters() {
}
l.stream.WriteObjectEnd()
}
// not needed for this tracer
}

// CaptureFault implements the Tracer interface to trace an execution fault
// while running an opcode.
func (l *JsonStreamLogger_ZkEvm) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
// not needed for this tracer
if !l.stateClosed {
l.stream.WriteMore()
l.stream.WriteObjectField("error")
l.stream.WriteString(err.Error())
l.stream.WriteObjectEnd()
_ = l.stream.Flush()
l.stateClosed = true
}
}

// CaptureEnd is called after the call finishes to finalize the tracing.
Expand Down
58 changes: 58 additions & 0 deletions zk/debug_tools/test-contracts/contracts/MyToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);

event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}

contract MyToken is IERC20 {
uint override public totalSupply;
mapping(address => uint) override public balanceOf;
mapping(address => mapping(address => uint)) override public allowance;
uint8 public decimals = 18;

constructor() {
}

function transfer(address recipient, uint amount) override external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}

function approve(address spender, uint amount) override external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}

function transferFrom(address sender, address recipient, uint amount) override external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}

function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}

function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
7 changes: 7 additions & 0 deletions zk/debug_tools/test-contracts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion zk/debug_tools/test-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"emitlog:mainnet": "npx hardhat compile && npx hardhat run scripts/emitlog.js --network mainnet",
"spam:local": "npx hardhat compile && npx hardhat run scripts/spam-transactions.js --network local",
"delegateCall:local": "npx hardhat compile && npx hardhat run scripts/delegate-call.js --network local",
"erc20Revert:local": "npx hardhat compile && npx hardhat run scripts/ERC20-revert.js --network local",
"erc20Revert:sepolia": "npx hardhat compile && npx hardhat run scripts/ERC20-revert.js --network sepolia",
"chainCall:local": "npx hardhat compile && npx hardhat run scripts/chain-call.js --network local",
"chainCall:sepolia": "npx hardhat compile && npx hardhat run scripts/chain-call.js --network sepolia"
},
Expand All @@ -23,6 +25,7 @@
"license": "ISC",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"hardhat": "^2.22.4"
"hardhat": "^2.22.4",
"@openzeppelin/contracts": "^5.0.2"
}
}
27 changes: 27 additions & 0 deletions zk/debug_tools/test-contracts/scripts/ERC20-revert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// deploys contracts and calls a method to produce delegate call
async function main() {
try {
const ERC20 = await hre.ethers.getContractFactory("MyToken");

// Deploy the contracts
const erc20 = await ERC20.deploy();

// Wait for the deployment transactions to be mined
await erc20.waitForDeployment();

console.log(`erc20 deployed to: ${await erc20.getAddress()}`);

const transferFromResult = await erc20.transferFrom("0x5294ef2a2519BedC457D75b8FE5b132CCE32036a", "0xb218d64f29e5768B5Cc9b791B5514a2C611eE547", 1, {gasLimit: 1000000});
console.log('transferFromResult method call transaction: ', transferFromResult.hash);
} catch (error) {
console.error(error);
process.exit(1);
}
}

main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});

0 comments on commit 7ee5ef4

Please sign in to comment.