Skip to content

Commit 7d89029

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

4 files changed

Lines changed: 256 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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
"6080604052348015600f57600080fd5b506105198061001f6000396000f3fe6080604052348015610010"
50+
+ "57600080fd5b50600436106100415760003560e01c80634ecba0f014610046578063543b525514610079"
51+
+ "5780639fefb5fd146100ab575b600080fd5b610060600480360381019061005b919061036b565b6100cb"
52+
+ "565b6040516100709493929190610417565b60405180910390f35b610093600480360381019061008e91"
53+
+ "9061036b565b610104565b6040516100a29392919061045c565b60405180910390f35b6100b361013656"
54+
+ "5b6040516100c2939291906104ac565b60405180910390f35b6000806000806112346000526020600060"
55+
+ "2060008060045af1503d9350848651602088016000f592503d9150823b905092959194509250565b6000"
56+
+ "80600061123460005260206000602060008060045af1503d9250838551602087016001f591503d905092"
57+
+ "50925092565b600080600080606367ffffffffffffffff8111156101575761015661020a565b5b604051"
58+
+ "9080825280601f01601f1916602001820160405280156101895781602001600182028036833780820191"
59+
+ "505090505b50905060208101600181526001602082015260016040820152600260608201536003606182"
60+
+ "01536000606282015360001960005260206000606383600060055af194503d9350600051925050509091"
61+
+ "92565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301"
62+
+ "169050919050565b7f4e487b710000000000000000000000000000000000000000000000000000000060"
63+
+ "0052604160045260246000fd5b610242826101f9565b810181811067ffffffffffffffff821117156102"
64+
+ "615761026061020a565b5b80604052505050565b60006102746101db565b90506102808282610239565b"
65+
+ "919050565b600067ffffffffffffffff8211156102a05761029f61020a565b5b6102a9826101f9565b90"
66+
+ "50602081019050919050565b82818337600083830152505050565b60006102d86102d384610285565b61"
67+
+ "026a565b9050828152602081018484840111156102f4576102f36101f4565b5b6102ff8482856102b656"
68+
+ "5b509392505050565b600082601f83011261031c5761031b6101ef565b5b813561032c84826020860161"
69+
+ "02c5565b91505092915050565b6000819050919050565b61034881610335565b811461035357600080fd"
70+
+ "5b50565b6000813590506103658161033f565b92915050565b6000806040838503121561038257610381"
71+
+ "6101e5565b5b600083013567ffffffffffffffff8111156103a05761039f6101ea565b5b6103ac858286"
72+
+ "01610307565b92505060206103bd85828601610356565b9150509250929050565b6103d081610335565b"
73+
+ "82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104"
74+
+ "01826103d6565b9050919050565b610411816103f6565b82525050565b600060808201905061042c6000"
75+
+ "8301876103c7565b6104396020830186610408565b61044660408301856103c7565b6104536060830184"
76+
+ "6103c7565b95945050505050565b600060608201905061047160008301866103c7565b61047e60208301"
77+
+ "85610408565b61048b60408301846103c7565b949350505050565b6000819050919050565b6104a68161"
78+
+ "0493565b82525050565b60006060820190506104c160008301866103c7565b6104ce60208301856103c7"
79+
+ "565b6104db604083018461049d565b94935050505056fea2646970667358221220c9b28608a5295f3b52"
80+
+ "702e75aa5d40b18593bd0a9ff2e03e2274edbd42642c6a64736f6c634300081e0033";
81+
82+
@Before
83+
public void enableVmFeatures() {
84+
ConfigLoader.disable = false;
85+
manager.getDynamicPropertiesStore().saveAllowTvmTransferTrc10(1);
86+
manager.getDynamicPropertiesStore().saveAllowTvmConstantinople(1);
87+
manager.getDynamicPropertiesStore().saveAllowTvmIstanbul(1);
88+
manager.getDynamicPropertiesStore().saveAllowTvmLondon(1);
89+
manager.getDynamicPropertiesStore().saveAllowTvmCompatibleEvm(1);
90+
}
91+
92+
@Test
93+
public void verifyTvmOsakaFixesWithSolidity()
94+
throws ContractExeException, ReceiptCheckErrException,
95+
VMIllegalException, ContractValidateException {
96+
byte[] verifierAddress = deployVerifier();
97+
98+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(0);
99+
100+
TVMTestResult modExpResult =
101+
trigger(verifierAddress, "modexpZeroModulus()", Collections.emptyList(), 100_000_000L);
102+
byte[] modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
103+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
104+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
105+
Assert.assertEquals("MODEXP zero modulus currently returns empty returndata",
106+
BigInteger.ZERO, word(modExpReturn, 1));
107+
108+
TVMTestResult create2Result =
109+
trigger(verifierAddress, "failedCreate2KeepsPriorReturnData(bytes,uint256)",
110+
Arrays.asList("00", 7L), 100_000_000L);
111+
byte[] create2Return = create2Result.getRuntime().getResult().getHReturn();
112+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
113+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
114+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
115+
Assert.assertEquals("failed CREATE2 keeps the previous 32-byte return data buffer",
116+
BigInteger.valueOf(32), word(create2Return, 2));
117+
118+
TVMTestResult preOsakaCreate2SuccessResult =
119+
trigger(verifierAddress, "successfulCreate2KeepsPriorReturnData(bytes,uint256)",
120+
Arrays.asList(initCodeReturningRuntime("00"), 8L), 100_000_000L);
121+
byte[] preOsakaCreate2SuccessReturn =
122+
preOsakaCreate2SuccessResult.getRuntime().getResult().getHReturn();
123+
Assert.assertNull(preOsakaCreate2SuccessResult.getRuntime().getRuntimeError());
124+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 0));
125+
Assert.assertTrue(word(preOsakaCreate2SuccessReturn, 1).signum() != 0);
126+
Assert.assertEquals(BigInteger.valueOf(32), word(preOsakaCreate2SuccessReturn, 2));
127+
Assert.assertEquals(BigInteger.ONE, word(preOsakaCreate2SuccessReturn, 3));
128+
129+
manager.getDynamicPropertiesStore().saveAllowTvmOsaka(1);
130+
131+
modExpResult =
132+
trigger(verifierAddress, "modexpZeroModulus()", Collections.emptyList(), 100_000_000L);
133+
modExpReturn = modExpResult.getRuntime().getResult().getHReturn();
134+
Assert.assertNull(modExpResult.getRuntime().getRuntimeError());
135+
Assert.assertEquals(BigInteger.ONE, word(modExpReturn, 0));
136+
Assert.assertEquals("MODEXP zero modulus returns modLen bytes after Osaka",
137+
BigInteger.ONE, word(modExpReturn, 1));
138+
139+
create2Result =
140+
trigger(verifierAddress, "failedCreate2KeepsPriorReturnData(bytes,uint256)",
141+
Arrays.asList("00", 7L), 100_000_000L);
142+
create2Return = create2Result.getRuntime().getResult().getHReturn();
143+
Assert.assertNull(create2Result.getRuntime().getRuntimeError());
144+
Assert.assertEquals(BigInteger.valueOf(32), word(create2Return, 0));
145+
Assert.assertEquals(BigInteger.ZERO, word(create2Return, 1));
146+
Assert.assertEquals("failed CREATE2 clears the previous return data buffer after Osaka",
147+
BigInteger.ZERO, word(create2Return, 2));
148+
149+
TVMTestResult create2SuccessResult =
150+
trigger(verifierAddress, "successfulCreate2KeepsPriorReturnData(bytes,uint256)",
151+
Arrays.asList(initCodeReturningRuntime("00"), 9L), 100_000_000L);
152+
byte[] create2SuccessReturn = create2SuccessResult.getRuntime().getResult().getHReturn();
153+
Assert.assertNull(create2SuccessResult.getRuntime().getRuntimeError());
154+
Assert.assertEquals(BigInteger.valueOf(32), word(create2SuccessReturn, 0));
155+
Assert.assertTrue(word(create2SuccessReturn, 1).signum() != 0);
156+
Assert.assertEquals("successful CREATE2 clears the previous return data buffer after Osaka",
157+
BigInteger.ZERO, word(create2SuccessReturn, 2));
158+
Assert.assertEquals(BigInteger.ONE, word(create2SuccessReturn, 3));
159+
}
160+
161+
private byte[] deployVerifier()
162+
throws ContractExeException, ReceiptCheckErrException,
163+
VMIllegalException, ContractValidateException {
164+
byte[] owner = Hex.decode(OWNER_ADDRESS);
165+
Transaction trx = TvmTestUtils.generateDeploySmartContractAndGetTransaction(
166+
"TvmIssueVerifier", owner, ABI, BYTECODE, 0, 1_000_000_000L, 0, null);
167+
byte[] contractAddress = WalletUtil.generateContractAddress(trx);
168+
Assert.assertNull(TvmTestUtils
169+
.processTransactionAndReturnRuntime(trx, rootRepository, null)
170+
.getRuntimeError());
171+
return contractAddress;
172+
}
173+
174+
private TVMTestResult trigger(byte[] contractAddress, String method, List<Object> args,
175+
long feeLimit)
176+
throws ContractExeException, ReceiptCheckErrException,
177+
VMIllegalException, ContractValidateException {
178+
String input = AbiUtil.parseMethod(method, args);
179+
return TvmTestUtils.triggerContractAndReturnTvmTestResult(Hex.decode(OWNER_ADDRESS),
180+
contractAddress, Hex.decode(input), 0, feeLimit, manager, null);
181+
}
182+
183+
private static BigInteger word(byte[] data, int index) {
184+
int start = index * WORD_SIZE;
185+
return new BigInteger(1, Arrays.copyOfRange(data, start, start + WORD_SIZE));
186+
}
187+
188+
private static String initCodeReturningRuntime(String runtimeCode) {
189+
byte[] runtime = Hex.decode(runtimeCode);
190+
Assert.assertTrue(runtime.length <= 255);
191+
192+
byte[] initCode = new byte[12 + runtime.length];
193+
initCode[0] = 0x60;
194+
initCode[1] = (byte) runtime.length;
195+
initCode[2] = 0x60;
196+
initCode[3] = 0x0c;
197+
initCode[4] = 0x60;
198+
initCode[5] = 0x00;
199+
initCode[6] = 0x39;
200+
initCode[7] = 0x60;
201+
initCode[8] = (byte) runtime.length;
202+
initCode[9] = 0x60;
203+
initCode[10] = 0x00;
204+
initCode[11] = (byte) 0xf3;
205+
System.arraycopy(runtime, 0, initCode, 12, runtime.length);
206+
207+
return Hex.toHexString(initCode);
208+
}
209+
}

0 commit comments

Comments
 (0)