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
2 changes: 2 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/Op.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public class Op {
public static final int SHR = 0x1c;
// (0x1d) Arithmetic shift right
public static final int SAR = 0x1d;
// (0x1e) Count leading zeros
public static final int CLZ = 0x1e;

/* Cryptographic Operations */
// (0x20) Compute SHA3-256 hash
Expand Down
12 changes: 12 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/OperationActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.tron.common.crypto.Hash.sha3;
import static org.tron.common.utils.ByteUtil.EMPTY_BYTE_ARRAY;
import static org.tron.common.utils.ByteUtil.numberOfLeadingZeros;

import java.math.BigInteger;
import java.util.ArrayList;
Expand Down Expand Up @@ -287,6 +288,17 @@ public static void sarAction(Program program) {
program.step();
}

public static void clzAction(Program program) {
DataWord word = program.stackPop();
int clz = numberOfLeadingZeros(word.getData());
if (clz == 256) {
program.stackPush(new DataWord(256));
Comment thread
yanghang8612 marked this conversation as resolved.
} else {
program.stackPush(DataWord.of((byte) clz));
Comment thread
yanghang8612 marked this conversation as resolved.
}
program.step();
}

public static void sha3Action(Program program) {
DataWord memOffsetData = program.stackPop();
DataWord lengthData = program.stackPop();
Expand Down
20 changes: 19 additions & 1 deletion actuator/src/main/java/org/tron/core/vm/OperationRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum Version {
TRON_V1_2,
TRON_V1_3,
TRON_V1_4,
TRON_V1_5,
// add more
// TRON_V2,
// ETH
Expand All @@ -26,6 +27,7 @@ public enum Version {
tableMap.put(Version.TRON_V1_2, newTronV12OperationSet());
tableMap.put(Version.TRON_V1_3, newTronV13OperationSet());
tableMap.put(Version.TRON_V1_4, newTronV14OperationSet());
tableMap.put(Version.TRON_V1_5, newTronV15OperationSet());
}

public static JumpTable newTronV10OperationSet() {
Expand Down Expand Up @@ -63,12 +65,18 @@ public static JumpTable newTronV14OperationSet() {
return table;
}

public static JumpTable newTronV15OperationSet() {
JumpTable table = newTronV14OperationSet();
appendOsakaOperations(table);
return table;
}

// Just for warming up class to avoid out_of_time
public static void init() {}

public static JumpTable getTable() {
// always get the table which has the newest version
JumpTable table = tableMap.get(Version.TRON_V1_4);
JumpTable table = tableMap.get(Version.TRON_V1_5);

// next make the corresponding changes, exclude activating opcode
if (VMConfig.allowHigherLimitForMaxCpuTimeOfOneTx()) {
Expand Down Expand Up @@ -704,6 +712,16 @@ public static void appendCancunOperations(JumpTable table) {
tvmBlobProposal));
}

public static void appendOsakaOperations(JumpTable table) {
BooleanSupplier proposal = VMConfig::allowTvmOsaka;

table.set(new Operation(
Op.CLZ, 1, 1,
EnergyCost::getLowTierCost,
OperationActions::clzAction,
proposal));
}

public static void adjustSelfdestruct(JumpTable table) {
table.set(new Operation(
Op.SUICIDE, 1, 0,
Expand Down
123 changes: 123 additions & 0 deletions framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public static void destroy() {
VMConfig.initAllowTvmIstanbul(0);
VMConfig.initAllowTvmLondon(0);
VMConfig.initAllowTvmCompatibleEvm(0);
VMConfig.initAllowTvmOsaka(0);
}

@Test
Expand Down Expand Up @@ -904,6 +905,128 @@ public void testPush0() throws ContractValidateException {
VMConfig.initAllowTvmShangHai(0);
}

@Test
public void testCLZ() throws ContractValidateException {
VMConfig.initAllowTvmOsaka(1);

try {
invoke = new ProgramInvokeMockImpl();
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
InternalTransaction interTrx =
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);

// CLZ(0) = 256
byte[] op = buildCLZBytecode(new byte[32]);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(256), program.getStack().pop());

// CLZ(0x80...00) = 0 (highest bit set)
byte[] val = new byte[32];
val[0] = (byte) 0x80;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(0), program.getStack().pop());

// CLZ(0xFF...FF) = 0
val = new byte[32];
for (int i = 0; i < 32; i++) {
val[i] = (byte) 0xFF;
}
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(0), program.getStack().pop());

// CLZ(0x40...00) = 1
val = new byte[32];
val[0] = (byte) 0x40;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(1), program.getStack().pop());

// CLZ(0x7F...FF) = 1
val = new byte[32];
for (int i = 0; i < 32; i++) {
val[i] = (byte) 0xFF;
}
val[0] = (byte) 0x7F;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(1), program.getStack().pop());

// CLZ(1) = 255
val = new byte[32];
val[31] = 0x01;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(255), program.getStack().pop());

// Vectors with CLZ in [128, 254] — exercise the (byte) cast path in
// DataWord.of(byte) where the unsigned int would otherwise become a
// negative byte. Read-back goes through new BigInteger(1, data), so the
// bit pattern must round-trip as unsigned.
// CLZ = 128 (boundary): byte[16] high bit set
val = new byte[32];
val[16] = (byte) 0x80;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(128), program.getStack().pop());

// CLZ = 192 (mid-range): byte[24] high bit set
val = new byte[32];
val[24] = (byte) 0x80;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(192), program.getStack().pop());

// CLZ = 247 (near-upper): 30 zero bytes, then 0x01
val = new byte[32];
val[30] = 0x01;
op = buildCLZBytecode(val);
program = new Program(op, op, invoke, interTrx);
testOperations(program);
Assert.assertEquals(new DataWord(247), program.getStack().pop());

// Verify energy cost = LOW_TIER(5) + PUSH32 cost(3) = 8
Assert.assertEquals(8, program.getResult().getEnergyUsed());
} finally {
VMConfig.initAllowTvmOsaka(0);
}
}

@Test
public void testCLZRejectedWhenOsakaDisabled() throws ContractValidateException {
VMConfig.initAllowTvmOsaka(0);

invoke = new ProgramInvokeMockImpl();
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
InternalTransaction interTrx =
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);

byte[] op = buildCLZBytecode(new byte[32]);
program = new Program(op, op, invoke, interTrx);
testOperations(program);

Assert.assertTrue(program.getResult().getException()
instanceof Program.IllegalOperationException);
}

// Build bytecode: PUSH32 <value> CLZ
private byte[] buildCLZBytecode(byte[] value) {
byte[] op = new byte[34];
op[0] = 0x7f; // PUSH32
System.arraycopy(value, 0, op, 1, 32);
op[33] = Op.CLZ;
return op;
}

@Test
public void testSuicideCost() throws ContractValidateException {
invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], new byte[21]);
Expand Down
Loading