diff --git a/actuator/src/main/java/org/tron/core/vm/Op.java b/actuator/src/main/java/org/tron/core/vm/Op.java index ed2d8eb2f5..0a3fcc1dae 100644 --- a/actuator/src/main/java/org/tron/core/vm/Op.java +++ b/actuator/src/main/java/org/tron/core/vm/Op.java @@ -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 diff --git a/actuator/src/main/java/org/tron/core/vm/OperationActions.java b/actuator/src/main/java/org/tron/core/vm/OperationActions.java index 0d978743a5..88c3c55899 100644 --- a/actuator/src/main/java/org/tron/core/vm/OperationActions.java +++ b/actuator/src/main/java/org/tron/core/vm/OperationActions.java @@ -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; @@ -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)); + } else { + program.stackPush(DataWord.of((byte) clz)); + } + program.step(); + } + public static void sha3Action(Program program) { DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); diff --git a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java index f6140107ef..e967ef0e08 100644 --- a/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java +++ b/actuator/src/main/java/org/tron/core/vm/OperationRegistry.java @@ -13,6 +13,7 @@ public enum Version { TRON_V1_2, TRON_V1_3, TRON_V1_4, + TRON_V1_5, // add more // TRON_V2, // ETH @@ -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() { @@ -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()) { @@ -700,6 +708,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, diff --git a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java index 583b013194..39cc119d2d 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java @@ -904,6 +904,81 @@ public void testPush0() throws ContractValidateException { VMConfig.initAllowTvmShangHai(0); } + @Test + public void testCLZ() throws ContractValidateException { + VMConfig.initAllowTvmOsaka(1); + + 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()); + + // Verify energy cost = LOW_TIER(5) + PUSH32 cost(3) = 8 + Assert.assertEquals(8, program.getResult().getEnergyUsed()); + + VMConfig.initAllowTvmOsaka(0); + } + + // Build bytecode: PUSH32 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]);