Skip to content

Commit be82ead

Browse files
yanghang8612claude
andcommitted
feat(vm): implement TIP-7883 ModExp gas cost increase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5312d41 commit be82ead

File tree

2 files changed

+155
-0
lines changed

2 files changed

+155
-0
lines changed

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,10 @@ public long getEnergyForData(byte[] data) {
639639
byte[] expHighBytes = parseBytes(data, addSafely(ARGS_OFFSET, baseLen), min(expLen, 32,
640640
VMConfig.disableJavaLangMath()));
641641

642+
if (VMConfig.allowTvmOsaka()) {
643+
return getEnergyTIP7883(baseLen, modLen, expHighBytes, expLen);
644+
}
645+
642646
long multComplexity = getMultComplexity(max(baseLen, modLen, VMConfig.disableJavaLangMath()));
643647
long adjExpLen = getAdjustedExponentLength(expHighBytes, expLen);
644648

@@ -722,6 +726,63 @@ private long getAdjustedExponentLength(byte[] expHighBytes, long expLen) {
722726
}
723727
}
724728

729+
/**
730+
* TIP-7883: ModExp gas cost increase.
731+
* New pricing formula with higher minimum cost and no divisor.
732+
*/
733+
private long getEnergyTIP7883(int baseLen, int modLen,
734+
byte[] expHighBytes, int expLen) {
735+
long multComplexity = getMultComplexityTIP7883(baseLen, modLen);
736+
long iterCount = getIterationCountTIP7883(expHighBytes, expLen);
737+
738+
// use big numbers to stay safe in case of overflow
739+
BigInteger energy = BigInteger.valueOf(multComplexity)
740+
.multiply(BigInteger.valueOf(iterCount));
741+
742+
BigInteger minEnergy = BigInteger.valueOf(500);
743+
if (isLessThan(energy, minEnergy)) {
744+
return 500L;
745+
}
746+
747+
return isLessThan(energy, BigInteger.valueOf(Long.MAX_VALUE)) ? energy.longValueExact()
748+
: Long.MAX_VALUE;
749+
}
750+
751+
/**
752+
* TIP-7883: New multiplication complexity formula.
753+
* Minimal complexity of 16; doubled complexity for base/modulus > 32 bytes.
754+
*/
755+
private long getMultComplexityTIP7883(int baseLen, int modLen) {
756+
long maxLength = max(baseLen, modLen, VMConfig.disableJavaLangMath());
757+
long words = (maxLength + 7) / 8; // ceil(maxLength / 8)
758+
if (maxLength <= 32) {
759+
return 16;
760+
}
761+
return 2 * words * words;
762+
}
763+
764+
/**
765+
* TIP-7883: New iteration count formula.
766+
* Multiplier for exponents > 32 bytes increased from 8 to 16.
767+
*/
768+
private long getIterationCountTIP7883(byte[] expHighBytes, long expLen) {
769+
int leadingZeros = numberOfLeadingZeros(expHighBytes);
770+
int highestBit = 8 * expHighBytes.length - leadingZeros;
771+
772+
if (highestBit > 0) {
773+
highestBit--;
774+
}
775+
776+
long iterCount;
777+
if (expLen <= 32) {
778+
iterCount = highestBit;
779+
} else {
780+
iterCount = 16 * (expLen - 32) + highestBit;
781+
}
782+
783+
return max(iterCount, 1, VMConfig.disableJavaLangMath());
784+
}
785+
725786
private int parseLen(byte[] data, int idx) {
726787
byte[] bytes = parseBytes(data, 32 * idx, 32);
727788
return new DataWord(bytes).intValueSafe();

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,100 @@ public void testEIP7823() {
6060
}
6161
}
6262

