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

feat: add vm.toString cheatcode #29

Open
wants to merge 3 commits into
base: grw/feat-cheatcode-tracer
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 100 additions & 17 deletions e2e-tests/contracts/TestCheatcodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ contract TestCheatcodes {
require(success, "setGreeting failed");
}

function testLoad(bytes32 slot) external {
TestLoadTarget testLoadTarget = new TestLoadTarget();
(bool success, bytes memory data) = CHEATCODE_ADDRESS.call(
abi.encodeWithSignature("load(address,bytes32)", address(testLoadTarget), slot)
);
require(success, "load failed");
bytes32 loadedValue = abi.decode(data, (bytes32));
require(loadedValue == bytes32(uint256(1337)), "address mismatch");
}

function testRoll(uint256 blockNumber) external {
uint256 initialBlockNumber = block.number;
require(blockNumber != initialBlockNumber, "block number must be different than current block number");
Expand Down Expand Up @@ -126,17 +136,6 @@ contract TestCheatcodes {
);
}

function testWarp(uint256 timestamp) external {
uint256 initialTimestamp = block.timestamp;
require(timestamp != initialTimestamp, "timestamp must be different than current block timestamp");

(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("warp(uint256)", timestamp));
require(success, "warp failed");

uint256 finalTimestamp = block.timestamp;
require(finalTimestamp == timestamp, "timestamp was not changed");
}

function testStore(bytes32 slot, bytes32 value) external {
testStoreTarget testStoreInstance = new testStoreTarget();
testStoreInstance.testStoredValue(0);
Expand All @@ -149,12 +148,96 @@ contract TestCheatcodes {
testStoreInstance.testStoredValue(value);
}

function testLoad(bytes32 slot) external {
TestLoadTarget testLoadTarget = new TestLoadTarget();
(bool success, bytes memory data) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("load(address,bytes32)", address(testLoadTarget), slot));
require(success, "load failed");
bytes32 loadedValue = abi.decode(data, (bytes32));
require(loadedValue == bytes32(uint256(1337)), "address mismatch");
function testToStringFromAddress() external {
address testAddress = 0x413D15117be7a498e68A64FcfdB22C6e2AaE1808;
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(
abi.encodeWithSignature("toString(address)", testAddress)
);
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(
keccak256(bytes(testString)) == keccak256(bytes("0x413D15117be7a498e68A64FcfdB22C6e2AaE1808")),
"toString mismatch"
);
}

function testToStringFromBool() external {
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("toString(bool)", false));
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(keccak256(bytes(testString)) == keccak256(bytes("false")), "toString mismatch");

(success, rawData) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("toString(bool)", true));
data = trimReturnBytes(rawData);
testString = string(abi.encodePacked(data));
require(keccak256(bytes(testString)) == keccak256(bytes("true")), "toString mismatch");
}

function testToStringFromUint256(uint256 value, string memory stringValue) external {
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("toString(uint256)", value));
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(keccak256(bytes(testString)) == keccak256(bytes(stringValue)), "toString mismatch");
}

function testToStringFromInt256(int256 value, string memory stringValue) external {
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("toString(int256)", value));
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(keccak256(bytes(testString)) == keccak256(bytes(stringValue)), "toString mismatch");
}

function testToStringFromBytes32() external {
bytes32 testBytes = hex"4ec893b0a778b562e893cee722869c3e924e9ee46ec897cabda6b765a6624324";
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(
abi.encodeWithSignature("toString(bytes32)", testBytes)
);
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(
keccak256(bytes(testString)) ==
keccak256(bytes("0x4ec893b0a778b562e893cee722869c3e924e9ee46ec897cabda6b765a6624324")),
"toString mismatch"
);
}

function testToStringFromBytes() external {
bytes memory testBytes = hex"89987299ea14decf0e11d068474a6e459439802edca8aacf9644222e490d8ef6db";
(bool success, bytes memory rawData) = CHEATCODE_ADDRESS.call(
abi.encodeWithSignature("toString(bytes)", testBytes)
);
bytes memory data = trimReturnBytes(rawData);
string memory testString = string(abi.encodePacked(data));
require(
keccak256(bytes(testString)) ==
keccak256(bytes("0x89987299ea14decf0e11d068474a6e459439802edca8aacf9644222e490d8ef6db")),
"toString mismatch"
);
}

