Skip to content

Commit 512ce0c

Browse files
Merge pull request #6656 from yanghang8612/implement-tip-7939
feat(vm): implement TIP-7939 CLZ opcode
2 parents 4f88973 + 433f894 commit 512ce0c

4 files changed

Lines changed: 156 additions & 1 deletion

File tree

actuator/src/main/java/org/tron/core/vm/Op.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public class Op {
6464
public static final int SHR = 0x1c;
6565
// (0x1d) Arithmetic shift right
6666
public static final int SAR = 0x1d;
67+
// (0x1e) Count leading zeros
68+
public static final int CLZ = 0x1e;
6769

6870
/* Cryptographic Operations */
6971
// (0x20) Compute SHA3-256 hash

actuator/src/main/java/org/tron/core/vm/OperationActions.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static org.tron.common.crypto.Hash.sha3;
44
import static org.tron.common.utils.ByteUtil.EMPTY_BYTE_ARRAY;
5+
import static org.tron.common.utils.ByteUtil.numberOfLeadingZeros;
56

67
import java.math.BigInteger;
78
import java.util.ArrayList;
@@ -287,6 +288,17 @@ public static void sarAction(Program program) {
287288
program.step();
288289
}
289290

291+
public static void clzAction(Program program) {
292+
DataWord word = program.stackPop();
293+
int clz = numberOfLeadingZeros(word.getData());
294+
if (clz == 256) {
295+
program.stackPush(new DataWord(256));
296+
} else {
297+
program.stackPush(DataWord.of((byte) clz));
298+
}
299+
program.step();
300+
}
301+
290302
public static void sha3Action(Program program) {
291303
DataWord memOffsetData = program.stackPop();
292304
DataWord lengthData = program.stackPop();

actuator/src/main/java/org/tron/core/vm/OperationRegistry.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum Version {
1313
TRON_V1_2,
1414
TRON_V1_3,
1515
TRON_V1_4,
16+
TRON_V1_5,
1617
// add more
1718
// TRON_V2,
1819
// ETH
@@ -26,6 +27,7 @@ public enum Version {
2627
tableMap.put(Version.TRON_V1_2, newTronV12OperationSet());
2728
tableMap.put(Version.TRON_V1_3, newTronV13OperationSet());
2829
tableMap.put(Version.TRON_V1_4, newTronV14OperationSet());
30+
tableMap.put(Version.TRON_V1_5, newTronV15OperationSet());
2931
}
3032

3133
public static JumpTable newTronV10OperationSet() {
@@ -63,12 +65,18 @@ public static JumpTable newTronV14OperationSet() {
6365
return table;
6466
}
6567

68+
public static JumpTable newTronV15OperationSet() {
69+
JumpTable table = newTronV14OperationSet();
70+
appendOsakaOperations(table);
71+
return table;
72+
}
73+
6674
// Just for warming up class to avoid out_of_time
6775
public static void init() {}
6876

6977
public static JumpTable getTable() {
7078
// always get the table which has the newest version
71-
JumpTable table = tableMap.get(Version.TRON_V1_4);
79+
JumpTable table = tableMap.get(Version.TRON_V1_5);
7280

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

715+
public static void appendOsakaOperations(JumpTable table) {
716+
BooleanSupplier proposal = VMConfig::allowTvmOsaka;
717+
718+
table.set(new Operation(
719+
Op.CLZ, 1, 1,
720+
EnergyCost::getLowTierCost,
721+
OperationActions::clzAction,
722+
proposal));
723+
}
724+
707725
public static void adjustSelfdestruct(JumpTable table) {
708726
table.set(new Operation(
709727
Op.SUICIDE, 1, 0,

framework/src/test/java/org/tron/common/runtime/vm/OperationsTest.java

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public static void destroy() {
7878
VMConfig.initAllowTvmIstanbul(0);
7979
VMConfig.initAllowTvmLondon(0);
8080
VMConfig.initAllowTvmCompatibleEvm(0);
81+
VMConfig.initAllowTvmOsaka(0);
8182
}
8283

8384
@Test
@@ -905,6 +906,128 @@ public void testPush0() throws ContractValidateException {
905906
VMConfig.initAllowTvmShangHai(0);
906907
}
907908

909+
@Test
910+
public void testCLZ() throws ContractValidateException {
911+
VMConfig.initAllowTvmOsaka(1);
912+
913+
try {
914+
invoke = new ProgramInvokeMockImpl();
915+
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
916+
InternalTransaction interTrx =
917+
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
918+
919+
// CLZ(0) = 256
920+
byte[] op = buildCLZBytecode(new byte[32]);
921+
program = new Program(op, op, invoke, interTrx);
922+
testOperations(program);
923+
Assert.assertEquals(new DataWord(256), program.getStack().pop());
924+
925+
// CLZ(0x80...00) = 0 (highest bit set)
926+
byte[] val = new byte[32];
927+
val[0] = (byte) 0x80;
928+
op = buildCLZBytecode(val);
929+
program = new Program(op, op, invoke, interTrx);
930+
testOperations(program);
931+
Assert.assertEquals(new DataWord(0), program.getStack().pop());
932+
933+
// CLZ(0xFF...FF) = 0
934+
val = new byte[32];
935+
for (int i = 0; i < 32; i++) {
936+
val[i] = (byte) 0xFF;
937+
}
938+
op = buildCLZBytecode(val);
939+
program = new Program(op, op, invoke, interTrx);
940+
testOperations(program);
941+
Assert.assertEquals(new DataWord(0), program.getStack().pop());
942+
943+
// CLZ(0x40...00) = 1
944+
val = new byte[32];
945+
val[0] = (byte) 0x40;
946+
op = buildCLZBytecode(val);
947+
program = new Program(op, op, invoke, interTrx);
948+
testOperations(program);
949+
Assert.assertEquals(new DataWord(1), program.getStack().pop());
950+
951+
// CLZ(0x7F...FF) = 1
952+
val = new byte[32];
953+
for (int i = 0; i < 32; i++) {
954+
val[i] = (byte) 0xFF;
955+
}
956+
val[0] = (byte) 0x7F;
957+
op = buildCLZBytecode(val);
958+
program = new Program(op, op, invoke, interTrx);
959+
testOperations(program);
960+
Assert.assertEquals(new DataWord(1), program.getStack().pop());
961+
962+
// CLZ(1) = 255
963+
val = new byte[32];
964+
val[31] = 0x01;
965+
op = buildCLZBytecode(val);
966+
program = new Program(op, op, invoke, interTrx);
967+
testOperations(program);
968+
Assert.assertEquals(new DataWord(255), program.getStack().pop());
969+
970+
// Vectors with CLZ in [128, 254] — exercise the (byte) cast path in
971+
// DataWord.of(byte) where the unsigned int would otherwise become a
972+
// negative byte. Read-back goes through new BigInteger(1, data), so the
973+
// bit pattern must round-trip as unsigned.
974+
// CLZ = 128 (boundary): byte[16] high bit set
975+
val = new byte[32];
976+
val[16] = (byte) 0x80;
977+
op = buildCLZBytecode(val);
978+
program = new Program(op, op, invoke, interTrx);
979+
testOperations(program);
980+
Assert.assertEquals(new DataWord(128), program.getStack().pop());
981+
982+
// CLZ = 192 (mid-range): byte[24] high bit set
983+
val = new byte[32];
984+
val[24] = (byte) 0x80;
985+
op = buildCLZBytecode(val);
986+
program = new Program(op, op, invoke, interTrx);
987+
testOperations(program);
988+
Assert.assertEquals(new DataWord(192), program.getStack().pop());
989+
990+
// CLZ = 247 (near-upper): 30 zero bytes, then 0x01
991+
val = new byte[32];
992+
val[30] = 0x01;
993+
op = buildCLZBytecode(val);
994+
program = new Program(op, op, invoke, interTrx);
995+
testOperations(program);
996+
Assert.assertEquals(new DataWord(247), program.getStack().pop());
997+
998+
// Verify energy cost = LOW_TIER(5) + PUSH32 cost(3) = 8
999+
Assert.assertEquals(8, program.getResult().getEnergyUsed());
1000+
} finally {
1001+
VMConfig.initAllowTvmOsaka(0);
1002+
}
1003+
}
1004+
1005+
@Test
1006+
public void testCLZRejectedWhenOsakaDisabled() throws ContractValidateException {
1007+
VMConfig.initAllowTvmOsaka(0);
1008+
1009+
invoke = new ProgramInvokeMockImpl();
1010+
Protocol.Transaction trx = Protocol.Transaction.getDefaultInstance();
1011+
InternalTransaction interTrx =
1012+
new InternalTransaction(trx, InternalTransaction.TrxType.TRX_UNKNOWN_TYPE);
1013+
1014+
byte[] op = buildCLZBytecode(new byte[32]);
1015+
program = new Program(op, op, invoke, interTrx);
1016+
testOperations(program);
1017+
1018+
Assert.assertTrue(program.getResult().getException()
1019+
instanceof Program.IllegalOperationException);
1020+
}
1021+
1022+
// Build bytecode: PUSH32 <value> CLZ
1023+
private byte[] buildCLZBytecode(byte[] value) {
1024+
byte[] op = new byte[34];
1025+
op[0] = 0x7f; // PUSH32
1026+
System.arraycopy(value, 0, op, 1, 32);
1027+
op[33] = Op.CLZ;
1028+
return op;
1029+
}
1030+
9081031
@Test
9091032
public void testSuicideCost() throws ContractValidateException {
9101033
invoke = new ProgramInvokeMockImpl(StoreFactory.getInstance(), new byte[0], new byte[21]);

0 commit comments

Comments
 (0)