Skip to content

Commit 1f73907

Browse files
committed
test: buggy xcm message at on_initialize with regular eth tx after
1 parent 1842d82 commit 1f73907

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { beforeAll, customDevRpcRequest, describeSuite, expect } from "@moonwall/cli";
2+
import { ALITH_ADDRESS, CHARLETH_ADDRESS, alith } from "@moonwall/util";
3+
import { hexToNumber, parseEther } from "viem";
4+
import {
5+
ERC20_TOTAL_SUPPLY,
6+
XcmFragment,
7+
type XcmFragmentConfig,
8+
expectEVMResult,
9+
injectEncodedHrmpMessageAndSeal,
10+
injectHrmpMessage,
11+
injectHrmpMessageAndSeal,
12+
sovereignAccountOfSibling,
13+
} from "../../helpers";
14+
15+
describeSuite({
16+
id: "T21",
17+
title: "Trace ERC20 xcm #2",
18+
foundationMethods: "dev",
19+
testCases: ({ context, it }) => {
20+
let erc20ContractAddress: string;
21+
let eventEmitterAddress: `0x${string}`;
22+
let ethXcmTxHash: string;
23+
let regularEthTxHash: string;
24+
beforeAll(async () => {
25+
const { contractAddress, status } = await context.deployContract!("ERC20WithInitialSupply", {
26+
args: ["ERC20", "20S", ALITH_ADDRESS, ERC20_TOTAL_SUPPLY],
27+
});
28+
erc20ContractAddress = contractAddress;
29+
expect(status).eq("success");
30+
31+
const paraId = 888;
32+
const paraSovereign = sovereignAccountOfSibling(context, paraId);
33+
const amountTransferred = 1_000_000n;
34+
35+
// Get pallet indices
36+
const metadata = await context.polkadotJs().rpc.state.getMetadata();
37+
const balancesPalletIndex = metadata.asLatest.pallets
38+
.find(({ name }) => name.toString() === "Balances")!
39+
.index.toNumber();
40+
const erc20XcmPalletIndex = metadata.asLatest.pallets
41+
.find(({ name }) => name.toString() === "Erc20XcmBridge")!
42+
.index.toNumber();
43+
44+
// Send some native tokens to the sovereign account of paraId (to pay fees)
45+
await context
46+
.polkadotJs()
47+
.tx.balances.transferAllowDeath(paraSovereign, parseEther("1"))
48+
.signAndSend(alith);
49+
await context.createBlock();
50+
51+
// Send some erc20 tokens to the sovereign account of paraId
52+
const rawTx = await context.writeContract!({
53+
contractName: "ERC20WithInitialSupply",
54+
contractAddress: erc20ContractAddress as `0x${string}`,
55+
functionName: "transfer",
56+
args: [paraSovereign, amountTransferred],
57+
rawTxOnly: true,
58+
});
59+
const { result } = await context.createBlock(rawTx);
60+
expectEVMResult(result!.events, "Succeed");
61+
expect(
62+
await context.readContract!({
63+
contractName: "ERC20WithInitialSupply",
64+
contractAddress: erc20ContractAddress as `0x${string}`,
65+
functionName: "balanceOf",
66+
args: [paraSovereign],
67+
})
68+
).equals(amountTransferred);
69+
70+
// Create an XCM message that try to transfer more than available
71+
const failedConfig: XcmFragmentConfig = {
72+
assets: [
73+
{
74+
multilocation: {
75+
parents: 0,
76+
interior: {
77+
X1: { PalletInstance: Number(balancesPalletIndex) },
78+
},
79+
},
80+
fungible: 1_700_000_000_000_000n,
81+
},
82+
{
83+
multilocation: {
84+
parents: 0,
85+
interior: {
86+
X2: [
87+
{
88+
PalletInstance: erc20XcmPalletIndex,
89+
},
90+
{
91+
AccountKey20: {
92+
network: null,
93+
key: erc20ContractAddress,
94+
},
95+
},
96+
],
97+
},
98+
},
99+
fungible: amountTransferred * 2n, // Try to transfer twice the available amount
100+
},
101+
],
102+
beneficiary: CHARLETH_ADDRESS,
103+
};
104+
105+
const failedXcmMessage = new XcmFragment(failedConfig)
106+
.withdraw_asset()
107+
.clear_origin()
108+
.buy_execution()
109+
.deposit_asset(2n)
110+
.as_v3();
111+
112+
// Mock the reception of the failed xcm message N times
113+
// N should be high enough to fill IdleMaxServiceWeigh
114+
// The goal is to have at least one XCM message queud for next block on_initialize
115+
for (let i = 0; i < 20; i++) {
116+
await injectHrmpMessage(context, paraId, {
117+
type: "XcmVersionedXcm",
118+
payload: failedXcmMessage,
119+
});
120+
}
121+
await context.createBlock();
122+
123+
// By calling deployContract() a new block will be created,
124+
// including the ethereum-xcm transaction (on_initialize) + regular ethereum transaction
125+
const { contractAddress: eventEmitterAddress_ } = await context.deployContract!(
126+
"EventEmitter",
127+
{
128+
from: alith.address,
129+
} as any
130+
);
131+
eventEmitterAddress = eventEmitterAddress_;
132+
133+
// The old buggy runtime rollback the eth-xcm tx because XCM executor rollback evm reverts
134+
regularEthTxHash = (await context.viem().getBlock()).transactions[0];
135+
136+
// Get the latest block events
137+
const block = await context.polkadotJs().rpc.chain.getBlock();
138+
const allRecords = await context.polkadotJs().query.system.events.at(block.block.header.hash);
139+
140+
// Compute XCM message ID
141+
const messageHash = context.polkadotJs().createType("XcmVersionedXcm", failedXcmMessage).hash;
142+
143+
// Find messageQueue.Processed event with matching message ID
144+
const processedEvent = allRecords.find(
145+
({ event }) =>
146+
event.section === "messageQueue" &&
147+
event.method === "Processed" &&
148+
event.data[0].toString() === messageHash.toHex()
149+
);
150+
151+
expect(processedEvent).to.not.be.undefined;
152+
});
153+
154+
// IMPORTANT: this test will fail once we will merge https://github.com/moonbeam-foundation/moonbeam/pull/3258
155+
it({
156+
id: "T01",
157+
title: "should doesn't include the failed ERC20 xcm transaction in block trace",
158+
test: async function () {
159+
const number = await context.viem().getBlockNumber();
160+
const trace = await customDevRpcRequest("debug_traceBlockByNumber", [
161+
number.toString(),
162+
{ tracer: "callTracer" },
163+
]);
164+
165+
// Verify that only the regular eth transaction is included in the block trace.
166+
expect(trace.length).to.eq(1);
167+
168+
// 1st traced transaction is regular ethereum transaction.
169+
// - `From` is Alith's adddress.
170+
// - `To` is the ethereum contract address.
171+
const txHash = trace[0].txHash;
172+
expect(txHash).to.eq(regularEthTxHash);
173+
const call = trace[0].result;
174+
expect(call.from).to.eq(alith.address.toLowerCase());
175+
expect(call.to).to.eq(eventEmitterAddress.toLowerCase());
176+
expect(call.type).be.eq("CREATE");
177+
},
178+
});
179+
},
180+
});

0 commit comments

Comments
 (0)