Skip to content
Open
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
61 changes: 61 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Comment on lines +739 to +748
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good defensive coding — using BigInteger for the intermediate multiplication avoids any potential overflow with large baseLen/modLen values (up to 1024 bytes), and the Long.MAX_VALUE cap ensures the result is always safe to return as a long. Well handled.

}

/**
* 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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment // ceil(maxLength / 8) is a nice touch — it makes the intent of (maxLength + 7) / 8 immediately clear without needing to mentally decode the arithmetic. The structure also maps 1:1 to the spec's pseudocode, which makes auditing straightforward.

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading