From a3070132313c6883f35cd691c6026e6a905eab26 Mon Sep 17 00:00:00 2001 From: emilevgenievgeorgiev <133787159+emilevgenievgeorgiev@users.noreply.github.com> Date: Mon, 3 Feb 2025 17:52:10 +0200 Subject: [PATCH] =?UTF-8?q?test:=20extended=20coverage=20for=20debug=5Ftra?= =?UTF-8?q?ceTransaction=20with=20opcode=20logger=E2=80=A6=20(#875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: extended coverage for debug_traceTransaction with opcode logger tests Signed-off-by: emilevgenievgeorgiev * chore: fix tests Signed-off-by: nikolay * chore: delete unused artifacts Signed-off-by: nikolay * chore: fix test Signed-off-by: nikolay * chore: fix test Signed-off-by: nikolay --------- Signed-off-by: emilevgenievgeorgiev Signed-off-by: nikolay Co-authored-by: nikolay --- .../OpcodeLogger.sol/OpcodeLogger.json | 136 ++++++++ .../TokenCreateOpcodeLogger.json | 265 +++++++++++++++ .../solidity/opcode-logger/OpcodeLogger.sol | 47 +++ .../opcode-logger/TokenCreateOpcodeLogger.sol | 109 ++++++ test/constants.js | 1 + .../{OpcodeLogger.js => opcodeLogger.js} | 319 +++++++++++++++++- test/utils.js | 9 + 7 files changed, 883 insertions(+), 3 deletions(-) create mode 100644 contracts-abi/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol/TokenCreateOpcodeLogger.json create mode 100644 contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol rename test/solidity/opcode-logger/{OpcodeLogger.js => opcodeLogger.js} (75%) diff --git a/contracts-abi/contracts/solidity/opcode-logger/OpcodeLogger.sol/OpcodeLogger.json b/contracts-abi/contracts/solidity/opcode-logger/OpcodeLogger.sol/OpcodeLogger.json index 27c6e6e74..c43e7a36e 100644 --- a/contracts-abi/contracts/solidity/opcode-logger/OpcodeLogger.sol/OpcodeLogger.json +++ b/contracts-abi/contracts/solidity/opcode-logger/OpcodeLogger.sol/OpcodeLogger.json @@ -100,6 +100,142 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "int64[]", + "name": "amounts", + "type": "int64[]" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "executeHtsMintTokenRevertingCalls", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "int64[]", + "name": "amounts", + "type": "int64[]" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "executeHtsMintTokenRevertingCallsAndFailToAssociate", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenExternalCall", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64[]", + "name": "amounts", + "type": "int64[]" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "nestEverySecondHtsMintTokenCall", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "owner", diff --git a/contracts-abi/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol/TokenCreateOpcodeLogger.json b/contracts-abi/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol/TokenCreateOpcodeLogger.json new file mode 100644 index 000000000..e3b4abe88 --- /dev/null +++ b/contracts-abi/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol/TokenCreateOpcodeLogger.json @@ -0,0 +1,265 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "CallResponseEvent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "CreatedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "kycGranted", + "type": "bool" + } + ], + "name": "KycGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "indexed": false, + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "name": "MintedToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "name": "ResponseCode", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "associateTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "createNonFungibleTokenPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "bytes[]", + "name": "metadata", + "type": "bytes[]" + } + ], + "name": "mintTokenPublic", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "int64", + "name": "newTotalSupply", + "type": "int64" + }, + { + "internalType": "int64[]", + "name": "serialNumbers", + "type": "int64[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunctionSelector", + "type": "bytes" + } + ], + "name": "redirectForToken", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serialNumber", + "type": "uint256" + } + ], + "name": "transferFromNFT", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/contracts/solidity/opcode-logger/OpcodeLogger.sol b/contracts/solidity/opcode-logger/OpcodeLogger.sol index 6ca1ab3a5..67f9c3ada 100644 --- a/contracts/solidity/opcode-logger/OpcodeLogger.sol +++ b/contracts/solidity/opcode-logger/OpcodeLogger.sol @@ -74,4 +74,51 @@ contract OpcodeLogger { return isSuccess; } + + function executeHtsMintTokenRevertingCalls(address contractAddress, address tokenAddress, int64[] memory amounts, bytes[] memory metadata) external returns (bool success) { + for (uint i = 0; i < amounts.length; i++) { + if (amounts[i] < 0){ + // reverts with 'Minting reverted with INVALID_TOKEN_ID' + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(contractAddress, 0, metadata)); + } else { + // reverts with 'Minting tokens reverted with TOKEN_MAX_SUPPLY_REACHED' + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(tokenAddress, amounts[i], metadata)); + } + } + } + + function executeHtsMintTokenRevertingCallsAndFailToAssociate(address contractAddress, address tokenAddress, int64[] memory amounts, bytes[] memory metadata) external returns (bool success) { + for (uint i = 0; i < amounts.length; i++) { + if (amounts[i] < 0){ + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(contractAddress, 0, metadata)); + } else { + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(tokenAddress, amounts[i], metadata)); + } + } + + // reverts with 'Association reverted with TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT' + (success,) = address(contractAddress).call(abiEncodeAssociateTokenPublic(contractAddress, tokenAddress)); + } + + function nestEverySecondHtsMintTokenCall(address contractAddress, address token, int64[] memory amounts, bytes[] memory metadata) external returns (bool success) { + for (uint i = 0; i < amounts.length; i++) { + if (i % 2 == 0){ + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(token, amounts[i], metadata)); + } else { + this.mintTokenExternalCall(contractAddress, token, amounts[i], metadata); + } + } + } + + function mintTokenExternalCall(address contractAddress, address token, int64 amount, bytes[] memory metadata) external returns (bool success) { + (success,) = address(contractAddress).call(abiEncodeMintTokenPublic(token, amount, metadata)); + } + + function abiEncodeMintTokenPublic(address token, int64 amount, bytes[] memory metadata) internal pure returns (bytes memory abiEncodedData) { + return abi.encodeWithSignature("mintTokenPublic(address,int64,bytes[])", token, amount, metadata); + } + + function abiEncodeAssociateTokenPublic(address account, address token) internal pure returns (bytes memory abiEncodedData) { + return abi.encodeWithSignature("associateTokenPublic(address,address)", account, token); + } } diff --git a/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol b/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol new file mode 100644 index 000000000..eae8d284a --- /dev/null +++ b/contracts/solidity/opcode-logger/TokenCreateOpcodeLogger.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "contracts/system-contracts/hedera-token-service/HederaTokenService.sol"; +import "contracts/system-contracts/hedera-token-service/ExpiryHelper.sol"; +import "contracts/system-contracts/hedera-token-service/KeyHelper.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + +contract TokenCreateOpcodeLogger is HederaTokenService, ExpiryHelper, KeyHelper { + + string name = "tokenName"; + string symbol = "tokenSymbol"; + string memo = "memo"; + int64 initialTotalSupply = 10000; + int64 maxSupply = 10000; + int32 decimals = 8; + bool freezeDefaultStatus = false; + + event ResponseCode(int responseCode); + event CreatedToken(address tokenAddress); + event MintedToken(int64 newTotalSupply, int64[] serialNumbers); + event KycGranted(bool kycGranted); + + function createFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = + HederaTokenService.createFungibleToken(token, initialTotalSupply, decimals); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + + function createNonFungibleTokenPublic( + address treasury + ) public payable { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](5); + keys[0] = getSingleKey(KeyType.ADMIN, KeyType.PAUSE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[1] = getSingleKey(KeyType.KYC, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[2] = getSingleKey(KeyType.FREEZE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[3] = getSingleKey(KeyType.SUPPLY, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + keys[4] = getSingleKey(KeyType.WIPE, KeyValueType.INHERIT_ACCOUNT_KEY, bytes("")); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry( + 0, treasury, 8000000 + ); + + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + name, symbol, treasury, memo, true, maxSupply, freezeDefaultStatus, keys, expiry + ); + + (int responseCode, address tokenAddress) = HederaTokenService.createNonFungibleToken(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + emit CreatedToken(tokenAddress); + } + + function mintTokenPublic(address token, int64 amount, bytes[] memory metadata) public + returns (int responseCode, int64 newTotalSupply, int64[] memory serialNumbers) { + (responseCode, newTotalSupply, serialNumbers) = HederaTokenService.mintToken(token, amount, metadata); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + if (responseCode == HederaResponseCodes.INVALID_TOKEN_ID) { + revert(string(abi.encodePacked("Minting reverted with INVALID_TOKEN_ID"))); + } else if (responseCode == HederaResponseCodes.TOKEN_MAX_SUPPLY_REACHED) { + revert(string(abi.encodePacked("Minting ", Strings.toString((uint256(uint64(amount)))), " tokens reverted with TOKEN_MAX_SUPPLY_REACHED"))); + } + + revert (string(abi.encodePacked("Minting was reverted with responseCode: ", Strings.toString(uint256(responseCode))))); + } + emit MintedToken(newTotalSupply, serialNumbers); + } + + function associateTokenPublic(address account, address token) public returns (int responseCode) { + responseCode = HederaTokenService.associateToken(account, token); + emit ResponseCode(responseCode); + + if (responseCode != HederaResponseCodes.SUCCESS) { + if (responseCode == HederaResponseCodes.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT) { + revert(string(abi.encodePacked("Association reverted with TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT"))); + } + revert ("Default associateTokenPublic() revert reason"); + } + } +} diff --git a/test/constants.js b/test/constants.js index 44d475382..7ee65ec99 100644 --- a/test/constants.js +++ b/test/constants.js @@ -77,6 +77,7 @@ const Contract = { OZERC20Mock: 'OZERC20Mock', OZERC721Mock: 'OZERC721Mock', TokenCreateContract: 'TokenCreateContract', + TokenCreateOpcodeLogger: 'TokenCreateOpcodeLogger', DiamondCutFacet: 'DiamondCutFacet', Diamond: 'Diamond', DiamondInit: 'DiamondInit', diff --git a/test/solidity/opcode-logger/OpcodeLogger.js b/test/solidity/opcode-logger/opcodeLogger.js similarity index 75% rename from test/solidity/opcode-logger/OpcodeLogger.js rename to test/solidity/opcode-logger/opcodeLogger.js index 4656b75b7..56f9070db 100644 --- a/test/solidity/opcode-logger/OpcodeLogger.js +++ b/test/solidity/opcode-logger/opcodeLogger.js @@ -1,8 +1,29 @@ +/*- + * + * Hedera Smart Contracts + * + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + const Constants = require('../../constants'); -const {expect} = require('chai'); +const { expect, assert } = require('chai'); const hre = require('hardhat'); const fs = require('fs'); const {ethers} = hre; +const { hexToASCII } = require('../../utils') const BESU_RESULTS_JSON_PATH = __dirname + '/opcodeLoggerBesuResults.json'; const IS_BESU_NETWORK = hre.network.name === 'besu_local'; @@ -17,7 +38,7 @@ describe('@OpcodeLogger Test Suite', async function () { randomAddress = (ethers.Wallet.createRandom()).address; const factoryOpcodeLogger = await ethers.getContractFactory(Constants.Contract.OpcodeLogger); - opcodeLogger = await factoryOpcodeLogger.deploy(); + opcodeLogger = await factoryOpcodeLogger.deploy({gasLimit: 5_000_000}); await opcodeLogger.waitForDeployment(); }); @@ -599,12 +620,18 @@ describe('@OpcodeLogger Test Suite', async function () { } describe('nested calls', async function () { - let errorsExternal; + let errorsExternal, nestedContractCreateTx; before(async () => { const factoryErrorsExternal = await ethers.getContractFactory(Constants.Contract.ErrorsExternal); errorsExternal = await factoryErrorsExternal.deploy(); await errorsExternal.waitForDeployment(); + + const contractCreatorFactory = await ethers.getContractFactory(Constants.Contract.ContractCreator); + const contractCreator = await contractCreatorFactory.deploy(); + await contractCreator.waitForDeployment(); + const contractByteCode = (await hre.artifacts.readArtifact('Base')).bytecode; + nestedContractCreateTx = await contractCreator.createNewContract(contractByteCode); }); it('successful NESTED CALL to existing contract with disabledMemory, disabledStack, disabledStorage set to true', async function () { @@ -796,15 +823,69 @@ describe('@OpcodeLogger Test Suite', async function () { expect(sl.stack).to.equal(null); }); }); + + it('successful NESTED Create CALL Deploy a contract which successfully deploys another contract with disableMemory, DisableStack and disableStorage set to true', async function () { + const res = await executeDebugTraceTransaction(nestedContractCreateTx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: true + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('successful NESTED Create CALL Deploy a contract which successfully deploys another contract with disableMemory, DisableStack and disableStorage set to false', async function () { + const res = await executeDebugTraceTransaction(nestedContractCreateTx.hash, { + tracer: 'opcodeLogger', + disableStorage: false, + disableMemory: false, + disableStack: false + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.not.equal(null); + expect(sl.memory).to.not.equal(null); + expect(sl.stack).to.not.equal(null); + }); + }); }); describe('precompiles', async function () { let precompiles; + let tokenCreateContract; + let tokenCreateTx; + let tokenCreateContractAddress; + let tokenAddress; before(async () => { const factoryPrecompiles = await ethers.getContractFactory(Constants.Contract.Precompiles); precompiles = await factoryPrecompiles.deploy(); await precompiles.waitForDeployment(); + + const tokenCreateFactory = await ethers.getContractFactory(Constants.Contract.TokenCreateOpcodeLogger); + tokenCreateContract = await tokenCreateFactory.deploy(Constants.GAS_LIMIT_1_000_000); + await tokenCreateContract.waitForDeployment(); + tokenCreateTx = await tokenCreateContract.createFungibleTokenPublic( + await tokenCreateContract.getAddress(), + { + value: BigInt('10000000000000000000'), + gasLimit: 1_000_000, + } + ); + const tokenAddressReceipt = await tokenCreateTx.wait(); + tokenAddress = { tokenAddress } = tokenAddressReceipt.logs.filter( + (e) => e.fragment.name === Constants.Events.CreatedToken + )[0].args.tokenAddress; + tokenCreateContractAddress = await tokenCreateContract.getAddress(); }); it('successful ETH precompile call to 0x2 with disabledMemory, disabledStack, disabledStorage set to true', async function () { @@ -886,5 +967,237 @@ describe('@OpcodeLogger Test Suite', async function () { expect(sl.stack).to.not.equal(null); }); }); + + it('successful ETH precompile call to 0x2 with disabledStorage set to false, disabledMemory, disabledStack set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2); + await tx.wait(); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: false, + disableMemory: true, + disableStack: true + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.not.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('failing ETH precompile call to 0x2 with disabledStorage set to false, disabledMemory, disabledStack set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2, { gasLimit: 21_496 }); + await expect(tx.wait()).to.be.rejectedWith(Error); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: false, + disableMemory: true, + disableStack: true + }); + + expect(res.failed).to.be.true; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.not.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('successful ETH precompile call to 0x2 with disabledMemory set to false, disabledStorage, disabledStack set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2); + await tx.wait(); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: false, + disableStack: true + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.not.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('failing ETH precompile call to 0x2 with disabledMemory set to false, disabledStorage, disabledStack set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2, { gasLimit: 21_496 }); + await expect(tx.wait()).to.be.rejectedWith(Error); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: false, + disableStack: true + }); + + expect(res.failed).to.be.true; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.not.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('successful ETH precompile call to 0x2 with disabledStack set to false, disabledStorage, disabledMemory set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2); + await tx.wait(); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: false + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.not.equal(null); + }); + }); + + it('failing ETH precompile call to 0x2 with disabledStack set to false, disabledStorage, disabledMemory set to true', async function () { + const tx = await precompiles.modExp(5644, 3, 2, { gasLimit: 21_496 }); + await expect(tx.wait()).to.be.rejectedWith(Error); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: false + }); + + expect(res.failed).to.be.true; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.not.equal(null); + }); + }); + + it('successful tokenCreate call with disabledStorage, disabledMemory, disabledStack set to true', async function () { + const res = await executeDebugTraceTransaction(tokenCreateTx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: true + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.equal(null); + expect(sl.memory).to.equal(null); + expect(sl.stack).to.equal(null); + }); + }); + + it('successful tokenCreate call with disabledStorage, disabledMemory, disabledStack set to false', async function () { + const res = await executeDebugTraceTransaction(tokenCreateTx.hash, { + tracer: 'opcodeLogger', + disableStorage: false, + disableMemory: false, + disableStack: false + }); + + expect(res.failed).to.be.false; + expect(res.structLogs.length).to.be.greaterThan(0); + res.structLogs.map(function (sl) { + expect(sl.storage).to.not.equal(null); + expect(sl.memory).to.not.equal(null); + expect(sl.stack).to.not.equal(null); + }); + }); + + it('should not contain revert operation when GAS is depleted (insufficient)', async function () { + const tx = await tokenCreateContract.createNonFungibleTokenPublic( + tokenCreateContractAddress, + { gasLimit: 21432 } + ); + + const res = await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: true + }); + + const revertOperations = res.structLogs.filter(function (opLog) { + return opLog.op === "REVERT" + }); + expect(revertOperations.length).to.equal(0); + }); + }); + + describe('negative', async function () { + it('should fail to debug a transaction with invalid hash', async function () { + const res = await executeDebugTraceTransaction('0x0fdfb3da2d40cd9ac8776ca02c17cb4aae634d2726f5aad049ab4ce5056b1a5c', { + tracer: 'opcodeLogger', + disableStorage: true, + disableMemory: true, + disableStack: true + }); + + expect(res.failed).to.be.true; + expect(res.structLogs).to.be.empty; + }); + + it('should fail with invalid parameter value type for disableMemory, disableStack or disableStorage', async function () { + const tx = await opcodeLogger.call(opcodeLogger.target, '0xdbdf7fce'); // calling resetCounter() + await tx.wait(); + try { + await executeDebugTraceTransaction(tx.hash, { + tracer: 'opcodeLogger', + disableStorage: 'true', + disableMemory: 1, + disableStack: 0 + }); + + } catch (error) { + expect(error.name).to.equal('ProviderError'); + expect(error._isProviderError).to.be.true; + expect(error._stack).to.contain('Invalid parameter 2: Invalid tracerConfig'); + + return; + } + + assert.fail('Executing debug trace transaction with invalid parameter value types did not result in error') + }); + + it('should fail when executing debug trace transaction with incorrect tracer parameter', async function () { + const tx = await opcodeLogger.call(opcodeLogger.target, '0xdbdf7fce'); // calling resetCounter() + await tx.wait(); + const incorrectTracer = 'opcodeLogger1'; + + try { + await executeDebugTraceTransaction(tx.hash, { + tracer: incorrectTracer, + disableStorage: true, + disableMemory: true, + disableStack: true + }); + } catch (error) { + expect(error.name).to.equal('ProviderError'); + expect(error._isProviderError).to.be.true; + expect(error._stack).to.contain(`Invalid parameter 1: Invalid tracer type, value: ${incorrectTracer}`); + + return; + } + + assert.fail('Executing debug trace transaction with incorrect tracer parameter did not result in error') + }); }); }); diff --git a/test/utils.js b/test/utils.js index ed639e36a..67a1d2cf4 100644 --- a/test/utils.js +++ b/test/utils.js @@ -30,6 +30,15 @@ class Utils { static to32ByteString(str) { return str.toString(16).replace('0x', '').padStart(64, '0'); }; + + static hexToASCII(str) { + const hex = str.toString(); + let ascii = ''; + for (let n = 0; n < hex.length; n += 2) { + ascii += String.fromCharCode(parseInt(hex.substring(n, n + 2), 16)); + } + return ascii; + }; } module.exports = Utils;