function testWarp(uint256 timestamp) external {
uint256 initialTimestamp = block.timestamp;
require(timestamp != initialTimestamp, "timestamp must be different than current block timestamp");

(bool success, ) = CHEATCODE_ADDRESS.call(abi.encodeWithSignature("warp(uint256)", timestamp));
require(success, "warp failed");

uint256 finalTimestamp = block.timestamp;
require(finalTimestamp == timestamp, "timestamp was not changed");
}

function trimReturnBytes(bytes memory rawData) internal pure returns (bytes memory) {
uint256 lengthStartingPos = rawData.length - 32;
bytes memory lengthSlice = new bytes(32);
for (uint256 i = 0; i < 32; i++) {
lengthSlice[i] = rawData[lengthStartingPos + i];
}
uint256 length = abi.decode(lengthSlice, (uint256));
bytes memory data = new bytes(length);
for (uint256 i = 0; i < length; i++) {
data[i] = rawData[i];
}
return data;
}
}

Expand Down
93 changes: 91 additions & 2 deletions e2e-tests/test/cheatcodes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ describe("Cheatcodes", function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);
const slot = hre.ethers.constants.HashZero;
const value = hre.ethers.constants.MaxUint256;

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
const slot = hre.ethers.constants.HashZero;
const value = hre.ethers.constants.MaxUint256;
const tx = await cheatcodes.testStore(slot, value, {
gasLimit: 10000000,
});
Expand All @@ -175,6 +175,95 @@ describe("Cheatcodes", function () {
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from address", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
const tx = await cheatcodes.testToStringFromAddress({ gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from bool", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
const tx = await cheatcodes.testToStringFromBool({ gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from uint256", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);
const value = hre.ethers.BigNumber.from(hre.ethers.utils.randomBytes(32));

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);

const tx = await cheatcodes.testToStringFromUint256(value, value.toString(), { gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from int256", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);
const value = hre.ethers.BigNumber.from(hre.ethers.utils.randomBytes(32));

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);

const tx = await cheatcodes.testToStringFromUint256(value, value.toString(), { gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from bytes32", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);

const tx = await cheatcodes.testToStringFromBytes32({ gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.toString from bytes", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
const deployer = new Deployer(hre, wallet);

// Act
const cheatcodes = await deployContract(deployer, "TestCheatcodes", []);
const tx = await cheatcodes.testToStringFromBytes({ gasLimit: 100000000 });
const receipt = await tx.wait();

// Assert
expect(receipt.status).to.eq(1);
});

it("Should test vm.warp", async function () {
// Arrange
const wallet = new Wallet(RichAccounts[0].PrivateKey);
Expand Down
72 changes: 64 additions & 8 deletions src/cheatcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
node::{BlockContext, InMemoryNodeInner},
utils::bytecode_to_factory_dep,
};
use ethers::{abi::AbiDecode, prelude::abigen};
use ethers::{abi::AbiDecode, prelude::abigen, utils::to_checksum};
use itertools::Itertools;
use multivm::zk_evm_1_3_3::tracing::AfterExecutionData;
use multivm::zk_evm_1_3_3::vm_state::PrimitiveValue;
Expand Down Expand Up @@ -90,6 +90,8 @@
abigen!(
CheatcodeContract,
r#"[


function addr(uint256 privateKey)
function deal(address who, uint256 newBalance)
function etch(address who, bytes calldata code)
Expand All @@ -100,8 +102,14 @@
function startPrank(address sender)
function startPrank(address sender, address origin)
function stopPrank()
function warp(uint256 timestamp)
function store(address account, bytes32 slot, bytes32 value)
function toString(address value)
function toString(bool value)
function toString(uint256 value)
function toString(int256 value)
function toString(bytes32 value)
function toString(bytes value)
function warp(uint256 timestamp)
]"#
);

Expand Down Expand Up @@ -392,6 +400,37 @@
let key = StorageKey::new(AccountTreeId::new(account), H256(slot));
self.write_storage(key, H256(value), storage);
}
ToString0(ToString0Call { value }) => {
tracing::info!("Converting address into string");
let address = Address::from(value);
let address_with_checksum = to_checksum(&address, None);
self.add_return_data(address_with_checksum.as_bytes());
}
ToString1(ToString1Call { value }) => {
tracing::info!("Converting bool into string");
let bool_value = value.to_string();
self.add_return_data(bool_value.as_bytes());
}
ToString2(ToString2Call { value }) => {
tracing::info!("Converting uint256 into string");
let uint_value = value.to_string();
self.add_return_data(uint_value.as_bytes());
}
ToString3(ToString3Call { value }) => {
tracing::info!("Converting int256 into string");
let int_value = value.to_string();
self.add_return_data(int_value.as_bytes());
}
ToString4(ToString4Call { value }) => {
tracing::info!("Converting bytes32 into string");
let bytes_value = format!("0x{}", hex::encode(value));
self.add_return_data(bytes_value.as_bytes());
}
ToString5(ToString5Call { value }) => {
tracing::info!("Converting bytes into string");
let bytes_value = format!("0x{}", hex::encode(value));
self.add_return_data(bytes_value.as_bytes());
}
Warp(WarpCall { timestamp }) => {
tracing::info!("Setting block timestamp {}", timestamp);
self.node_ctx.set_time(timestamp.as_u64());
Expand All @@ -400,12 +439,10 @@
AccountTreeId::new(zksync_types::SYSTEM_CONTEXT_ADDRESS),
zksync_types::CURRENT_VIRTUAL_BLOCK_INFO_POSITION,
);
let mut storage = storage.borrow_mut();
let (block_number, _) = unpack_block_info(h256_to_u256(storage.read_value(&key)));
storage.set_value(
key,
u256_to_h256(pack_block_info(block_number, timestamp.as_u64())),
);
let (block_number, _) =
unpack_block_info(h256_to_u256(storage.borrow_mut().read_value(&key)));
let value = u256_to_h256(pack_block_info(block_number, timestamp.as_u64()));
self.write_storage(key, value, storage);
}
};
}
Expand All @@ -422,6 +459,25 @@
write_value: value,
});
}

fn add_return_data(&mut self, data: &[u8]) {
let data_length = data.len();
let mut data: Vec<U256> = data
.chunks(32)
.map(|b| {
// Copies the bytes into a 32 byte array
// padding with zeros to the right if necessary
let mut bytes = [0u8; 32];
bytes[..b.len()].copy_from_slice(b);
bytes.into()
})
.collect_vec();

// Add the length of the data to the end of the return data
data.push(data_length.into());

self.returndata = Some(data);
}
}

