Skip to content

Commit

Permalink
feat: impl debug_traceCall (#151)
Browse files Browse the repository at this point in the history
* feat: impl debug_traceCall

* feat: adds test contract dir

* add refresh_test_contracts in Makefile

* fix: rename contacts_for_l2_call -> contracts_for_l2_call

* move not_implemented() to crate::utils, replace usage of macro

---------

Co-authored-by: Nicolas Villanueva <[email protected]>
  • Loading branch information
grw-ms and MexicanAce authored Oct 9, 2023
1 parent f691f8c commit 4e35082
Show file tree
Hide file tree
Showing 23 changed files with 4,439 additions and 52 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ clean-contracts:
rebuild-contracts:
cd etc/system-contracts && yarn build; yarn preprocess; yarn build-bootloader
./scripts/refresh_contracts.sh
./scripts/refresh_test_contracts.sh

# Build the Rust project
rust-build:
Expand Down
48 changes: 47 additions & 1 deletion SUPPORTED_APIS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The `status` options are:
| [`CONFIG`](#config-namespace) | [`config_setShowStorageLogs`](#config_setshowstoragelogs) | `SUPPORTED` | Updates `show_storage_logs` to print storage log reads/writes |
| [`CONFIG`](#config-namespace) | [`config_setShowVmDetails`](#config_setshowvmdetails) | `SUPPORTED` | Updates `show_vm_details` to print more detailed results from vm execution |
| [`CONFIG`](#config-namespace) | [`config_setShowGasDetails`](#config_setshowgasdetails) | `SUPPORTED` | Updates `show_gas_details` to print more details about gas estimation and usage |
| `DEBUG` | `debug_traceCall` | `NOT IMPLEMENTED`<br />[GitHub Issue #61](https://github.com/matter-labs/era-test-node/issues/61) | Performs a call and returns structured traces of the execution |
| [`DEBUG`](#debug-namespace) | [`debug_traceCall`](#debug_tracecall) | `SUPPORTED` | Performs a call and returns structured traces of the execution |
| `DEBUG` | `debug_traceBlockByHash` | `NOT IMPLEMENTED`<br />[GitHub Issue #63](https://github.com/matter-labs/era-test-node/issues/63) | Returns structured traces for operations within the block of the specified block hash |
| `DEBUG` | `debug_traceBlockByNumber` | `NOT IMPLEMENTED`<br />[GitHub Issue #64](https://github.com/matter-labs/era-test-node/issues/64) | Returns structured traces for operations within the block of the specified block number |
| `DEBUG` | `debug_traceTransaction` | `NOT IMPLEMENTED`<br />[GitHub Issue #65](https://github.com/matter-labs/era-test-node/issues/65) | Returns a structured trace of the execution of the specified transaction |
Expand Down Expand Up @@ -291,6 +291,52 @@ curl --request POST \
--data '{"jsonrpc": "2.0","id": "1","method": "config_setResolveHashes","params": [true]}'
```

## `DEBUG NAMESPACE`

### `debug_traceCall`

[source](src/debug.rs)

The `debug_traceCall` is similar to `eth_call` but returns call traces for each call.

Currently calls can only be traced on the latest block. This is the default and hence the block argument can be omitted.

The third argument mirrors the [`TraceConfig` of go-ethereum](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#traceconfig), but with the restriction that the only supported tracer is `CallTracer`. Memory, Stack and Storage traces are not supported.

#### Arguments

+ `transaction: Transaction`

+ `block: BlockNumber`

+ `tracer: TracerConfig`

#### Status

`SUPPORTED`

#### Example

```bash
curl --request POST \
--url http://localhost:8011/ \
--header 'content-type: application/json' \
--data '{
"jsonrpc": "2.0",
"id": "2",
"method": "debug_traceCall",
"params": [{
"to": "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
"data": "0x0000",
"from": "0xa61464658AfeAf65CccaaFD3a512b69A83B77618",
"gas": "0x0000",
"gasPrice": "0x0000",
"value": "0x0000",
"nonce": "0x0000"
}, "latest"]
}'
```

## `NETWORK NAMESPACE`

### `net_version`
Expand Down
25 changes: 25 additions & 0 deletions e2e-tests/contracts/Primary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Secondary.sol";

contract Primary {
Secondary secondary;

constructor(address _secondary) {
secondary = Secondary(_secondary);
}

function name() public pure returns (string memory) {
return "Primary";
}

function calculate(uint256 value) public returns (uint) {
return secondary.multiply(value);
}

function shouldRevert() public view returns (uint) {
return secondary.shouldRevert();
}
}
24 changes: 24 additions & 0 deletions e2e-tests/contracts/Secondary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Secondary {
uint data;

constructor(uint _data) {
data = _data;
}

function name() public pure returns (string memory) {
return "Secondary";
}

function multiply(uint256 value) public view returns (uint) {
return data * value;
}

function shouldRevert() public pure returns (uint) {
require(false, "This should revert");
return 1;
}
}
75 changes: 75 additions & 0 deletions e2e-tests/test/debug-apis.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { expect } from "chai";
import { Wallet } from "zksync-web3";
import * as hre from "hardhat";
import { Deployer } from "@matterlabs/hardhat-zksync-deploy";
import { RichAccounts } from "../helpers/constants";
import { deployContract, expectThrowsAsync, getTestProvider } from "../helpers/utils";

const provider = getTestProvider();

describe("debug namespace", function () {
it("Should return error if block is not 'latest' or unspecified", async function () {
expectThrowsAsync(async () => {
await provider.send("debug_traceCall", [{ to: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "earliest"]);
}, "block parameter should be 'latest' or unspecified");

expectThrowsAsync(async () => {
await provider.send("debug_traceCall", [{ to: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "1"]);
}, "block parameter should be 'latest' or unspecified");
});

it("Should only trace top-level calls with onlyTopCall", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey);

const deployer = new Deployer(hre, wallet);
const secondary = await deployContract(deployer, "Secondary", ["3"]);
await deployContract(deployer, "Primary", [secondary.address]);

const result = await provider.send("debug_traceCall", [
{
to: secondary.address,
data: secondary.interface.encodeFunctionData("multiply", ["4"]),
gas: "0x5f5e100",
},
"latest",
{ tracer: "callTracer", tracerConfig: { onlyTopCall: true } },
]);

const { calls, revertReason } = result;

// call should be successful
expect(revertReason).to.equal(null);

// should have no subcalls
expect(calls.length).to.equal(0);
});

it("Should trace contract calls", async function () {
const wallet = new Wallet(RichAccounts[0].PrivateKey);

const deployer = new Deployer(hre, wallet);
const secondary = await deployContract(deployer, "Secondary", ["3"]);
const primary = await deployContract(deployer, "Primary", [secondary.address]);

const result = await provider.send("debug_traceCall", [
{
to: primary.address,
data: primary.interface.encodeFunctionData("calculate", ["4"]),
gas: "0x5f5e100",
},
]);

const { calls, output, revertReason } = result;

// call should be successful
expect(revertReason).to.equal(null);

// subcall from primary to secondary contract should be present
const contract_call = calls[0].calls.at(-1).calls[0].calls[0];
expect(contract_call.from.toLowerCase()).to.equal(primary.address.toLowerCase());
expect(contract_call.to.toLowerCase()).to.equal(secondary.address.toLowerCase());

const [output_number] = primary.interface.decodeFunctionResult("calculate", output);
expect(output_number.toNumber()).to.equal(12);
});
});
5 changes: 5 additions & 0 deletions etc/test-contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
typechain-types
cache-zk
artifacts-zk
build-info
17 changes: 17 additions & 0 deletions etc/test-contracts/SystemConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"GUARANTEED_PUBDATA_BYTES": 4000,
"MAX_PUBDATA_PER_BATCH": 110000,
"MAX_TRANSACTIONS_IN_BATCH": 1024,
"BATCH_OVERHEAD_L2_GAS": 1200000,
"BATCH_OVERHEAD_L1_GAS": 1000000,
"L2_TX_INTRINSIC_GAS": 14070,
"L2_TX_INTRINSIC_PUBDATA": 0,
"L1_TX_INTRINSIC_L2_GAS": 167157,
"L1_TX_INTRINSIC_PUBDATA": 88,
"MAX_GAS_PER_TRANSACTION": 80000000,
"BOOTLOADER_MEMORY_FOR_TXS": 485225,
"REFUND_GAS": 7343,
"KECCAK_ROUND_COST_GAS": 40,
"SHA256_ROUND_COST_GAS": 7,
"ECRECOVER_COST_GAS": 1112
}
25 changes: 25 additions & 0 deletions etc/test-contracts/contracts/tracing/Primary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./Secondary.sol";

contract Primary {
Secondary secondary;

constructor(address _secondary) {
secondary = Secondary(_secondary);
}

function name() public pure returns (string memory) {
return "Primary";
}

function calculate(uint256 value) public returns (uint) {
return secondary.multiply(value);
}

function shouldRevert() public view returns (uint) {
return secondary.shouldRevert();
}
}
24 changes: 24 additions & 0 deletions etc/test-contracts/contracts/tracing/Secondary.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Secondary {
uint data;

constructor(uint _data) {
data = _data;
}

function name() public pure returns (string memory) {
return "Secondary";
}

function multiply(uint256 value) public view returns (uint) {
return data * value;
}

function shouldRevert() public pure returns (uint) {
require(false, "This should revert");
return 1;
}
}
37 changes: 37 additions & 0 deletions etc/test-contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import '@nomiclabs/hardhat-solpp';
import '@typechain/hardhat'
import '@nomiclabs/hardhat-ethers';
import '@matterlabs/hardhat-zksync-solc';

const systemConfig = require('./SystemConfig.json');

export default {
zksolc: {
version: '1.3.14',
compilerSource: 'binary',
settings: {
isSystem: true
}
},
zkSyncDeploy: {
zkSyncNetwork: 'http://localhost:3050',
ethNetwork: 'http://localhost:8545'
},
solidity: {
version: '0.8.17'
},
solpp: {
defs: (() => {
return {
ECRECOVER_COST_GAS: systemConfig.ECRECOVER_COST_GAS,
KECCAK_ROUND_COST_GAS: systemConfig.KECCAK_ROUND_COST_GAS,
SHA256_ROUND_COST_GAS: systemConfig.SHA256_ROUND_COST_GAS
}
})()
},
networks: {
hardhat: {
zksync: true
}
}
};
48 changes: 48 additions & 0 deletions etc/test-contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "system-contracts",
"version": "0.1.0",
"repository": "[email protected]:matter-labs/system-contracts.git",
"license": "MIT",
"dependencies": {
"@matterlabs/hardhat-zksync-deploy": "^0.6.5",
"@nomiclabs/hardhat-solpp": "^2.0.1",
"commander": "^9.4.1",
"ethers": "^5.7.0",
"hardhat": "^2.11.0",
"preprocess": "^3.2.0",
"zksync-web3": "^0.13.0"
},
"devDependencies": {
"@matterlabs/hardhat-zksync-solc": "^0.4.2",
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/ethers-v6": "0.4.0",
"@typechain/hardhat": "8.0.0",
"@types/chai": "^4.3.1",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.34",
"chai": "^4.3.6",
"mocha": "^10.0.0",
"prettier": "^2.3.0",
"prettier-plugin-solidity": "^1.0.0-alpha.27",
"template-file": "^6.0.1",
"ts-generator": "^0.1.1",
"ts-node": "^10.7.0",
"typechain": "^8.1.1",
"typescript": "^4.6.4"
},
"mocha": {
"timeout": 240000,
"exit": true,
"color": false,
"slow": 0,
"require": [
"ts-node/register"
]
},
"scripts": {
"build": "hardhat compile",
"clean": "hardhat clean"
},
"packageManager": "[email protected]"
}
Loading

0 comments on commit 4e35082

Please sign in to comment.