diff --git a/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java b/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java index 634f7f2d3d..9af8e97fdd 100644 --- a/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java +++ b/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java @@ -639,6 +639,10 @@ public long getEnergyForData(byte[] data) { byte[] expHighBytes = parseBytes(data, addSafely(ARGS_OFFSET, baseLen), min(expLen, 32, VMConfig.disableJavaLangMath())); + if (VMConfig.allowTvmOsaka()) { + return getEnergyTIP7883(baseLen, modLen, expHighBytes, expLen); + } + long multComplexity = getMultComplexity(max(baseLen, modLen, VMConfig.disableJavaLangMath())); long adjExpLen = getAdjustedExponentLength(expHighBytes, expLen); @@ -722,6 +726,63 @@ private long getAdjustedExponentLength(byte[] expHighBytes, long expLen) { } } + /** + * TIP-7883: ModExp gas cost increase. + * New pricing formula with higher minimum cost and no divisor. + */ + private long getEnergyTIP7883(int baseLen, int modLen, + byte[] expHighBytes, int expLen) { + long multComplexity = getMultComplexityTIP7883(baseLen, modLen); + long iterCount = getIterationCountTIP7883(expHighBytes, expLen); + + // use big numbers to stay safe in case of overflow + BigInteger energy = BigInteger.valueOf(multComplexity) + .multiply(BigInteger.valueOf(iterCount)); + + BigInteger minEnergy = BigInteger.valueOf(500); + if (isLessThan(energy, minEnergy)) { + return 500L; + } + + return isLessThan(energy, BigInteger.valueOf(Long.MAX_VALUE)) ? energy.longValueExact() + : Long.MAX_VALUE; + } + + /** + * TIP-7883: New multiplication complexity formula. + * Minimal complexity of 16; doubled complexity for base/modulus > 32 bytes. + */ + private long getMultComplexityTIP7883(int baseLen, int modLen) { + long maxLength = max(baseLen, modLen, VMConfig.disableJavaLangMath()); + long words = (maxLength + 7) / 8; // ceil(maxLength / 8) + if (maxLength <= 32) { + return 16; + } + return 2 * words * words; + } + + /** + * TIP-7883: New iteration count formula. + * Multiplier for exponents > 32 bytes increased from 8 to 16. + */ + private long getIterationCountTIP7883(byte[] expHighBytes, long expLen) { + int leadingZeros = numberOfLeadingZeros(expHighBytes); + int highestBit = 8 * expHighBytes.length - leadingZeros; + + if (highestBit > 0) { + highestBit--; + } + + long iterCount; + if (expLen <= 32) { + iterCount = highestBit; + } else { + iterCount = 16 * (expLen - 32) + highestBit; + } + + return max(iterCount, 1, VMConfig.disableJavaLangMath()); + } + private int parseLen(byte[] data, int idx) { byte[] bytes = parseBytes(data, 32 * idx, 32); return new DataWord(bytes).intValueSafe(); diff --git a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmOsakaTest.java b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmOsakaTest.java index 897ec43e24..7e50103a51 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmOsakaTest.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/AllowTvmOsakaTest.java @@ -60,6 +60,100 @@ public void testEIP7823() { } } + /** + * Build ModExp input data for energy calculation testing. + */ + private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] expValue) { + byte[] base = new byte[baseLen]; + byte[] exp = new byte[expLen]; + if (expValue.length > 0 && expLen > 0) { + System.arraycopy(expValue, 0, exp, 0, expValue.length); + } + byte[] mod = new byte[modLen]; + return ByteUtil.merge(toLenBytes(baseLen), toLenBytes(expLen), toLenBytes(modLen), + base, exp, mod); + } + + private static long getEnergy(int baseLen, int expLen, int modLen, byte[] expValue) { + return modExp.getEnergyForData(buildModExpData(baseLen, expLen, modLen, expValue)); + } + + @Test + public void testEIP7883ModExpPricing() { + ConfigLoader.disable = true; + VMConfig.initAllowTvmOsaka(1); + + try { + byte[] square = {0x02}; + byte[] qube = {0x03}; + byte[] pow0x10001 = {0x01, 0x00, 0x01}; + + // nagydani_1: baseLen=64, expLen=square/qube:1 pow:3, modLen=64 + Assert.assertEquals(500L, getEnergy(64, 1, 64, square)); + Assert.assertEquals(500L, getEnergy(64, 1, 64, qube)); + Assert.assertEquals(2048L, getEnergy(64, 3, 64, pow0x10001)); + + // nagydani_2: baseLen=128, modLen=128 + Assert.assertEquals(512L, getEnergy(128, 1, 128, square)); + Assert.assertEquals(512L, getEnergy(128, 1, 128, qube)); + Assert.assertEquals(8192L, getEnergy(128, 3, 128, pow0x10001)); + + // nagydani_3: baseLen=256, modLen=256 + Assert.assertEquals(2048L, getEnergy(256, 1, 256, square)); + Assert.assertEquals(2048L, getEnergy(256, 1, 256, qube)); + Assert.assertEquals(32768L, getEnergy(256, 3, 256, pow0x10001)); + + // nagydani_4: baseLen=512, modLen=512 + Assert.assertEquals(8192L, getEnergy(512, 1, 512, square)); + Assert.assertEquals(8192L, getEnergy(512, 1, 512, qube)); + Assert.assertEquals(131072L, getEnergy(512, 3, 512, pow0x10001)); + + // nagydani_5: baseLen=1024, modLen=1024 + Assert.assertEquals(32768L, getEnergy(1024, 1, 1024, square)); + Assert.assertEquals(32768L, getEnergy(1024, 1, 1024, qube)); + Assert.assertEquals(524288L, getEnergy(1024, 3, 1024, pow0x10001)); + + // Minimum energy: zero-length inputs + Assert.assertEquals(500L, getEnergy(0, 0, 0, new byte[]{})); + + // Small base/mod (<=32): complexity=16 + Assert.assertEquals(500L, getEnergy(1, 1, 1, square)); + Assert.assertEquals(500L, getEnergy(32, 1, 32, square)); + + // Boundary: base/mod at 33 (just over 32) uses doubled formula + // words = ceil(33/8) = 5, complexity = 2 * 25 = 50, iterCount = 1 + Assert.assertEquals(500L, getEnergy(33, 1, 33, square)); + + // Exponent > 32 bytes: multiplier is 16 + // expLen=64, high bytes all zero → highestBit=0, iterCount = 16*(64-32)+0 = 512 + // baseLen=64, modLen=64 → complexity=128, energy=128*512=65536 + Assert.assertEquals(65536L, getEnergy(64, 64, 64, new byte[]{})); + + // Exponent > 32 bytes with non-zero high bytes + // expLen=64, first byte=0x01 → highestBit=248, iterCount = 16*32+248 = 760 + // baseLen=64, modLen=64 → complexity=128, energy=128*760=97280 + Assert.assertEquals(97280L, getEnergy(64, 64, 64, new byte[]{0x01})); + } finally { + VMConfig.initAllowTvmOsaka(0); + ConfigLoader.disable = false; + } + } + + @Test + public void testEIP7883DisabledPreservesOldPricing() { + ConfigLoader.disable = true; + VMConfig.initAllowTvmOsaka(0); + + try { + // When Osaka is disabled, old pricing formula should be used + // nagydani_1_square: old formula = 4096 * 1 / 20 = 204 + long energy = getEnergy(64, 1, 64, new byte[]{0x02}); + Assert.assertEquals(204L, energy); + } finally { + ConfigLoader.disable = false; + } + } + @Test public void testEIP7823DisabledShouldPass() { ConfigLoader.disable = true;