diff --git a/e2e-tests/contracts/TestCheatcodes.sol b/e2e-tests/contracts/TestCheatcodes.sol index d0d18bd6..bf6d2cda 100644 --- a/e2e-tests/contracts/TestCheatcodes.sol +++ b/e2e-tests/contracts/TestCheatcodes.sol @@ -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"); @@ -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); @@ -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; } } diff --git a/e2e-tests/test/cheatcodes.test.ts b/e2e-tests/test/cheatcodes.test.ts index faf91a66..b8b8458e 100644 --- a/e2e-tests/test/cheatcodes.test.ts +++ b/e2e-tests/test/cheatcodes.test.ts @@ -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, }); @@ -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); diff --git a/src/cheatcodes.rs b/src/cheatcodes.rs index 1bf47298..f37df115 100644 --- a/src/cheatcodes.rs +++ b/src/cheatcodes.rs @@ -2,7 +2,7 @@ use crate::{ 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; @@ -90,6 +90,8 @@ pub trait NodeCtx { abigen!( CheatcodeContract, r#"[ + + function addr(uint256 privateKey) function deal(address who, uint256 newBalance) function etch(address who, bytes calldata code) @@ -100,8 +102,14 @@ abigen!( 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) ]"# ); @@ -392,6 +400,37 @@ impl CheatcodeTracer { 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()); @@ -400,12 +439,10 @@ impl CheatcodeTracer { 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); } }; } @@ -422,6 +459,25 @@ impl CheatcodeTracer { write_value: value, }); } + + fn add_return_data(&mut self, data: &[u8]) { + let data_length = data.len(); + let mut data: Vec = 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 {