Skip to content

Commit e2b0660

Browse files
committed
Fix Osaka MODEXP and CREATE2 semantics
1 parent 381d369 commit e2b0660

7 files changed

Lines changed: 323 additions & 0 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,9 @@ public Pair<Boolean, byte[]> execute(byte[] data) {
705705

706706
// check if modulus is zero
707707
if (isZero(mod)) {
708+
if (VMConfig.allowTvmOsaka()) {
709+
return Pair.of(true, new byte[modLen]);
710+
}
708711
return Pair.of(true, EMPTY_BYTE_ARRAY);
709712
}
710713

actuator/src/main/java/org/tron/core/vm/program/Program.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,10 @@ public ProgramTrace getTrace() {
16161616
}
16171617

16181618
public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) {
1619+
if (VMConfig.allowTvmOsaka()) {
1620+
returnDataBuffer = null; // reset return buffer right before the call
1621+
}
1622+
16191623
byte[] senderAddress;
16201624
if (VMConfig.allowTvmCompatibleEvm() && getCallDeep() == MAX_DEPTH) {
16211625
stackPushZero();

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

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,25 @@ public void testEIP7823() {
6666
* Build ModExp input data for energy calculation testing.
6767
*/
6868
private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] expValue) {
69+
return buildModExpData(baseLen, expLen, modLen, new byte[]{}, expValue, new byte[]{});
70+
}
71+
72+
private static byte[] buildModExpData(int baseLen, int expLen, int modLen, byte[] baseValue,
73+
byte[] expValue, byte[] modValue) {
6974
byte[] base = new byte[baseLen];
75+
if (baseValue.length > 0 && baseLen > 0) {
76+
System.arraycopy(baseValue, 0, base, 0,
77+
StrictMathWrapper.min(baseValue.length, baseLen));
78+
}
7079
byte[] exp = new byte[expLen];
7180
if (expValue.length > 0 && expLen > 0) {
7281
System.arraycopy(expValue, 0, exp, 0, StrictMathWrapper.min(expValue.length, expLen));
7382
}
7483
byte[] mod = new byte[modLen];
84+
if (modValue.length > 0 && modLen > 0) {
85+
System.arraycopy(modValue, 0, mod, 0,
86+
StrictMathWrapper.min(modValue.length, modLen));
87+
}
7588
return ByteUtil.merge(toLenBytes(baseLen), toLenBytes(expLen), toLenBytes(modLen),
7689
base, exp, mod);
7790
}
@@ -80,6 +93,72 @@ private static long getEnergy(int baseLen, int expLen, int modLen, byte[] expVal
8093
return modExp.getEnergyForData(buildModExpData(baseLen, expLen, modLen, expValue));
8194
}
8295

96+
@Test
97+
public void testModExpZeroModulusOutputLengthGatedByOsaka() {
98+
ConfigLoader.disable = true;
99+
100+
byte[] modLenZero = buildModExpData(1, 1, 0, new byte[]{0x02}, new byte[]{0x03},
101+
new byte[]{});
102+
byte[] modLenOne = buildModExpData(1, 1, 1, new byte[]{0x02}, new byte[]{0x03},
103+
new byte[]{0x00});
104+
byte[] modLen32 = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03},
105+
new byte[]{});
106+
107+
try {
108+
VMConfig.initAllowTvmOsaka(0);
109+
Pair<Boolean, byte[]> result = modExp.execute(modLenZero);
110+
Assert.assertTrue(result.getLeft());
111+
Assert.assertEquals(0, result.getRight().length);
112+
113+
result = modExp.execute(modLenOne);
114+
Assert.assertTrue(result.getLeft());
115+
Assert.assertEquals(0, result.getRight().length);
116+
117+
result = modExp.execute(modLen32);
118+
Assert.assertTrue(result.getLeft());
119+
Assert.assertEquals(0, result.getRight().length);
120+
121+
VMConfig.initAllowTvmOsaka(1);
122+
result = modExp.execute(modLenZero);
123+
Assert.assertTrue(result.getLeft());
124+
Assert.assertEquals(0, result.getRight().length);
125+
126+
result = modExp.execute(modLenOne);
127+
Assert.assertTrue(result.getLeft());
128+
Assert.assertArrayEquals(new byte[1], result.getRight());
129+
130+
result = modExp.execute(modLen32);
131+
Assert.assertTrue(result.getLeft());
132+
Assert.assertArrayEquals(new byte[32], result.getRight());
133+
} finally {
134+
VMConfig.initAllowTvmOsaka(0);
135+
ConfigLoader.disable = false;
136+
}
137+
}
138+
139+
@Test
140+
public void testModExpZeroModulusEnergyMatchesNonZeroModulus() {
141+
ConfigLoader.disable = true;
142+
143+
byte[] zeroModulus = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03},
144+
new byte[]{});
145+
byte[] nonZeroModulus = buildModExpData(1, 1, 32, new byte[]{0x02}, new byte[]{0x03},
146+
new byte[]{0x05});
147+
148+
try {
149+
VMConfig.initAllowTvmOsaka(0);
150+
Assert.assertEquals(modExp.getEnergyForData(nonZeroModulus),
151+
modExp.getEnergyForData(zeroModulus));
152+
153+
VMConfig.initAllowTvmOsaka(1);
154+
Assert.assertEquals(modExp.getEnergyForData(nonZeroModulus),
155+
modExp.getEnergyForData(zeroModulus));
156+
} finally {
157+
VMConfig.initAllowTvmOsaka(0);
158+
ConfigLoader.disable = false;
159+
}
160+
}
161+
83162
@Test
84163
public void testEIP7883ModExpPricing() {
85164
ConfigLoader.disable = true;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package org.tron.common.runtime.vm;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.math.BigInteger;
7+
import java.util.Arrays;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.bouncycastle.util.encoders.Hex;
12+
import org.junit.Assert;
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
import org.tron.common.runtime.TVMTestResult;
16+
import org.tron.common.runtime.TvmTestUtils;
17+
import org.tron.common.utils.WalletUtil;
18+
import org.tron.common.utils.client.utils.AbiUtil;
19+
import org.tron.core.exception.ContractExeException;
20+
import org.tron.core.exception.ContractValidateException;
21+
import org.tron.core.exception.ReceiptCheckErrException;
22+
import org.tron.core.exception.VMIllegalException;
23+
import org.tron.core.vm.config.ConfigLoader;
24+
import org.tron.protos.Protocol.Transaction;
25+
26+
@Slf4j
27+
public class TvmIssueVerifierTest extends VMTestBase {
28+
29+
private static final int WORD_SIZE = 32;
30+
31+
private static final String ABI = loadResource("solidity/TvmIssueVerifier.abi");
32+
33+
private static final String BYTECODE = loadResource("solidity/TvmIssueVerifier.bin");
34+
35+
@Before
36+
public void enableVmFeatures() {
37+
ConfigLoader.disable = false;
38+
manager.getDynamicPropertiesStore().saveAllowTvmTransferTrc10(1);
39+
manager.getDynamicPropertiesStore().saveAllowTvmConstantinople(1);
40+
manager.getDynamicPropertiesStore().saveAllowTvmIstanbul(1);
41+
manager.getDynamicPropertiesStore().saveAllowTvmLondon(1);
42+
manager.getDynamicPropertiesStore().saveAllowTvmCompatibleEvm(1);
43+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1);
44+
}
45+
46+
@Test
47+
public void verifyTvmOsakaFixesWithSolidity()
48+
throws ContractExeException, ReceiptCheckErrException,
49+
VMIllegalException, ContractValidateException {
50+
byte[] verifierAddress = deployVerifier();
51+
52+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(0);
53+
54+
TVMTestResult modExpResult = trigger(verifierAddress, "modexpZeroModulus()",
55+
Collections.emptyList(), 100_000_000L);
56+
byte[] modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
57+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
58+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
59+
Assert.assertEquals("MODEXP zero modulus currently returns empty returndata",
60+
BigInteger.ZERO, word(modExpReturn, 1));
61+
62+
TVMTestResult create2Result = trigger(verifierAddress,
63+
"failedCreate2KeepsPriorReturnData(bytes,uint256)", Arrays.asList("00", 7L),
64+
100_000_000L);
65+
byte[] create2Return = create2Result.getRuntime().getResult().getHReturn();
66+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
67+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
68+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
69+
Assert.assertEquals("failed CREATE2 keeps the previous 32-byte return data buffer",
70+
BigInteger.valueOf(32), word(create2Return, 2));
71+
72+
TVMTestResult preOsakaCreate2SuccessResult = trigger(verifierAddress,
73+
"successfulCreate2KeepsPriorReturnData(bytes,uint256)",
74+
Arrays.asList(initCodeReturningRuntime("00"), 8L), 100_000_000L);
75+
byte[] preOsakaCreate2SuccessReturn =
76+
preOsakaCreate2SuccessResult.getRuntime().getResult().getHReturn();
77+
Assert.assertNull(preOsakaCreate2SuccessResult.getRuntime().getRuntimeError());
78+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 0));
79+
Assert.assertTrue(word(preOsakaCreate2SuccessReturn, 1).signum() != 0);
80+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 2));
81+
Assert.assertEquals(BigInteger.ONE, word(preOsakaCreate2SuccessReturn, 3));
82+
83+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1);
84+
85+
modExpResult = trigger(verifierAddress, "modexpZeroModulus()",
86+
Collections.emptyList(), 100_000_000L);
87+
modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
88+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
89+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
90+
Assert.assertEquals("MODEXP zero modulus returns modLen bytes after Osaka",
91+
BigInteger.ONE, word(modExpReturn, 1));
92+
93+
create2Result = trigger(verifierAddress,
94+
"failedCreate2KeepsPriorReturnData(bytes,uint256)", Arrays.asList("00", 7L),
95+
100_000_000L);
96+
create2Return = create2Result.getRuntime().getResult().getHReturn();
97+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
98+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
99+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
100+
Assert.assertEquals("failed CREATE2 clears the previous return data buffer after Osaka",
101+
BigInteger.ZERO, word(create2Return, 2));
102+
103+
TVMTestResult create2SuccessResult = trigger(verifierAddress,
104+
"successfulCreate2KeepsPriorReturnData(bytes,uint256)",
105+
Arrays.asList(initCodeReturningRuntime("00"), 9L), 100_000_000L);
106+
byte[] create2SuccessReturn = create2SuccessResult.getRuntime().getResult().getHReturn();
107+
Assert.assertNull(create2SuccessResult.getRuntime().getRuntimeError());
108+
Assert.assertEquals(BigInteger.valueOf(32), word(create2SuccessReturn, 0));
109+
Assert.assertTrue(word(create2SuccessReturn, 1).signum() != 0);
110+
Assert.assertEquals("successful CREATE2 clears the previous return data buffer after Osaka",
111+
BigInteger.ZERO, word(create2SuccessReturn, 2));
112+
Assert.assertEquals(BigInteger.ONE, word(create2SuccessReturn, 3));
113+
}
114+
115+
private byte[] deployVerifier()
116+
throws ContractExeException, ReceiptCheckErrException,
117+
VMIllegalException, ContractValidateException {
118+
byte[] owner = Hex.decode(OWNER_ADDRESS);
119+
Transaction trx = TvmTestUtils.generateDeploySmartContractAndGetTransaction(
120+
"TvmIssueVerifier", owner, ABI, BYTECODE, 0, 1_000_000_000L, 0, null);
121+
byte[] contractAddress = WalletUtil.generateContractAddress(trx);
122+
runtime = TvmTestUtils.processTransactionAndReturnRuntime(trx, rootRepository, null);
123+
Assert.assertNull(runtime.getRuntimeError());
124+
return contractAddress;
125+
}
126+
127+
private TVMTestResult trigger(byte[] contractAddress, String method, List<Object> args,
128+
long feeLimit)
129+
throws ContractExeException, ReceiptCheckErrException,
130+
VMIllegalException, ContractValidateException {
131+
String input = AbiUtil.parseMethod(method, args);
132+
return TvmTestUtils.triggerContractAndReturnTvmTestResult(Hex.decode(OWNER_ADDRESS),
133+
contractAddress, Hex.decode(input), 0, feeLimit, manager, null);
134+
}
135+
136+
private static BigInteger word(byte[] data, int index) {
137+
int start = index * WORD_SIZE;
138+
return new BigInteger(1, Arrays.copyOfRange(data, start, start + WORD_SIZE));
139+
}
140+
141+
private static String initCodeReturningRuntime(String runtimeCode) {
142+
byte[] runtime = Hex.decode(runtimeCode);
143+
Assert.assertTrue(runtime.length <= 255);
144+
145+
byte[] initCode = new byte[12 + runtime.length];
146+
initCode[0] = 0x60;
147+
initCode[1] = (byte) runtime.length;
148+
initCode[2] = 0x60;
149+
initCode[3] = 0x0c;
150+
initCode[4] = 0x60;
151+
initCode[5] = 0x00;
152+
initCode[6] = 0x39;
153+
initCode[7] = 0x60;
154+
initCode[8] = (byte) runtime.length;
155+
initCode[9] = 0x60;
156+
initCode[10] = 0x00;
157+
initCode[11] = (byte) 0xf3;
158+
System.arraycopy(runtime, 0, initCode, 12, runtime.length);
159+
160+
return Hex.toHexString(initCode);
161+
}
162+
163+
private static String loadResource(String resource) {
164+
try (InputStream in = TvmIssueVerifierTest.class.getClassLoader()
165+
.getResourceAsStream(resource)) {
166+
if (in == null) {
167+
throw new IllegalStateException("Missing test resource: " + resource);
168+
}
169+
ByteArrayOutputStream out = new ByteArrayOutputStream();
170+
byte[] buffer = new byte[4096];
171+
int read;
172+
while ((read = in.read(buffer)) >= 0) {
173+
out.write(buffer, 0, read);
174+
}
175+
return out.toString("UTF-8").trim();
176+
} catch (IOException e) {
177+
throw new IllegalStateException("Cannot read test resource: " + resource, e);
178+
}
179+
}
180+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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"}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6080604052348015600f57600080fd5b506105198061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80634ecba0f014610046578063543b5255146100795780639fefb5fd146100ab575b600080fd5b610060600480360381019061005b919061036b565b6100cb565b6040516100709493929190610417565b60405180910390f35b610093600480360381019061008e919061036b565b610104565b6040516100a29392919061045c565b60405180910390f35b6100b3610136565b6040516100c2939291906104ac565b60405180910390f35b60008060008061123460005260206000602060008060045af1503d9350848651602088016000f592503d9150823b905092959194509250565b600080600061123460005260206000602060008060045af1503d9250838551602087016001f591503d90509250925092565b600080600080606367ffffffffffffffff8111156101575761015661020a565b5b6040519080825280601f01601f1916602001820160405280156101895781602001600182028036833780820191505090505b5090506020810160018152600160208201526001604082015260026060820153600360618201536000606282015360001960005260206000606383600060055af194503d935060005192505050909192565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610242826101f9565b810181811067ffffffffffffffff821117156102615761026061020a565b5b80604052505050565b60006102746101db565b90506102808282610239565b919050565b600067ffffffffffffffff8211156102a05761029f61020a565b5b6102a9826101f9565b9050602081019050919050565b82818337600083830152505050565b60006102d86102d384610285565b61026a565b9050828152602081018484840111156102f4576102f36101f4565b5b6102ff8482856102b6565b509392505050565b600082601f83011261031c5761031b6101ef565b5b813561032c8482602086016102c5565b91505092915050565b6000819050919050565b61034881610335565b811461035357600080fd5b50565b6000813590506103658161033f565b92915050565b60008060408385031215610382576103816101e5565b5b600083013567ffffffffffffffff8111156103a05761039f6101ea565b5b6103ac85828601610307565b92505060206103bd85828601610356565b9150509250929050565b6103d081610335565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610401826103d6565b9050919050565b610411816103f6565b82525050565b600060808201905061042c60008301876103c7565b6104396020830186610408565b61044660408301856103c7565b61045360608301846103c7565b95945050505050565b600060608201905061047160008301866103c7565b61047e6020830185610408565b61048b60408301846103c7565b949350505050565b6000819050919050565b6104a681610493565b82525050565b60006060820190506104c160008301866103c7565b6104ce60208301856103c7565b6104db604083018461049d565b94935050505056fea2646970667358221220c9b28608a5295f3b52702e75aa5d40b18593bd0a9ff2e03e2274edbd42642c6a64736f6c634300081e0033
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
pragma solidity ^0.8.25;
2+
3+
contract TvmIssueVerifier {
4+
function modexpZeroModulus()
5+
public
6+
returns (uint256 ok, uint256 sizeAfter, bytes32 copiedWord)
7+
{
8+
bytes memory input = new bytes(99);
9+
10+
assembly {
11+
let p := add(input, 0x20)
12+
mstore(p, 1)
13+
mstore(add(p, 0x20), 1)
14+
mstore(add(p, 0x40), 1)
15+
mstore8(add(p, 0x60), 2)
16+
mstore8(add(p, 0x61), 3)
17+
mstore8(add(p, 0x62), 0)
18+
mstore(0, not(0))
19+
20+
ok := call(gas(), 5, 0, p, 99, 0, 32)
21+
sizeAfter := returndatasize()
22+
copiedWord := mload(0)
23+
}
24+
}
25+
26+
function failedCreate2KeepsPriorReturnData(bytes memory code, uint256 salt)
27+
public
28+
returns (uint256 beforeSize, address created, uint256 afterSize)
29+
{
30+
assembly {
31+
mstore(0, 0x1234)
32+
pop(call(gas(), 4, 0, 0, 32, 0, 32))
33+
beforeSize := returndatasize()
34+
35+
created := create2(1, add(code, 0x20), mload(code), salt)
36+
afterSize := returndatasize()
37+
}
38+
}
39+
40+
function successfulCreate2KeepsPriorReturnData(bytes memory code, uint256 salt)
41+
public
42+
returns (uint256 beforeSize, address created, uint256 afterSize, uint256 createdSize)
43+
{
44+
assembly {
45+
mstore(0, 0x1234)
46+
pop(call(gas(), 4, 0, 0, 32, 0, 32))
47+
beforeSize := returndatasize()
48+
49+
created := create2(0, add(code, 0x20), mload(code), salt)
50+
afterSize := returndatasize()
51+
createdSize := extcodesize(created)
52+
}
53+
}
54+
55+
}

0 commit comments

Comments
 (0)