63+
/**
64+
* Build ModExp input data for energy calculation testing.
65+
*/
66+
private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] expValue) {
67+
byte[] base = new byte[baseLen];
68+
byte[] exp = new byte[expLen];
69+
if (expValue.length > 0 && expLen > 0) {
70+
System.arraycopy(expValue, 0, exp, 0, Math.min(expValue.length, expLen));
71+
}
72+
byte[] mod = new byte[modLen];
73+
return ByteUtil.merge(toLenBytes(baseLen), toLenBytes(expLen), toLenBytes(modLen),
74+
base, exp, mod);
75+
}
76+
77+
private static long getEnergy(int baseLen, int expLen, int modLen, byte[] expValue) {
78+
return modExp.getEnergyForData(buildModExpData(baseLen, expLen, modLen, expValue));
79+
}
80+
81+
@Test
82+
public void testEIP7883ModExpPricing() {
83+
ConfigLoader.disable = true;
84+
VMConfig.initAllowTvmOsaka(1);
85+
86+
try {
87+
byte[] square = {0x02};
88+
byte[] qube = {0x03};
89+
byte[] pow0x10001 = {0x01, 0x00, 0x01};
90+
91+
// nagydani_1: baseLen=64, expLen=square/qube:1 pow:3, modLen=64
92+
Assert.assertEquals(500L, getEnergy(64, 1, 64, square));
93+
Assert.assertEquals(500L, getEnergy(64, 1, 64, qube));
94+
Assert.assertEquals(2048L, getEnergy(64, 3, 64, pow0x10001));
95+
96+
// nagydani_2: baseLen=128, modLen=128
97+
Assert.assertEquals(512L, getEnergy(128, 1, 128, square));
98+
Assert.assertEquals(512L, getEnergy(128, 1, 128, qube));
99+
Assert.assertEquals(8192L, getEnergy(128, 3, 128, pow0x10001));
100+
101+
// nagydani_3: baseLen=256, modLen=256
102+
Assert.assertEquals(2048L, getEnergy(256, 1, 256, square));
103+
Assert.assertEquals(2048L, getEnergy(256, 1, 256, qube));
104+
Assert.assertEquals(32768L, getEnergy(256, 3, 256, pow0x10001));
105+
106+
// nagydani_4: baseLen=512, modLen=512
107+
Assert.assertEquals(8192L, getEnergy(512, 1, 512, square));
108+
Assert.assertEquals(8192L, getEnergy(512, 1, 512, qube));
109+
Assert.assertEquals(131072L, getEnergy(512, 3, 512, pow0x10001));
110+
111+
// nagydani_5: baseLen=1024, modLen=1024
112+
Assert.assertEquals(32768L, getEnergy(1024, 1, 1024, square));
113+
Assert.assertEquals(32768L, getEnergy(1024, 1, 1024, qube));
114+
Assert.assertEquals(524288L, getEnergy(1024, 3, 1024, pow0x10001));
115+
116+
// Minimum energy: zero-length inputs
117+
Assert.assertEquals(500L, getEnergy(0, 0, 0, new byte[]{}));
118+
119+
// Small base/mod (<=32): complexity=16
120+
Assert.assertEquals(500L, getEnergy(1, 1, 1, square));
121+
Assert.assertEquals(500L, getEnergy(32, 1, 32, square));
122+
123+
// Boundary: base/mod at 33 (just over 32) uses doubled formula
124+
// words = ceil(33/8) = 5, complexity = 2 * 25 = 50, iterCount = 1
125+
Assert.assertEquals(500L, getEnergy(33, 1, 33, square));
126+
127+
// Exponent > 32 bytes: multiplier is 16
128+
// expLen=64, high bytes all zero → highestBit=0, iterCount = 16*(64-32)+0 = 512
129+
// baseLen=64, modLen=64 → complexity=128, energy=128*512=65536
130+
Assert.assertEquals(65536L, getEnergy(64, 64, 64, new byte[]{}));
131+
132+
// Exponent > 32 bytes with non-zero high bytes
133+
// expLen=64, first byte=0x01 → highestBit=248, iterCount = 16*32+248 = 760
134+
// baseLen=64, modLen=64 → complexity=128, energy=128*760=97280
135+
Assert.assertEquals(97280L, getEnergy(64, 64, 64, new byte[]{0x01}));
136+
} finally {
137+
VMConfig.initAllowTvmOsaka(0);
138+
ConfigLoader.disable = false;
139+
}
140+
}
141+
142+
@Test
143+
public void testEIP7883DisabledPreservesOldPricing() {
144+
ConfigLoader.disable = true;
145+
VMConfig.initAllowTvmOsaka(0);
146+
147+
try {
148+
// When Osaka is disabled, old pricing formula should be used
149+
// nagydani_1_square: old formula = 4096 * 1 / 20 = 204
150+
long energy = getEnergy(64, 1, 64, new byte[]{0x02});
151+
Assert.assertEquals(204L, energy);
152+
} finally {
153+
ConfigLoader.disable = false;
154+
}
155+
}
156+
63157
@Test
64158
public void testEIP7823DisabledShouldPass() {
65159
ConfigLoader.disable = true;

0 commit comments

Comments
 (0)