pub struct CheatcodeNodeContext<T> {
Expand Down Expand Up @@ -468,14 +524,14 @@
use crate::{
deps::system_contracts::bytecode_from_slice,
http_fork_source::HttpForkSource,
node::{InMemoryNode, TransactionResult},

Check failure on line 527 in src/cheatcodes.rs

View workflow job for this annotation

GitHub Actions / unit-tests (macos-latest)

unused imports: `LogBuilder`, `TransactionBuilder`, `TransactionResult`
testing::{self, LogBuilder, TransactionBuilder},
};
use ethers::abi::{short_signature, AbiEncode, HumanReadableParser, ParamType, Token};

Check failure on line 530 in src/cheatcodes.rs

View workflow job for this annotation

GitHub Actions / unit-tests (macos-latest)

unused imports: `AbiEncode`, `HumanReadableParser`, `ParamType`, `Token`
use zksync_basic_types::{Address, L2ChainId, Nonce, H160, H256, U256};
use zksync_core::api_server::web3::backend_jsonrpc::namespaces::eth::EthNamespaceT;
use zksync_types::{
api::{Block, CallTracerConfig, SupportedTracers, TransactionReceipt},

Check failure on line 534 in src/cheatcodes.rs

View workflow job for this annotation

GitHub Actions / unit-tests (macos-latest)

unused imports: `Block`, `CallTracerConfig`, `SupportedTracers`, `TransactionReceipt`, `transaction_request::CallRequestBuilder`
fee::Fee,
l2::L2Tx,
transaction_request::CallRequestBuilder,
Expand Down
Loading