Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ public void traceAccountCreationResult(
// reason is present, since that means creation failed before executing the frame's
// code, and tracePostExecution() will never be called; so this is our only chance
// to keep the action stack in sync with the message frame stack.
if (hasActionSidecarsEnabled(frame) && haltReason.isPresent()) {
// We skip finalizing for empty action stack, as those produce warnings.
if (hasActionSidecarsEnabled(frame) && haltReason.isPresent() && !actionStack.isEmpty()) {
actionStack.finalizeLastAction(frame, stackValidationChoice(frame));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ public void pushActionOfIntermediate(@NonNull final MessageFrame frame) {
completePush(builder, requireNonNull(frame.getMessageFrameStack().peek()));
}

public boolean isEmpty() {
return actionsStack.isEmpty();
}

private void completePush(@NonNull ContractAction.Builder builder, @NonNull final MessageFrame frame) {
builder.callType(asActionType(frame.getType()))
.gas(frame.getRemainingGas())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;

Expand Down Expand Up @@ -178,6 +179,15 @@ void accountCreationTraceFinalizesWithSidecarsAndHaltReason() {
verify(actionStack).finalizeLastAction(frame, ActionStack.Validation.ON);
}

@Test
void contractCreationTraceFinalizesWithSidecarsAndHaltReason() {
givenSidecarsOnly();
given(actionStack.isEmpty()).willReturn(true);
subject.traceAccountCreationResult(frame, Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS));

verify(actionStack, never()).finalizeLastAction(frame, ActionStack.Validation.ON);
}

private void givenNoActionSidecars() {
givenConfig(false, false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static org.hyperledger.besu.evm.frame.MessageFrame.Type.CONTRACT_CREATION;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -606,6 +607,18 @@ void tracksIntermediateCallAsExpected() {
assertEquals(NON_SYSTEM_CONTRACT_ID, action.callingContract());
}

@Test
void testIsEmptyWithEmptyStack() {
assertTrue(subject.isEmpty());
}

@Test
void testIsEmptyWithNonEmptyStack() {
final var wrappedAction = new ActionWrapper(CALL_ACTION);
actionsStack.push(wrappedAction);
assertFalse(subject.isEmpty());
}

private void givenResolvableEvmAddress() {
given(parentFrame.getWorldUpdater()).willReturn(worldUpdater);
given(worldUpdater.getHederaAccount(EIP_1014_ADDRESS)).willReturn(evmAccount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertCreationMaxAssociations;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertCreationViaCallMaxAssociations;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertHgcaaLogDoesNotContain;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.contractListWithPropertiesInheritedFrom;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.getEcdsaPrivateKeyFromSpec;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed;
Expand Down Expand Up @@ -95,6 +96,7 @@
import com.hedera.node.app.hapi.utils.ethereum.EthTxData;
import com.hedera.services.bdd.junit.HapiTest;
import com.hedera.services.bdd.junit.LeakyHapiTest;
import com.hedera.services.bdd.junit.hedera.NodeSelector;
import com.hedera.services.bdd.spec.HapiSpec;
import com.hedera.services.bdd.spec.assertions.ContractInfoAsserts;
import com.hedera.services.bdd.spec.keys.KeyShape;
Expand All @@ -114,6 +116,7 @@
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -913,6 +916,20 @@ final Stream<DynamicTest> contractCreateGas() {
}));
}

// Reproducing issue stated in #21821
@HapiTest
Stream<DynamicTest> contractDeployFailsDuringExecution() {
return hapiTest(
uploadInitCode("HushSenseManager"),
// fails during execution due to insufficient gas
contractCreate("HushSenseManager").gas(200_000L).hasKnownStatus(ResponseCodeEnum.INSUFFICIENT_GAS),
// Ensure we don't log for empty action stack anymore
assertHgcaaLogDoesNotContain(
NodeSelector.allNodes(), "Action stack prematurely empty", Duration.ofSeconds(10)),
// succeeds with enough gas
contractCreate("HushSenseManager").gas(1_000_000L).hasKnownStatus(ResponseCodeEnum.SUCCESS));
}

private TransactionRecord getRecord(HapiSpec spec, String txn, ResponseCodeEnum status) {
final var hapiGetRecord = getTxnRecord(txn);
allRunFor(spec, hapiGetRecord);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561000f575f80fd5b5061002c61002161003160201b60201c565b61003860201b60201c565b6100f9565b5f33905090565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050815f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b610d8a806101065f395ff3fe608060405234801561000f575f80fd5b5060043610610060575f3560e01c8063715018a6146100645780637e6b8afd1461006e5780638da5cb5b1461008c578063c4d66de8146100aa578063e16151b4146100c6578063f2fde38b146100e2575b5f80fd5b61006c6100fe565b005b610076610111565b6040516100839190610755565b60405180910390f35b610094610136565b6040516100a19190610755565b60405180910390f35b6100c460048036038101906100bf919061079c565b61015d565b005b6100e060048036038101906100db91906107fd565b610237565b005b6100fc60048036038101906100f7919061079c565b61054e565b005b6101066105d0565b61010f5f61064e565b565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6101656105d0565b5f73ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146101f4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101eb90610895565b60405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b61023f6105d0565b5f73ffffffffffffffffffffffffffffffffffffffff1660015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16036102ce576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c5906108fd565b60405180910390fd5b5f61016790505f8173ffffffffffffffffffffffffffffffffffffffff166349146bde8560015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff166040518363ffffffff1660e01b815260040161033192919061091b565b6020604051808303815f875af115801561034d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103719190610975565b905060168114158015610385575060678114155b156103c5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103bc906109ea565b60405180910390fd5b5f8273ffffffffffffffffffffffffffffffffffffffff1663e0f4059a60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16865f67ffffffffffffffff81111561041f5761041e610a08565b5b60405190808252806020026020018201604052801561045257816020015b606081526020019060019003908161043d5790505b506040518463ffffffff1660e01b815260040161047193929190610b89565b6020604051808303815f875af115801561048d573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104b19190610975565b9050601681146104f6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104ed90610c0f565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff167fe8ea3d4dd0a2eaaf3f7532ad391255544f8a4bcf78f850bbff61d5bac9f775528560070b60405161053f9190610c45565b60405180910390a25050505050565b6105566105d0565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036105c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105bb90610cce565b60405180910390fd5b6105cd8161064e565b50565b6105d861070f565b73ffffffffffffffffffffffffffffffffffffffff166105f6610136565b73ffffffffffffffffffffffffffffffffffffffff161461064c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161064390610d36565b60405180910390fd5b565b5f805f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050815f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61073f82610716565b9050919050565b61074f81610735565b82525050565b5f6020820190506107685f830184610746565b92915050565b5f80fd5b61077b81610735565b8114610785575f80fd5b50565b5f8135905061079681610772565b92915050565b5f602082840312156107b1576107b061076e565b5b5f6107be84828501610788565b91505092915050565b5f8160070b9050919050565b6107dc816107c7565b81146107e6575f80fd5b50565b5f813590506107f7816107d3565b92915050565b5f80604083850312156108135761081261076e565b5b5f61082085828601610788565b9250506020610831858286016107e9565b9150509250929050565b5f82825260208201905092915050565b7f436f6e747261637420616c726561647920696e697469616c697a6564000000005f82015250565b5f61087f601c8361083b565b915061088a8261084b565b602082019050919050565b5f6020820190508181035f8301526108ac81610873565b9050919050565b7f48545320746f6b656e206e6f7420696e697469616c697a6564000000000000005f82015250565b5f6108e760198361083b565b91506108f2826108b3565b602082019050919050565b5f6020820190508181035f830152610914816108db565b9050919050565b5f60408201905061092e5f830185610746565b61093b6020830184610746565b9392505050565b5f819050919050565b61095481610942565b811461095e575f80fd5b50565b5f8151905061096f8161094b565b92915050565b5f6020828403121561098a5761098961076e565b5b5f61099784828501610961565b91505092915050565b7f4854533a20546f6b656e206173736f63696174696f6e206661696c65640000005f82015250565b5f6109d4601d8361083b565b91506109df826109a0565b602082019050919050565b5f6020820190508181035f830152610a01816109c8565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610a3e816107c7565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610aa4578082015181840152602081019050610a89565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610ac982610a6d565b610ad38185610a77565b9350610ae3818560208601610a87565b610aec81610aaf565b840191505092915050565b5f610b028383610abf565b905092915050565b5f602082019050919050565b5f610b2082610a44565b610b2a8185610a4e565b935083602082028501610b3c85610a5e565b805f5b85811015610b775784840389528151610b588582610af7565b9450610b6383610b0a565b925060208a01995050600181019050610b3f565b50829750879550505050505092915050565b5f606082019050610b9c5f830186610746565b610ba96020830185610a35565b8181036040830152610bbb8184610b16565b9050949350505050565b7f4854533a204d696e74206661696c6564000000000000000000000000000000005f82015250565b5f610bf960108361083b565b9150610c0482610bc5565b602082019050919050565b5f6020820190508181035f830152610c2681610bed565b9050919050565b5f819050919050565b610c3f81610c2d565b82525050565b5f602082019050610c585f830184610c36565b92915050565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f20615f8201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b5f610cb860268361083b565b9150610cc382610c5e565b604082019050919050565b5f6020820190508181035f830152610ce581610cac565b9050919050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65725f82015250565b5f610d2060208361083b565b9150610d2b82610cec565b602082019050919050565b5f6020820190508181035f830152610d4d81610d14565b905091905056fea2646970667358221220990e341bb8b823bd3d0ea8af0a8942d763f3c05240b5295052b5d7c26f748a2f64736f6c63430008140033
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "RewardMinted",
"type": "event"
},
{
"inputs": [],
"name": "htsTokenId",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_htsTokenId",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "int64",
"name": "amount",
"type": "int64"
}
],
"name": "mintReward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./Ownable.sol";

interface IHederaTokenService {
function associateToken(address account, address token) external returns (int);
function mintToken(address token, int64 amount, bytes[] calldata metadata) external returns (int);
}

library HederaResponseCodes {
// Minimal subset of response codes used by this contract.
int public constant SUCCESS = 22;
int public constant TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT = 103;
}

/**
* @title HushSense Token Manager
* @notice This contract MANAGES a native Hedera HTS token.
* It is NOT an ERC-20 token itself.
* It is given the Supply Key of the HTS token so it can mint rewards.
*/
contract HushSenseManager is Ownable {

/// @notice The address of the HTS token this contract manages
address public htsTokenId;

/// @notice Emitted when tokens are minted as rewards
event RewardMinted(address indexed to, uint256 amount);

constructor() {
// Contract is deployed with you as the owner
}

/**
* @notice Links this contract to the HTS token it will manage.
* This can only be called ONCE by the owner.
* @param _htsTokenId The address (Token ID) of the HTS token.
*/
function initialize(address _htsTokenId) external onlyOwner {
require(htsTokenId == address(0), "Contract already initialized");
htsTokenId = _htsTokenId;
}

/**
* @notice Allows the owner (your backend) to mint new tokens as rewards.
* This function calls the native HTS precompile.
* @param to The recipient's EVM address.
* @param amount The number of tokens to mint (using 0 decimals, as defined).
*/
function mintReward(address to, int64 amount) external onlyOwner {
require(htsTokenId != address(0), "HTS token not initialized");

IHederaTokenService hts = IHederaTokenService(address(0x0000000000000000000000000000000000000167));

// 1. Associate the user with the token if they aren't already
// This is a "best-effort" call and is safe to run even if already associated.
// This requires the RECIPIENT to have 'automatic token associations' enabled
int associationResponse = hts.associateToken(to, htsTokenId);
if (associationResponse != HederaResponseCodes.SUCCESS &&
associationResponse != HederaResponseCodes.TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT) {
revert("HTS: Token association failed");
}

// 2. Mint the tokens
int response = hts.mintToken(htsTokenId, amount, new bytes[](0));
if (response != HederaResponseCodes.SUCCESS) {
revert("HTS: Mint failed");
}

emit RewardMinted(to, uint256(int256(amount)));
}
}
Loading