Skip to content
Merged
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
10 changes: 10 additions & 0 deletions actuator/src/main/java/org/tron/core/actuator/VMActuator.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,15 @@ public void execute(Object object) throws ContractExeException {
result = program.getResult();
result.setException(e);
result.rejectInternalTransactions();
clearExceptionResult(result);
result.setRuntimeError(result.getException().getMessage());
logger.info("JVMStackOverFlowException: {}", result.getException().getMessage());
} catch (OutOfTimeException e) {
program.spendAllEnergy();
result = program.getResult();
result.setException(e);
result.rejectInternalTransactions();
clearExceptionResult(result);
result.setRuntimeError(result.getException().getMessage());
logger.info("timeout: {}", result.getException().getMessage());
} catch (Throwable e) {
Expand All @@ -282,6 +284,7 @@ public void execute(Object object) throws ContractExeException {
}
result = program.getResult();
result.rejectInternalTransactions();
clearExceptionResult(result);
if (Objects.isNull(result.getException())) {
logger.error(e.getMessage(), e);
result.setException(new RuntimeException("Unknown Throwable"));
Expand Down Expand Up @@ -310,6 +313,13 @@ public void execute(Object object) throws ContractExeException {

}

private void clearExceptionResult(ProgramResult result) {
if (VMConfig.allowTvmOsaka()) {
result.getDeleteAccounts().clear();
result.getLogInfoList().clear();
}
}

private void create()
throws ContractValidateException {
if (!rootRepository.getDynamicPropertiesStore().supportVM()) {
Expand Down
3 changes: 2 additions & 1 deletion actuator/src/main/java/org/tron/core/vm/program/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -1621,7 +1621,8 @@ public void createContract2(DataWord value, DataWord memStart, DataWord memSize,
}

byte[] senderAddress;
if (VMConfig.allowTvmCompatibleEvm() && getCallDeep() == MAX_DEPTH) {
if ((VMConfig.allowTvmCompatibleEvm() || VMConfig.allowTvmOsaka())
&& getCallDeep() == MAX_DEPTH) {
stackPushZero();
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.tron.common.runtime;

import static org.mockito.ArgumentMatchers.any;

import java.lang.reflect.Field;
import java.util.Collections;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.tron.common.runtime.vm.DataWord;
import org.tron.common.runtime.vm.LogInfo;
import org.tron.core.actuator.VMActuator;
import org.tron.core.db.TransactionContext;
import org.tron.core.vm.OperationRegistry;
import org.tron.core.vm.VM;
import org.tron.core.vm.config.VMConfig;
import org.tron.core.vm.program.Program;

public class VMActuatorMockTest {

@BeforeClass
public static void init() {
// warm up the registry so VM.play(..., OperationRegistry.getTable()) arg eval is safe
OperationRegistry.init();
}

private void runCatchPathTest(Throwable thrownByVm, boolean osakaOn, int expectedSize)
throws Exception {
boolean prevOsaka = VMConfig.allowTvmOsaka();
VMConfig.initAllowTvmOsaka(osakaOn ? 1 : 0);
try (MockedStatic<VM> vmMock = Mockito.mockStatic(VM.class)) {
Program program = Mockito.mock(Program.class);
ProgramResult result = new ProgramResult();
result.addLogInfo(new LogInfo(new byte[20], Collections.emptyList(), new byte[0]));
result.addDeleteAccount(new DataWord(1));
Mockito.when(program.getResult()).thenReturn(result);

vmMock.when(() -> VM.play(any(), any())).thenThrow(thrownByVm);

VMActuator actuator = new VMActuator(false);
Field f = VMActuator.class.getDeclaredField("program");
f.setAccessible(true);
f.set(actuator, program);

TransactionContext context = Mockito.mock(TransactionContext.class);
Mockito.when(context.getProgramResult()).thenReturn(new ProgramResult());

actuator.execute(context);

Assert.assertEquals(expectedSize, result.getLogInfoList().size());
Assert.assertEquals(expectedSize, result.getDeleteAccounts().size());
} finally {
VMConfig.initAllowTvmOsaka(prevOsaka ? 1 : 0);
}
}

@Test
public void osakaClearsLogOnOutOfTime() throws Exception {
runCatchPathTest(new Program.OutOfTimeException("timeout"), true, 0);
}

@Test
public void osakaClearsLogOnJvmStackOverflow() throws Exception {
runCatchPathTest(new Program.JVMStackOverFlowException(), true, 0);
}

@Test
public void osakaClearsLogOnThrowable() throws Exception {
runCatchPathTest(new RuntimeException("boom"), true, 0);
}

@Test
public void preOsakaKeepsLogOnOutOfTime() throws Exception {
runCatchPathTest(new Program.OutOfTimeException("timeout"), false, 1);
}

@Test
public void preOsakaKeepsLogOnJvmStackOverflow() throws Exception {
runCatchPathTest(new Program.JVMStackOverFlowException(), false, 1);
}

@Test
public void preOsakaKeepsLogOnThrowable() throws Exception {
runCatchPathTest(new RuntimeException("boom"), false, 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,72 @@ Op.CALL, new DataWord(10000),
VMConfig.initAllowTvmSelfdestructRestriction(0);
}

@Test
public void testCreate2MaxDepthWithOsakaOnly() throws ContractValidateException {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to add a test case where both allowTvmCompatibleEvm and allowTvmOsaka are false, just to explicitly confirm that CREATE2 behaves as expected when neither flag is enabled. Helps make the intent clear for future readers!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, thanks! Added testCreate2MaxDepthWithNeitherFlag in the latest push: with both allowTvmCompatibleEvm and allowTvmOsaka disabled, the MAX_DEPTH short-circuit is not taken, so at max depth CREATE2 still proceeds as before — it records an internal transaction and pushes the new contract address instead of 0. This documents the baseline behavior alongside the Osaka-only case for future readers. PTAL.

boolean allowTvmCompatibleEvm = VMConfig.allowTvmCompatibleEvm();
boolean allowTvmOsaka = VMConfig.allowTvmOsaka();
VMConfig.initAllowTvmCompatibleEvm(0);
VMConfig.initAllowTvmOsaka(1);
try {
invoke = new ProgramInvokeMockImpl() {
@Override
public int getCallDeep() {
return 64;
}
};
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
InternalTransaction interTrx =
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
program = new Program(new byte[0], new byte[0], invoke, interTrx);
program.setRootTransactionId(new byte[32]);

program.createContract2(DataWord.ZERO(), DataWord.ZERO(), DataWord.ZERO(), DataWord.ZERO());

Assert.assertEquals(DataWord.ZERO(), program.getStack().pop());
Assert.assertTrue(program.getResult().getInternalTransactions().isEmpty());
} finally {
VMConfig.initAllowTvmCompatibleEvm(allowTvmCompatibleEvm ? 1 : 0);
VMConfig.initAllowTvmOsaka(allowTvmOsaka ? 1 : 0);
}
}

@Test
public void testCreate2MaxDepthWithNeitherFlag() throws ContractValidateException {
boolean allowTvmCompatibleEvm = VMConfig.allowTvmCompatibleEvm();
boolean allowTvmOsaka = VMConfig.allowTvmOsaka();
VMConfig.initAllowTvmCompatibleEvm(0);
VMConfig.initAllowTvmOsaka(0);
try {
byte[] contractAddr = Hex.decode("41471fd3ad3e9eeadeec4608b92d16ce6b500704cc");
invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], contractAddr) {
@Override
public int getCallDeep() {
return 64;
}

@Override
public boolean byTestingSuite() {
return true;
}
};
program = new Program(null, null, invoke,
new InternalTransaction(Protocol.Transaction.getDefaultInstance(),
InternalTransaction.TrxType.TRX_UNKNOWN_TYPE));
program.setRootTransactionId(new byte[32]);

program.createContract2(DataWord.ZERO(), DataWord.ZERO(), DataWord.ZERO(), DataWord.ZERO());

// With neither flag enabled the MAX_DEPTH short-circuit must not fire: CREATE2
// proceeds, records an internal transaction and pushes the new contract
// address (not 0), unlike the Osaka/CompatibleEvm guarded path above.
Assert.assertFalse(program.getResult().getInternalTransactions().isEmpty());
Assert.assertFalse(program.getStack().pop().isZero());
} finally {
VMConfig.initAllowTvmCompatibleEvm(allowTvmCompatibleEvm ? 1 : 0);
VMConfig.initAllowTvmOsaka(allowTvmOsaka ? 1 : 0);
}
}

// TIP-854 outer-frame containment: a CALL to validateMultiSign or
// batchValidateSign with malformed calldata must (a) push 0 onto the outer
// stack, (b) leave the outer frame free of any propagated exception, and
Expand Down
Loading