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 1ac96b9d59d..0dc8fb31ada 100644 --- a/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java +++ b/actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java @@ -705,6 +705,9 @@ public Pair execute(byte[] data) { // check if modulus is zero if (isZero(mod)) { + if (VMConfig.allowTvmOsaka()) { + return Pair.of(true, new byte[modLen]); + } return Pair.of(true, EMPTY_BYTE_ARRAY); } diff --git a/actuator/src/main/java/org/tron/core/vm/program/Program.java b/actuator/src/main/java/org/tron/core/vm/program/Program.java index 80d972041dc..3ed968e1afa 100644 --- a/actuator/src/main/java/org/tron/core/vm/program/Program.java +++ b/actuator/src/main/java/org/tron/core/vm/program/Program.java @@ -1616,6 +1616,10 @@ public ProgramTrace getTrace() { } public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) { + if (VMConfig.allowTvmOsaka()) { + returnDataBuffer = null; // reset return buffer right before the call + } + byte[] senderAddress; if (VMConfig.allowTvmCompatibleEvm() && getCallDeep() == MAX_DEPTH) { stackPushZero(); 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 c7000175b00..566fef614ca 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 @@ -66,12 +66,25 @@ public void testEIP7823() { * Build ModExp input data for energy calculation testing. */ private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] expValue) { + return buildModExpData(baseLen, expLen, modLen, new byte[]{}, expValue, new byte[]{}); + } + + private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] baseValue, + byte[] expValue, byte[] modValue) { byte[] base = new byte[baseLen]; + if (baseValue.length > 0 && baseLen > 0) { + System.arraycopy(baseValue, 0, base, 0, + StrictMathWrapper.min(baseValue.length, baseLen)); + } byte[] exp = new byte[expLen]; if (expValue.length > 0 && expLen > 0) { System.arraycopy(expValue, 0, exp, 0, StrictMathWrapper.min(expValue.length, expLen)); } byte[] mod = new byte[modLen]; + if (modValue.length > 0 && modLen > 0) { + System.arraycopy(modValue, 0, mod, 0, + StrictMathWrapper.min(modValue.length, modLen)); + } return ByteUtil.merge(toLenBytes(baseLen), toLenBytes(expLen), toLenBytes(modLen), base, exp, mod); } @@ -80,6 +93,72 @@ private static long getEnergy(int baseLen, int expLen, int modLen, byte[] expVal return modExp.getEnergyForData(buildModExpData(baseLen, expLen, modLen, expValue)); } + @Test + public void testModExpZeroModulusOutputLengthGatedByOsaka() { + ConfigLoader.disable = true; + + byte[] modLenZero = buildModExpData(1, 1, 0, new byte[]{0x02}, new byte[]{0x03}, + new byte[]{}); + byte[] modLenOne = buildModExpData(1, 1, 1, new byte[]{0x02}, new byte[]{0x03}, + new byte[]{0x00}); + byte[] modLen32 = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03}, + new byte[]{}); + + try { + VMConfig.initAllowTvmOsaka(0); + Pair result = modExp.execute(modLenZero); + Assert.assertTrue(result.getLeft()); + Assert.assertEquals(0, result.getRight().length); + + result = modExp.execute(modLenOne); + Assert.assertTrue(result.getLeft()); + Assert.assertEquals(0, result.getRight().length); + + result = modExp.execute(modLen32); + Assert.assertTrue(result.getLeft()); + Assert.assertEquals(0, result.getRight().length); + + VMConfig.initAllowTvmOsaka(1); + result = modExp.execute(modLenZero); + Assert.assertTrue(result.getLeft()); + Assert.assertEquals(0, result.getRight().length); + + result = modExp.execute(modLenOne); + Assert.assertTrue(result.getLeft()); + Assert.assertArrayEquals(new byte[1], result.getRight()); + + result = modExp.execute(modLen32); + Assert.assertTrue(result.getLeft()); + Assert.assertArrayEquals(new byte[32], result.getRight()); + } finally { + VMConfig.initAllowTvmOsaka(0); + ConfigLoader.disable = false; + } + } + + @Test + public void testModExpZeroModulusEnergyMatchesNonZeroModulus() { + ConfigLoader.disable = true; + + byte[] zeroModulus = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03}, + new byte[]{}); + byte[] nonZeroModulus = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03}, + new byte[]{0x05}); + + try { + VMConfig.initAllowTvmOsaka(0); + Assert.assertEquals(modExp.getEnergyForData(nonZeroModulus), + modExp.getEnergyForData(zeroModulus)); + + VMConfig.initAllowTvmOsaka(1); + Assert.assertEquals(modExp.getEnergyForData(nonZeroModulus), + modExp.getEnergyForData(zeroModulus)); + } finally { + VMConfig.initAllowTvmOsaka(0); + ConfigLoader.disable = false; + } + } + @Test public void testEIP7883ModExpPricing() { ConfigLoader.disable = true; diff --git a/framework/src/test/java/org/tron/common/runtime/vm/TvmIssueVerifierTest.java b/framework/src/test/java/org/tron/common/runtime/vm/TvmIssueVerifierTest.java new file mode 100644 index 00000000000..26fe4915140 --- /dev/null +++ b/framework/src/test/java/org/tron/common/runtime/vm/TvmIssueVerifierTest.java @@ -0,0 +1,180 @@ +package org.tron.common.runtime.vm; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.util.encoders.Hex; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.tron.common.runtime.TVMTestResult; +import org.tron.common.runtime.TvmTestUtils; +import org.tron.common.utils.WalletUtil; +import org.tron.common.utils.client.utils.AbiUtil; +import org.tron.core.exception.ContractExeException; +import org.tron.core.exception.ContractValidateException; +import org.tron.core.exception.ReceiptCheckErrException; +import org.tron.core.exception.VMIllegalException; +import org.tron.core.vm.config.ConfigLoader; +import org.tron.protos.Protocol.Transaction; + +@Slf4j +public class TvmIssueVerifierTest extends VMTestBase { + + private static final int WORD_SIZE = 32; + + private static final String ABI = loadResource("solidity/TvmIssueVerifier.abi"); + + private static final String BYTECODE = loadResource("solidity/TvmIssueVerifier.bin"); + + @Before + public void enableVmFeatures() { + ConfigLoader.disable = false; + manager.getDynamicPropertiesStore().saveAllowTvmTransferTrc10(1); + manager.getDynamicPropertiesStore().saveAllowTvmConstantinople(1); + manager.getDynamicPropertiesStore().saveAllowTvmIstanbul(1); + manager.getDynamicPropertiesStore().saveAllowTvmLondon(1); + manager.getDynamicPropertiesStore().saveAllowTvmCompatibleEvm(1); + manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1); + } + + @Test + public void verifyTvmOsakaFixesWithSolidity() + throws ContractExeException, ReceiptCheckErrException, + VMIllegalException, ContractValidateException { + byte[] verifierAddress = deployVerifier(); + + manager.getDynamicPropertiesStore().saveAllowTvmOsaka(0); + + TVMTestResult modExpResult = trigger(verifierAddress, "modexpZeroModulus()", + Collections.emptyList(), 100_000_000L); + byte[] modExpReturn = modExpResult.getRuntime().getResult().getHReturn(); + Assert.assertNull(modExpResult.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0)); + Assert.assertEquals("MODEXP zero modulus currently returns empty returndata", + BigInteger.ZERO, word(modExpReturn, 1)); + + TVMTestResult create2Result = trigger(verifierAddress, + "failedCreate2KeepsPriorReturnData(bytes,uint256)", Arrays.asList("00", 7L), + 100_000_000L); + byte[] create2Return = create2Result.getRuntime().getResult().getHReturn(); + Assert.assertNull(create2Result.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0)); + Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1)); + Assert.assertEquals("failed CREATE2 keeps the previous 32-byte return data buffer", + BigInteger.valueOf(32), word(create2Return, 2)); + + TVMTestResult preOsakaCreate2SuccessResult = trigger(verifierAddress, + "successfulCreate2KeepsPriorReturnData(bytes,uint256)", + Arrays.asList(initCodeReturningRuntime("00"), 8L), 100_000_000L); + byte[] preOsakaCreate2SuccessReturn = + preOsakaCreate2SuccessResult.getRuntime().getResult().getHReturn(); + Assert.assertNull(preOsakaCreate2SuccessResult.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 0)); + Assert.assertTrue(word(preOsakaCreate2SuccessReturn, 1).signum() != 0); + Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 2)); + Assert.assertEquals(BigInteger.ONE, word(preOsakaCreate2SuccessReturn, 3)); + + manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1); + + modExpResult = trigger(verifierAddress, "modexpZeroModulus()", + Collections.emptyList(), 100_000_000L); + modExpReturn = modExpResult.getRuntime().getResult().getHReturn(); + Assert.assertNull(modExpResult.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0)); + Assert.assertEquals("MODEXP zero modulus returns modLen bytes after Osaka", + BigInteger.ONE, word(modExpReturn, 1)); + + create2Result = trigger(verifierAddress, + "failedCreate2KeepsPriorReturnData(bytes,uint256)", Arrays.asList("00", 7L), + 100_000_000L); + create2Return = create2Result.getRuntime().getResult().getHReturn(); + Assert.assertNull(create2Result.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0)); + Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1)); + Assert.assertEquals("failed CREATE2 clears the previous return data buffer after Osaka", + BigInteger.ZERO, word(create2Return, 2)); + + TVMTestResult create2SuccessResult = trigger(verifierAddress, + "successfulCreate2KeepsPriorReturnData(bytes,uint256)", + Arrays.asList(initCodeReturningRuntime("00"), 9L), 100_000_000L); + byte[] create2SuccessReturn = create2SuccessResult.getRuntime().getResult().getHReturn(); + Assert.assertNull(create2SuccessResult.getRuntime().getRuntimeError()); + Assert.assertEquals(BigInteger.valueOf(32), word(create2SuccessReturn, 0)); + Assert.assertTrue(word(create2SuccessReturn, 1).signum() != 0); + Assert.assertEquals("successful CREATE2 clears the previous return data buffer after Osaka", + BigInteger.ZERO, word(create2SuccessReturn, 2)); + Assert.assertEquals(BigInteger.ONE, word(create2SuccessReturn, 3)); + } + + private byte[] deployVerifier() + throws ContractExeException, ReceiptCheckErrException, + VMIllegalException, ContractValidateException { + byte[] owner = Hex.decode(OWNER_ADDRESS); + Transaction trx = TvmTestUtils.generateDeploySmartContractAndGetTransaction( + "TvmIssueVerifier", owner, ABI, BYTECODE, 0, 1_000_000_000L, 0, null); + byte[] contractAddress = WalletUtil.generateContractAddress(trx); + runtime = TvmTestUtils.processTransactionAndReturnRuntime(trx, rootRepository, null); + Assert.assertNull(runtime.getRuntimeError()); + return contractAddress; + } + + private TVMTestResult trigger(byte[] contractAddress, String method, List args, + long feeLimit) + throws ContractExeException, ReceiptCheckErrException, + VMIllegalException, ContractValidateException { + String input = AbiUtil.parseMethod(method, args); + return TvmTestUtils.triggerContractAndReturnTvmTestResult(Hex.decode(OWNER_ADDRESS), + contractAddress, Hex.decode(input), 0, feeLimit, manager, null); + } + + private static BigInteger word(byte[] data, int index) { + int start = index * WORD_SIZE; + return new BigInteger(1, Arrays.copyOfRange(data, start, start + WORD_SIZE)); + } + + private static String initCodeReturningRuntime(String runtimeCode) { + byte[] runtime = Hex.decode(runtimeCode); + Assert.assertTrue(runtime.length <= 255); + + byte[] initCode = new byte[12 + runtime.length]; + initCode[0] = 0x60; + initCode[1] = (byte) runtime.length; + initCode[2] = 0x60; + initCode[3] = 0x0c; + initCode[4] = 0x60; + initCode[5] = 0x00; + initCode[6] = 0x39; + initCode[7] = 0x60; + initCode[8] = (byte) runtime.length; + initCode[9] = 0x60; + initCode[10] = 0x00; + initCode[11] = (byte) 0xf3; + System.arraycopy(runtime, 0, initCode, 12, runtime.length); + + return Hex.toHexString(initCode); + } + + private static String loadResource(String resource) { + try (InputStream in = TvmIssueVerifierTest.class.getClassLoader() + .getResourceAsStream(resource)) { + if (in == null) { + throw new IllegalStateException("Missing test resource: " + resource); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int read; + while ((read = in.read(buffer)) >= 0) { + out.write(buffer, 0, read); + } + return out.toString("UTF-8").trim(); + } catch (IOException e) { + throw new IllegalStateException("Cannot read test resource: " + resource, e); + } + } +} diff --git a/framework/src/test/resources/solidity/TvmIssueVerifier.abi b/framework/src/test/resources/solidity/TvmIssueVerifier.abi new file mode 100644 index 00000000000..f70dae923cb --- /dev/null +++ b/framework/src/test/resources/solidity/TvmIssueVerifier.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"failedCreate2KeepsPriorReturnData","outputs":[{"internalType":"uint256","name":"beforeSize","type":"uint256"},{"internalType":"address","name":"created","type":"address"},{"internalType":"uint256","name":"afterSize","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"modexpZeroModulus","outputs":[{"internalType":"uint256","name":"ok","type":"uint256"},{"internalType":"uint256","name":"sizeAfter","type":"uint256"},{"internalType":"bytes32","name":"copiedWord","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"code","type":"bytes"},{"internalType":"uint256","name":"salt","type":"uint256"}],"name":"successfulCreate2KeepsPriorReturnData","outputs":[{"internalType":"uint256","name":"beforeSize","type":"uint256"},{"internalType":"address","name":"created","type":"address"},{"internalType":"uint256","name":"afterSize","type":"uint256"},{"internalType":"uint256","name":"createdSize","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] diff --git a/framework/src/test/resources/solidity/TvmIssueVerifier.bin b/framework/src/test/resources/solidity/TvmIssueVerifier.bin new file mode 100644 index 00000000000..0db3fc71973 --- /dev/null +++ b/framework/src/test/resources/solidity/TvmIssueVerifier.bin @@ -0,0 +1 @@ +6080604052348015600f57600080fd5b506105198061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80634ecba0f014610046578063543b5255146100795780639fefb5fd146100ab575b600080fd5b610060600480360381019061005b919061036b565b6100cb565b6040516100709493929190610417565b60405180910390f35b610093600480360381019061008e919061036b565b610104565b6040516100a29392919061045c565b60405180910390f35b6100b3610136565b6040516100c2939291906104ac565b60405180910390f35b60008060008061123460005260206000602060008060045af1503d9350848651602088016000f592503d9150823b905092959194509250565b600080600061123460005260206000602060008060045af1503d9250838551602087016001f591503d90509250925092565b600080600080606367ffffffffffffffff8111156101575761015661020a565b5b6040519080825280601f01601f1916602001820160405280156101895781602001600182028036833780820191505090505b5090506020810160018152600160208201526001604082015260026060820153600360618201536000606282015360001960005260206000606383600060055af194503d935060005192505050909192565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610242826101f9565b810181811067ffffffffffffffff821117156102615761026061020a565b5b80604052505050565b60006102746101db565b90506102808282610239565b919050565b600067ffffffffffffffff8211156102a05761029f61020a565b5b6102a9826101f9565b9050602081019050919050565b82818337600083830152505050565b60006102d86102d384610285565b61026a565b9050828152602081018484840111156102f4576102f36101f4565b5b6102ff8482856102b6565b509392505050565b600082601f83011261031c5761031b6101ef565b5b813561032c8482602086016102c5565b91505092915050565b6000819050919050565b61034881610335565b811461035357600080fd5b50565b6000813590506103658161033f565b92915050565b60008060408385031215610382576103816101e5565b5b600083013567ffffffffffffffff8111156103a05761039f6101ea565b5b6103ac85828601610307565b92505060206103bd85828601610356565b9150509250929050565b6103d081610335565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610401826103d6565b9050919050565b610411816103f6565b82525050565b600060808201905061042c60008301876103c7565b6104396020830186610408565b61044660408301856103c7565b61045360608301846103c7565b95945050505050565b600060608201905061047160008301866103c7565b61047e6020830185610408565b61048b60408301846103c7565b949350505050565b6000819050919050565b6104a681610493565b82525050565b60006060820190506104c160008301866103c7565b6104ce60208301856103c7565b6104db604083018461049d565b94935050505056fea2646970667358221220c9b28608a5295f3b52702e75aa5d40b18593bd0a9ff2e03e2274edbd42642c6a64736f6c634300081e0033 diff --git a/framework/src/test/resources/solidity/TvmIssueVerifier.sol b/framework/src/test/resources/solidity/TvmIssueVerifier.sol new file mode 100644 index 00000000000..98ac9c28680 --- /dev/null +++ b/framework/src/test/resources/solidity/TvmIssueVerifier.sol @@ -0,0 +1,55 @@ +pragma solidity ^0.8.25; + +contract TvmIssueVerifier { + function modexpZeroModulus() + public + returns (uint256 ok, uint256 sizeAfter, bytes32 copiedWord) + { + bytes memory input = new bytes(99); + + assembly { + let p := add(input, 0x20) + mstore(p, 1) + mstore(add(p, 0x20), 1) + mstore(add(p, 0x40), 1) + mstore8(add(p, 0x60), 2) + mstore8(add(p, 0x61), 3) + mstore8(add(p, 0x62), 0) + mstore(0, not(0)) + + ok := call(gas(), 5, 0, p, 99, 0, 32) + sizeAfter := returndatasize() + copiedWord := mload(0) + } + } + + function failedCreate2KeepsPriorReturnData(bytes memory code, uint256 salt) + public + returns (uint256 beforeSize, address created, uint256 afterSize) + { + assembly { + mstore(0, 0x1234) + pop(call(gas(), 4, 0, 0, 32, 0, 32)) + beforeSize := returndatasize() + + created := create2(1, add(code, 0x20), mload(code), salt) + afterSize := returndatasize() + } + } + + function successfulCreate2KeepsPriorReturnData(bytes memory code, uint256 salt) + public + returns (uint256 beforeSize, address created, uint256 afterSize, uint256 createdSize) + { + assembly { + mstore(0, 0x1234) + pop(call(gas(), 4, 0, 0, 32, 0, 32)) + beforeSize := returndatasize() + + created := create2(0, add(code, 0x20), mload(code), salt) + afterSize := returndatasize() + createdSize := extcodesize(created) + } + } + +}