Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/runtime/cheatcodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ async function maybeAutoRegisterContract(tronWeb, toBase58, triggerJson) {

module.exports = {
rpcCall,
isTransientFetchError,
callOrFalse,
mine,
setBlockTime,
Expand Down
51 changes: 35 additions & 16 deletions src/runtime/ethers-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,27 @@ const stubProvider = {
const addr = remapped || signersMod.toBase58(address);
try {
const info = await tw.trx.getContract(addr);
return info && info.bytecode ? '0x' + info.bytecode : '0x';
if (info && info.bytecode) return '0x' + info.bytecode;
} catch {
return '0x';
/* fall through to eth_getCode */
}
// `getContract` reads ContractStore, which misses CREATE2-opcode
// deployments (e.g. RelayedCall's assembly-`create2` relayer) — those
// live in CodeStore. Fall back to `eth_getCode` on `/jsonrpc`, which
// reads CodeStore. Same endpoint maybeAutoRegisterContract uses.
try {
const base = tw.fullNode.host.replace(/\/$/, '');
const hexAddr = '0x' + TronWeb.address.toHex(addr).slice(2);
const res = await fetch(base + '/jsonrpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_getCode', params: [hexAddr, 'latest'], id: 1 }),
}).then((r) => r.json());
if (res && typeof res.result === 'string' && res.result !== '0x' && res.result !== '0x0') return res.result;
} catch {
/* */
}
return '0x';
},
async getBalance(address) {
const tw = this._tw();
Expand Down Expand Up @@ -217,25 +234,27 @@ const stubProvider = {
async getStorage(address, slot) {
const tw = this._tw();
if (!tw) return '0x' + '0'.repeat(64);
const addr = signersMod.toBase58(address);
const slotHex = typeof slot === 'bigint' ? ethersV6.toBeHex(slot, 32) : slot;
const rpc = tw.fullNode.host.replace(/\/$/, '') + '/tre';
const res = await fetch(rpc, {
// Read the EXACT slot via `eth_getStorageAt` on `/jsonrpc`. The prior
// `/tre debug_storageRangeAt` approach returned `Object.values(storage)[0]`
// — the FIRST slot the node yields, NOT the requested one (java-tron's
// storageRangeAt does not filter to the key) — so ERC-1967 slot reads
// (proxy implementation/admin) came back as slot 0 (the impl's `value`).
// `eth_getStorageAt` is exact. Also apply the EVM-pred → TVM-actual remap
// (matches getCode/getBalance) for predicted-address reads (clones, etc.).
const remapped = lookupTvmActualBase58(address);
const addr = remapped || signersMod.toBase58(address);
const slotHex = ethersV6.toBeHex(slot, 32);
const base = tw.fullNode.host.replace(/\/$/, '');
const hexAddr = '0x' + TronWeb.address.toHex(addr).slice(2);
const res = await fetch(base + '/jsonrpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'debug_storageRangeAt',
params: [0, 0, addr, slotHex, 1],
}),
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getStorageAt', params: [hexAddr, slotHex, 'latest'] }),
})
.then((r) => r.json())
.catch(() => null);
const storage = res && res.result && res.result.storage;
if (storage) {
const entry = Object.values(storage)[0];
if (entry && entry.value) return '0x' + entry.value;
if (res && typeof res.result === 'string' && res.result !== '0x') {
return ethersV6.zeroPadValue(res.result, 32);
}
return '0x' + '0'.repeat(64);
},
Expand Down
31 changes: 24 additions & 7 deletions src/runtime/wait.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// `getUnconfirmedTransactionInfo` first and only fall back to the
// solidified view as a hedge for public networks.

const { mine } = require('./cheatcodes');
const { mine, isTransientFetchError } = require('./cheatcodes');

// JSON parser that preserves integer precision above 2^53. TVM's
// transaction-info response embeds `callValueInfo[*].callValue` (and
Expand Down Expand Up @@ -46,12 +46,29 @@ async function _getInfoBigSafe(tronWeb, txId, endpoint) {
// TronWeb's JSON parsing (lossy on large integers).
const base = tronWeb.fullNode.host.replace(/\/$/, '');
const url = base + endpoint;
const resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value: txId }),
});
const text = await resp.text();
const fetchText = async () => {
const resp = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value: txId }),
});
return resp.text();
};
// Single retry on a connection-level error ("fetch failed", "other side
// closed", ECONNRESET, …) on EITHER the request or the body read. java-tron
// under G1GC with -Xmx2g can stop-the-world for 50–200 ms late in long
// parallel runs; a pause exceeding the HTTP keep-alive timeout drops the
// socket and surfaces here. Without a retry, one dropped poll fails the test
// — and because every later fixture's waitForReceipt hits the same node, it
// cascades the rest of the worker. Mirrors rpcCall's retry (cheatcodes.js).
let text;
try {
text = await fetchText();
} catch (e) {
if (!isTransientFetchError(e)) throw e;
await new Promise((r) => setTimeout(r, 100));
text = await fetchText();
}
if (!text) return null;
return jsonParseBigSafe(text);
}
Expand Down
Loading