Skip to content

Commit 2f88ae5

Browse files
committed
fix(vm): canonicalize ModExp zero modulus output (TIP-871)
1 parent 381d369 commit 2f88ae5

4 files changed

Lines changed: 255 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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,46 @@ private static long getEnergy(int baseLen, int expLen, int modLen, byte[] expVal
8080
return modExp.getEnergyForData(buildModExpData(baseLen, expLen, modLen, expValue));
8181
}
8282

83+
@Test
84+
public void testModExpZeroModulusOutputLengthGatedByOsaka() {
85+
ConfigLoader.disable = true;
86+
87+
byte[] modLenZero = buildModExpData(1, 1, 0, new byte[]{0x03});
88+
byte[] modLenOne = buildModExpData(1, 1, 1, new byte[]{0x03});
89+
byte[] modLen32 = buildModExpData(1, 1, 32, new byte[]{0x03});
90+
91+
try {
92+
VMConfig.initAllowTvmOsaka(0);
93+
Pair<Boolean, byte[]> result = modExp.execute(modLenZero);
94+
Assert.assertTrue(result.getLeft());
95+
Assert.assertEquals(0, result.getRight().length);
96+
97+
result = modExp.execute(modLenOne);
98+
Assert.assertTrue(result.getLeft());
99+
Assert.assertEquals(0, result.getRight().length);
100+
101+
result = modExp.execute(modLen32);
102+
Assert.assertTrue(result.getLeft());
103+
Assert.assertEquals(0, result.getRight().length);
104+
105+
VMConfig.initAllowTvmOsaka(1);
106+
result = modExp.execute(modLenZero);
107+
Assert.assertTrue(result.getLeft());
108+
Assert.assertEquals(0, result.getRight().length);
109+
110+
result = modExp.execute(modLenOne);
111+
Assert.assertTrue(result.getLeft());
112+
Assert.assertArrayEquals(new byte[1], result.getRight());
113+
114+
result = modExp.execute(modLen32);
115+
Assert.assertTrue(result.getLeft());
116+
Assert.assertArrayEquals(new byte[32], result.getRight());
117+
} finally {
118+
VMConfig.initAllowTvmOsaka(0);
119+
ConfigLoader.disable = false;
120+
}
121+
}
122+
83123
@Test
84124
public void testEIP7883ModExpPricing() {
85125
ConfigLoader.disable = true;
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package org.tron.common.runtime.vm;
2+
3+
import java.math.BigInteger;
4+
import java.util.Arrays;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import org.bouncycastle.util.encoders.Hex;
8+
import org.junit.Assert;
9+
import org.junit.Before;
10+
import org.junit.Test;
11+
import org.tron.common.runtime.TVMTestResult;
12+
import org.tron.common.runtime.TvmTestUtils;
13+
import org.tron.common.utils.WalletUtil;
14+
import org.tron.common.utils.client.utils.AbiUtil;
15+
import org.tron.core.exception.ContractExeException;
16+
import org.tron.core.exception.ContractValidateException;
17+
import org.tron.core.exception.ReceiptCheckErrException;
18+
import org.tron.core.exception.VMIllegalException;
19+
import org.tron.core.vm.config.ConfigLoader;
20+
import org.tron.protos.Protocol.Transaction;
21+
22+
public class TvmIssueVerifierTest extends VMTestBase {
23+
24+
private static final int WORD_SIZE = 32;
25+
26+
private static final String ABI =
27+
"[{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"code\",\"type\":\"bytes\"},"
28+
+ "{\"internalType\":\"uint256\",\"name\":\"salt\",\"type\":\"uint256\"}],"
29+
+ "\"name\":\"failedCreate2KeepsPriorReturnData\","
30+
+ "\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"beforeSize\","
31+
+ "\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"created\","
32+
+ "\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"afterSize\","
33+
+ "\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},"
34+
+ "{\"inputs\":[],\"name\":\"modexpZeroModulus\","
35+
+ "\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"ok\","
36+
+ "\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"sizeAfter\","
37+
+ "\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"copiedWord\","
38+
+ "\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},"
39+
+ "{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"code\",\"type\":\"bytes\"},"
40+
+ "{\"internalType\":\"uint256\",\"name\":\"salt\",\"type\":\"uint256\"}],"
41+
+ "\"name\":\"successfulCreate2KeepsPriorReturnData\","
42+
+ "\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"beforeSize\","
43+
+ "\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"created\","
44+
+ "\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"afterSize\","
45+
+ "\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"createdSize\","
46+
+ "\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]";
47+
48+
private static final String BYTECODE =
49+
"6080604052348015600f57600080fd5b506105198061001f6000396000f3fe60806040523480156100105760"
50+
+ "0080fd5b50600436106100415760003560e01c80634ecba0f014610046578063543b5255146100795780639f"
51+
+ "efb5fd146100ab575b600080fd5b610060600480360381019061005b919061036b565b6100cb565b60405161"
52+
+ "00709493929190610417565b60405180910390f35b610093600480360381019061008e919061036b565b6101"
53+
+ "04565b6040516100a29392919061045c565b60405180910390f35b6100b3610136565b6040516100c2939291"
54+
+ "906104ac565b60405180910390f35b60008060008061123460005260206000602060008060045af1503d9350"
55+
+ "848651602088016000f592503d9150823b905092959194509250565b60008060006112346000526020600060"
56+
+ "2060008060045af1503d9250838551602087016001f591503d90509250925092565b600080600080606367ff"
57+
+ "ffffffffffffff8111156101575761015661020a565b5b6040519080825280601f01601f1916602001820160"
58+
+ "405280156101895781602001600182028036833780820191505090505b509050602081016001815260016020"
59+
+ "8201526001604082015260026060820153600360618201536000606282015360001960005260206000606383"
60+
+ "600060055af194503d935060005192505050909192565b6000604051905090565b600080fd5b600080fd5b60"
61+
+ "0080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000"
62+
+ "000000000000000000000000000000600052604160045260246000fd5b610242826101f9565b810181811067"
63+
+ "ffffffffffffffff821117156102615761026061020a565b5b80604052505050565b60006102746101db565b"
64+
+ "90506102808282610239565b919050565b600067ffffffffffffffff8211156102a05761029f61020a565b5b"
65+
+ "6102a9826101f9565b9050602081019050919050565b82818337600083830152505050565b60006102d86102"
66+
+ "d384610285565b61026a565b9050828152602081018484840111156102f4576102f36101f4565b5b6102ff84"
67+
+ "82856102b6565b509392505050565b600082601f83011261031c5761031b6101ef565b5b813561032c848260"
68+
+ "2086016102c5565b91505092915050565b6000819050919050565b61034881610335565b8114610353576000"
69+
+ "80fd5b50565b6000813590506103658161033f565b92915050565b6000806040838503121561038257610381"
70+
+ "6101e5565b5b600083013567ffffffffffffffff8111156103a05761039f6101ea565b5b6103ac8582860161"
71+
+ "0307565b92505060206103bd85828601610356565b9150509250929050565b6103d081610335565b82525050"
72+
+ "565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610401826103d656"
73+
+ "5b9050919050565b610411816103f6565b82525050565b600060808201905061042c60008301876103c7565b"
74+
+ "6104396020830186610408565b61044660408301856103c7565b61045360608301846103c7565b9594505050"
75+
+ "5050565b600060608201905061047160008301866103c7565b61047e6020830185610408565b61048b604083"
76+
+ "01846103c7565b949350505050565b6000819050919050565b6104a681610493565b82525050565b60006060"
77+
+ "820190506104c160008301866103c7565b6104ce60208301856103c7565b6104db604083018461049d565b94"
78+
+ "935050505056fea2646970667358221220c9b28608a5295f3b52702e75aa5d40b18593bd0a9ff2e03e2274ed"
79+
+ "bd42642c6a64736f6c634300081e0033";
80+
81+
@Before
82+
public void enableVmFeatures() {
83+
ConfigLoader.disable = false;
84+
manager.getDynamicPropertiesStore().saveAllowTvmTransferTrc10(1);
85+
manager.getDynamicPropertiesStore().saveAllowTvmConstantinople(1);
86+
manager.getDynamicPropertiesStore().saveAllowTvmIstanbul(1);
87+
manager.getDynamicPropertiesStore().saveAllowTvmLondon(1);
88+
manager.getDynamicPropertiesStore().saveAllowTvmCompatibleEvm(1);
89+
}
90+
91+
@Test
92+
public void verifyTvmOsakaFixesWithSolidity()
93+
throws ContractExeException, ReceiptCheckErrException,
94+
VMIllegalException, ContractValidateException {
95+
byte[] verifierAddress = deployVerifier();
96+
97+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(0);
98+
99+
TVMTestResult modExpResult =
100+
trigger(verifierAddress, "modexpZeroModulus()", Collections.emptyList(), 100_000_000L);
101+
byte[] modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
102+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
103+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
104+
Assert.assertEquals("MODEXP zero modulus currently returns empty returndata",
105+
BigInteger.ZERO, word(modExpReturn, 1));
106+
107+
TVMTestResult create2Result =
108+
trigger(verifierAddress, "failedCreate2KeepsPriorReturnData(bytes,uint256)",
109+
Arrays.asList("00", 7L), 100_000_000L);
110+
byte[] create2Return = create2Result.getRuntime().getResult().getHReturn();
111+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
112+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
113+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
114+
Assert.assertEquals("failed CREATE2 keeps the previous 32-byte return data buffer",
115+
BigInteger.valueOf(32), word(create2Return, 2));
116+
117+
TVMTestResult preOsakaCreate2SuccessResult =
118+
trigger(verifierAddress, "successfulCreate2KeepsPriorReturnData(bytes,uint256)",
119+
Arrays.asList(initCodeReturningRuntime("00"), 8L), 100_000_000L);
120+
byte[] preOsakaCreate2SuccessReturn =
121+
preOsakaCreate2SuccessResult.getRuntime().getResult().getHReturn();
122+
Assert.assertNull(preOsakaCreate2SuccessResult.getRuntime().getRuntimeError());
123+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 0));
124+
Assert.assertTrue(word(preOsakaCreate2SuccessReturn, 1).signum() != 0);
125+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 2));
126+
Assert.assertEquals(BigInteger.ONE, word(preOsakaCreate2SuccessReturn, 3));
127+
128+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1);
129+
130+
modExpResult =
131+
trigger(verifierAddress, "modexpZeroModulus()", Collections.emptyList(), 100_000_000L);
132+
modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
133+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
134+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
135+
Assert.assertEquals("MODEXP zero modulus returns modLen bytes after Osaka",
136+
BigInteger.ONE, word(modExpReturn, 1));
137+
138+
create2Result =
139+
trigger(verifierAddress, "failedCreate2KeepsPriorReturnData(bytes,uint256)",
140+
Arrays.asList("00", 7L), 100_000_000L);
141+
create2Return = create2Result.getRuntime().getResult().getHReturn();
142+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
143+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
144+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
145+
Assert.assertEquals("failed CREATE2 clears the previous return data buffer after Osaka",
146+
BigInteger.ZERO, word(create2Return, 2));
147+
148+
TVMTestResult create2SuccessResult =
149+
trigger(verifierAddress, "successfulCreate2KeepsPriorReturnData(bytes,uint256)",
150+
Arrays.asList(initCodeReturningRuntime("00"), 9L), 100_000_000L);
151+
byte[] create2SuccessReturn = create2SuccessResult.getRuntime().getResult().getHReturn();
152+
Assert.assertNull(create2SuccessResult.getRuntime().getRuntimeError());
153+
Assert.assertEquals(BigInteger.valueOf(32), word(create2SuccessReturn, 0));
154+
Assert.assertTrue(word(create2SuccessReturn, 1).signum() != 0);
155+
Assert.assertEquals("successful CREATE2 clears the previous return data buffer after Osaka",
156+
BigInteger.ZERO, word(create2SuccessReturn, 2));
157+
Assert.assertEquals(BigInteger.ONE, word(create2SuccessReturn, 3));
158+
}
159+
160+
private byte[] deployVerifier()
161+
throws ContractExeException, ReceiptCheckErrException,
162+
VMIllegalException, ContractValidateException {
163+
byte[] owner = Hex.decode(OWNER_ADDRESS);
164+
Transaction trx = TvmTestUtils.generateDeploySmartContractAndGetTransaction(
165+
"TvmIssueVerifier", owner, ABI, BYTECODE, 0, 1_000_000_000L, 0, null);
166+
byte[] contractAddress = WalletUtil.generateContractAddress(trx);
167+
Assert.assertNull(TvmTestUtils
168+
.processTransactionAndReturnRuntime(trx, rootRepository, null)
169+
.getRuntimeError());
170+
return contractAddress;
171+
}
172+
173+
private TVMTestResult trigger(byte[] contractAddress, String method, List<Object> args,
174+
long feeLimit)
175+
throws ContractExeException, ReceiptCheckErrException,
176+
VMIllegalException, ContractValidateException {
177+
String input = AbiUtil.parseMethod(method, args);
178+
return TvmTestUtils.triggerContractAndReturnTvmTestResult(Hex.decode(OWNER_ADDRESS),
179+
contractAddress, Hex.decode(input), 0, feeLimit, manager, null);
180+
}
181+
182+
private static BigInteger word(byte[] data, int index) {
183+
int start = index * WORD_SIZE;
184+
return new BigInteger(1, Arrays.copyOfRange(data, start, start + WORD_SIZE));
185+
}
186+
187+
private static String initCodeReturningRuntime(String runtimeCode) {
188+
byte[] runtime = Hex.decode(runtimeCode);
189+
Assert.assertTrue(runtime.length <= 255);
190+
191+
byte[] initCode = new byte[12 + runtime.length];
192+
initCode[0] = 0x60;
193+
initCode[1] = (byte) runtime.length;
194+
initCode[2] = 0x60;
195+
initCode[3] = 0x0c;
196+
initCode[4] = 0x60;
197+
initCode[5] = 0x00;
198+
initCode[6] = 0x39;
199+
initCode[7] = 0x60;
200+
initCode[8] = (byte) runtime.length;
201+
initCode[9] = 0x60;
202+
initCode[10] = 0x00;
203+
initCode[11] = (byte) 0xf3;
204+
System.arraycopy(runtime, 0, initCode, 12, runtime.length);
205+
206+
return Hex.toHexString(initCode);
207+
}
208+
}

0 commit comments

Comments
 (0)