test: Port Moonbeam Moonwall E2E test suites to DataHaven (#436)

## Summary
- Port 16 Moonbeam Moonwall E2E test suites to DataHaven, covering
proxy, multisig, sudo, txpool, receipts, precompiles, polkadot-js API,
and node RPC
- Adapt all tests for DataHaven runtime differences: 2 inherents
(timestamp + randomness) instead of Moonbeam's 4, different event
ordering/counts from fee/treasury handling, no PoV constraints
- Use resilient event-based lookups (`.find()`/`.some()`) instead of
hardcoded array indices throughout
- Add supporting Solidity contracts: `FailingConstructor`, `StorageLoop`

## New test suites
| Suite | Category | Tests |
|-------|----------|-------|
| `test-proxy-balance` | common/proxy | Proxy with Balances type |
| `test-proxy` | stagenet/proxy | Add/remove proxy, delayed & announced
proxy (7 tests) |
| `test-multisigs` | stagenet/multisig | Creation, approval, execution,
cancellation |
| `test-sudo` | stagenet/sudo | Dispatch, sudoAs, set/remove key |
| `test-polkadot-api` | stagenet/polkadot-js | Genesis, headers,
transfers, extrinsics, events |
| `test-polkadot-chain-info` | stagenet/polkadot-js | Chain name/type
queries |
| `test-node-rpc-peer` | stagenet/node-rpc | `system_peers` RPC |
| `test-precompile-bn128-bounds` | stagenet/precompile | bn128
add/mul/pairing edge cases |
| `test-receipt` | stagenet/receipt | Receipt fields, effective gas
price |
| `test-receipt-revert` | stagenet/receipt | Receipt for reverted
transactions |
| `test-evm-store-storage-growth` | stagenet/storage-growth | EVM SSTORE
storage growth |
| `test-txpool-future` | stagenet/txpool | Future transaction pool |
| `test-txpool-pending` | stagenet/txpool | Pending transaction pool |

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Steve Degosserie 2026-02-17 22:38:38 +02:00 committed by GitHub
parent 990df7ccce
commit b45009f62d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1255 additions and 0 deletions

View file

@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;
contract FailingConstructor {
constructor() {
require(false);
}
}

View file

@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract StorageLoop {
mapping(uint256 => uint256) public map;
mapping(uint256 => uint256) public map2;
function store(uint16 n) public {
for (uint16 i = 0; i < n; i++) {
map[i] = i + 1;
}
}
function store2(uint256 i) public {
map2[i] = i;
}
}

View file

@ -0,0 +1,80 @@
import { describeSuite, expect } from "@moonwall/cli";
import {
ALITH_ADDRESS,
CHARLETH_ADDRESS,
baltathar,
} from "@moonwall/util";
describeSuite({
id: "D010502",
title: "Proxy: Balances",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
it({
id: "T01",
title: "should accept known proxy",
test: async () => {
const beforeCharlieBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
const { result } = await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(baltathar.address, "Balances" as any, 0)
);
const proxyAdded = result!.events.find(
({ event }) => event.method === "ProxyAdded"
);
expect(proxyAdded).to.not.be.undefined;
expect(proxyAdded!.event.data[2].toString()).to.be.eq("Balances"); //ProxyType
expect(result!.events.some(({ event }) => event.method === "ExtrinsicSuccess")).to.be.true;
const { result: result2 } = await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
ALITH_ADDRESS,
null,
context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100)
)
.signAsync(baltathar)
);
const proxyExecuted = result2!.events.find(
({ event }) => event.method === "ProxyExecuted"
);
expect(proxyExecuted).to.not.be.undefined;
expect(proxyExecuted!.event.data[0].toString()).to.be.eq("Ok");
expect(result2!.events.some(({ event }) => event.method === "ExtrinsicSuccess")).to.be
.true;
const afterCharlieBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlieBalance - beforeCharlieBalance).to.be.eq(100n);
},
});
it({
id: "T02",
title: "shouldn't accept other proxy types",
test: async () => {
await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(baltathar.address, "Balances", 0)
);
const { result } = await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
ALITH_ADDRESS,
null,
context.polkadotJs().tx.system.remark("0x")
)
.signAsync(baltathar)
);
const proxyExecuted = result!.events.find(
({ event }) => event.method === "ProxyExecuted"
);
expect(proxyExecuted).to.not.be.undefined;
// Balances proxy type should not allow system.remark
expect(proxyExecuted!.event.data[0].toString()).to.not.be.eq("Ok");
expect(result!.events.some(({ event }) => event.method === "ExtrinsicSuccess")).to.be.true;
},
});
},
});

View file

@ -0,0 +1,184 @@
import { beforeAll, describeSuite, expect } from "@moonwall/cli";
import { blake2AsHex, createKeyMulti } from "@polkadot/util-crypto";
import { u8aToHex } from "@polkadot/util";
import {
ALITH_ADDRESS,
BALTATHAR_ADDRESS,
CHARLETH_ADDRESS,
DOROTHY_ADDRESS,
alith,
baltathar,
} from "@moonwall/util";
// This test cases in this suite are dependent on each other, and must be run in order.
// TODO: Make the test cases atomic
describeSuite({
id: "D022101",
title: "Multisigs - perform multisigs operations",
foundationMethods: "dev",
testCases: ({ context, it }) => {
let threshold: number;
let call: any;
let encodedCall: string;
let encodedCallHash: string;
// multisig accountId
let encodedMultisigId: Uint8Array;
let multisigId: string;
beforeAll(async function () {
// set threshold and create multisig accountId
threshold = 2;
encodedMultisigId = createKeyMulti([ALITH_ADDRESS, BALTATHAR_ADDRESS, CHARLETH_ADDRESS], 2);
multisigId = u8aToHex(encodedMultisigId.slice(0, 20));
// encode and hash the call we want to dispatch as a multisig operation
call = context.polkadotJs().tx.balances.transferKeepAlive(DOROTHY_ADDRESS, 20);
encodedCall = call.method.toHex();
encodedCallHash = blake2AsHex(encodedCall);
});
it({
id: "T01",
title: "Should create a multisig operation with asMulti",
test: async () => {
// set signatories
const otherSignatories = [BALTATHAR_ADDRESS, CHARLETH_ADDRESS];
const block = await context.createBlock(
context
.polkadotJs()
.tx.multisig.asMulti(threshold, otherSignatories, null, encodedCall, {})
.signAsync(alith)
);
// check the event 'NewMultisig' was emitted
const records = await context.polkadotJs().query.system.events();
const events = records.filter(
({ event }) => event.section === "multisig" && event.method === "NewMultisig"
);
expect(events).to.have.lengthOf(1);
expect(block.result!.successful).to.be.true;
},
});
it({
id: "T02",
title: "Should be able to approve a multisig operation with approveAsMulti",
test: async function () {
// signatories (sorted)
const otherSignatories = [CHARLETH_ADDRESS, ALITH_ADDRESS];
// create a new multisig operation
await context.createBlock(
context
.polkadotJs()
.tx.multisig.asMulti(threshold, otherSignatories, null, encodedCall, {})
.signAsync(alith),
{ allowFailures: true }
);
// take the info of the new multisig operation saved in storage
const multisigInfo = await context
.polkadotJs()
.query.multisig.multisigs(multisigId, encodedCallHash);
const block = await context.createBlock(
context
.polkadotJs()
.tx.multisig.approveAsMulti(
threshold,
otherSignatories,
multisigInfo.unwrap().when,
encodedCallHash,
{}
)
.signAsync(baltathar),
{ allowFailures: true }
);
// check the event 'MultisigApproval' was emitted
const records = await context.polkadotJs().query.system.events();
const events = records.filter(
({ event }) => event.section === "multisig" && event.method === "MultisigApproval"
);
expect(events).to.have.lengthOf(1);
expect(block.result!.successful).to.be.true;
},
});
it({
id: "T03",
title: "Should be able to cancel a multisig operation",
test: async () => {
const otherSignatories = [BALTATHAR_ADDRESS, CHARLETH_ADDRESS];
// create a new multisig operation
await context.createBlock(
context
.polkadotJs()
.tx.multisig.asMulti(threshold, otherSignatories, null, encodedCall, {})
.signAsync(alith),
{ allowFailures: true }
);
// take the info of the new multisig operation saved in storage
const multisigInfo = await context
.polkadotJs()
.query.multisig.multisigs(multisigId, encodedCallHash);
const block = await context.createBlock(
context
.polkadotJs()
.tx.multisig.cancelAsMulti(
threshold,
otherSignatories,
multisigInfo.unwrap().when,
encodedCallHash
)
.signAsync(alith)
);
const records = await context.polkadotJs().query.system.events();
const events = records.filter(
({ event }) => event.section === "multisig" && event.method === "MultisigCancelled"
);
expect(events, "event 'MultisigCancelled' was not emitted").to.have.lengthOf(1);
expect(block.result!.successful).to.be.true;
},
});
it({
id: "T04",
title: "Should fail if signatories are out of order",
test: async () => {
const otherSignatories = [CHARLETH_ADDRESS, BALTATHAR_ADDRESS];
const block = await context.createBlock(
context
.polkadotJs()
.tx.multisig.asMulti(threshold, otherSignatories, null, encodedCall, {})
.signAsync(alith),
{ allowFailures: true }
);
expect(block.result!.error!.name, "signatories (they are not sorted)").to.equal(
"SignatoriesOutOfOrder"
);
expect(block.result!.successful).to.be.false;
},
});
it({
id: "T05",
title: "Should fail if sender is present in signatories",
test: async () => {
// signatories (with sender in signatories)
const otherSignatories = [ALITH_ADDRESS, BALTATHAR_ADDRESS];
const block = await context.createBlock(
context
.polkadotJs()
.tx.multisig.asMulti(threshold, otherSignatories, null, encodedCall, {})
.signAsync(alith),
{ allowFailures: true }
);
expect(block.result!.error!.name).to.equal("SenderInSignatories");
expect(block.result!.successful).to.be.false;
},
});
},
});

View file

@ -0,0 +1,21 @@
import { customDevRpcRequest, describeSuite, expect } from "@moonwall/cli";
describeSuite({
id: "D022201",
title: "Node - RPC",
foundationMethods: "dev",
testCases: ({ context, it }) => {
it({
id: "T01",
title: "should report peer count in hex",
test: async function () {
// this tests that the "net_peerCount" response comes back in hex and not decimal.
// related: frontier commits 677548c and 78fb3bc
const result = await customDevRpcRequest("net_peerCount", []);
expect(result).to.be.equal("0x0");
expect(typeof result).to.be.equal("string");
},
});
},
});

View file

@ -0,0 +1,141 @@
import { describeSuite, expect } from "@moonwall/cli";
import { ALITH_ADDRESS, GLMR, generateKeyringPair } from "@moonwall/util";
describeSuite({
id: "D022501",
title: "Polkadot API",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
it({
id: "T01",
title: "should return genesis block",
test: async function () {
const lastHeader = await context.polkadotJs().rpc.chain.getHeader();
expect(Number(lastHeader.number) >= 0).to.be.true;
},
});
it({
id: "T02",
title: "should return latest header number",
test: async function () {
await context.createBlock();
const lastHeader = await context.polkadotJs().rpc.chain.getHeader();
expect(lastHeader.number.toNumber()).to.be.at.least(1);
},
});
it({
id: "T03",
title: "transfers should be stored on chain",
test: async function () {
const randomAddress = generateKeyringPair().address as `0x${string}`;
await context.createBlock(
context.polkadotJs().tx.balances.transferAllowDeath(randomAddress, 2n * GLMR)
);
expect(BigInt(await context.viem().getBalance({ address: randomAddress }))).to.equal(
2n * GLMR
);
},
});
it({
id: "T04",
title: "should appear in extrinsics",
test: async function () {
const randomAddress = generateKeyringPair().address as `0x${string}`;
await context.createBlock(
context.polkadotJs().tx.balances.transferAllowDeath(randomAddress, 2n * GLMR)
);
const signedBlock = await context.polkadotJs().rpc.chain.getBlock();
// Expecting 3 extrinsics so far:
// timestamp, author, the parachain validation data, randomness, and the balances transfer.
expect(signedBlock.block.extrinsics).to.be.of.length(3);
signedBlock.block.extrinsics.forEach((ex, index) => {
const {
method: { args, method, section },
} = ex;
const message = `${section}.${method}(${args.map((a) => a.toString()).join(", ")})`;
switch (index) {
case 0:
expect(message.substring(0, 13)).to.eq(`timestamp.set`);
break;
case 1:
expect(message.toLocaleLowerCase()).to.match(/^randomness\.setbaberandomness/);
break;
case 2:
expect(message).to.eq(
`balances.transferAllowDeath(${randomAddress}, 2000000000000000000)`
);
expect(ex.signer.toString()).to.eq(ALITH_ADDRESS);
break;
default:
throw new Error(`Unexpected extrinsic: ${message}`);
}
});
},
});
it({
id: "T05",
title: "should appear in events",
test: async function () {
// Generating two transfers to ensure treasury account exists
const randomAddress = generateKeyringPair().address as `0x${string}`;
await context.createBlock(
context.polkadotJs().tx.balances.transferAllowDeath(randomAddress, 2n * GLMR)
);
const randomAddress2 = generateKeyringPair().address as `0x${string}`;
await context.createBlock(
context.polkadotJs().tx.balances.transferAllowDeath(randomAddress2, 2n * GLMR)
);
const signedBlock = await context.polkadotJs().rpc.chain.getBlock();
const apiAt = await context.polkadotJs().at(signedBlock.block.header.hash);
const allRecords = await apiAt.query.system.events();
// map between the extrinsics and events
signedBlock.block.extrinsics.forEach((_, index) => {
// filter the specific events based on the phase and then the
// index of our extrinsic in the block
const events = allRecords
.filter(({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index))
.map(({ event }) => event);
switch (index) {
// First 2 events:
// timestamp.set:: system.ExtrinsicSuccess
// randomness.setBabeRandomness:: system.ExtrinsicSuccess
case 0:
case 1:
expect(events).to.be.of.length(1);
expect(context.polkadotJs().events.system.ExtrinsicSuccess.is(events[0])).to.be.true;
break;
// balances.transferAllowDeath emits: system.NewAccount, balances.Endowed,
// balances.Transfer, (other events), system.ExtrinsicSuccess
case 2:
log(events.map((e) => `${e.section}.${e.method}`).join(" - "));
expect(events.length).to.be.at.least(7);
expect(events.some((e) => context.polkadotJs().events.system.NewAccount.is(e))).to.be
.true;
expect(events.some((e) => context.polkadotJs().events.balances.Endowed.is(e))).to.be
.true;
expect(events.some((e) => context.polkadotJs().events.balances.Transfer.is(e))).to.be
.true;
// ExtrinsicSuccess should be the last event
expect(
context.polkadotJs().events.system.ExtrinsicSuccess.is(events[events.length - 1])
).to.be.true;
break;
default:
throw new Error(`Unexpected extrinsic`);
}
});
},
});
},
});

View file

@ -0,0 +1,21 @@
import { customDevRpcRequest, describeSuite, expect } from "@moonwall/cli";
describeSuite({
id: "D022502",
title: "Web3Api Information",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
it({
id: "T01",
title: "should include client version",
test: async function () {
const version = (await customDevRpcRequest("web3_clientVersion", [])) as string;
const specName = context.polkadotJs().runtimeVersion.specName.toString();
const specVersion = context.polkadotJs().runtimeVersion.specVersion.toString();
const implVersion = context.polkadotJs().runtimeVersion.implVersion.toString();
const expected = `${specName}/v${specVersion}.${implVersion}/fc-rpc-2.0.0-dev`;
expect(version).toBe(expected);
},
});
},
});

View file

@ -0,0 +1,80 @@
import { describeSuite } from "@moonwall/cli";
import { createViemTransaction, sendRawTransaction } from "@moonwall/util";
/*
* These test cases trigger bugs in the bn128 precompiles which perform a from_slice()
* call on unchecked input.
*
* Fixed by:
* https://github.com/paritytech/frontier/pull/394
*/
describeSuite({
id: "D022703",
title: "Precompiles - bn128 bounds",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
it({
id: "T01",
title: "should fail gracefully (case 1)",
test: async () => {
// some call data which will cause bn128 to be called with insufficient input. this
// presumably was generated through fuzzing.
const data =
("0x608060405234801561001057600080fd5b5060008060405180608001604052807f2243525c5eda" +
"1401003c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703815260207f6e01001d33be6da800" +
"00002bcc35964723180eed75f91a010001007d48f195c91581526020017f18b18acfb4c2c30276db" +
"5411000000000000000b4691610c5d3b00010001b17f81526020017f063c909c4720840cb5134cb9" +
"f546c80200579d040100d32efc0d288197f37266815246475b6100bf61044d505b6040604b808460" +
"006007600019f1925082610142576000000000acd401000000000000000000000000000000000000" +
"000000000000000000008152600401808060200182810300192c748252601e8152602001887f656c" +
"6c967074000381327572766520616464ad74696f6e206661696c6564000081525060200191500000" +
"000000000009fd5b7f2bd3e6d0f3b142924f5ca7b49ce5b9d585420400ae5648e61d02268b1a0a9f" +
"b7816000600202020202020202020202020202020202fd0202020203020202020202020202020202" +
"0202fb02020a02020202020202020202020202020202020202020202020202020202020202020202" +
"02020202020200000000000000000a1c000000000000000000000000000000000000000901010037" +
"190100000000000000000000f81a0100000002020202020202020202020202020202020202028a30" +
"a82123b27db75200aedc4a45a0e84fbd1f9f3621350bb778119630350eb7a7e613058daf51e9f514" +
"8ea65715eaac3d8019f80498112fc4860a020202020202020202fd02020202020202020202020202" +
"02020202020202020202020202020202020202020202020202020202020202020202020202020212" +
"02020202020202020202010202fd0202020202020202020202020202020202020202020202020202" +
"020202020202020202020202005f02d2020202020202020202020202020202020202020202020202" +
"02020202020202020202020302020202020202020202020202020202020202020202020202020202" +
"0302020202020202020202020202") as `0x${string}`;
const tx = await createViemTransaction(context, {
to: "0x0000000000000000000000000000000000000007",
data,
skipEstimation: true,
});
await sendRawTransaction(context, tx);
// we expect that the node hasn't crashed by here. without a fix, the previous web3 request
// would have been sufficient to crash our node. now it fails with "ExhaustsResources". if
// we can create a block, we must not have crashed.
await context.createBlock();
},
});
it({
id: "T02",
title: "should fail gracefully (case 2)",
test: async () => {
// similar to the above call data, although triggers a slightly different bug
const tx = await createViemTransaction(context, {
to: "0x0000000000000000000000000000000000000007",
data: "0x0000000000000000000000000000000000000000050000000000008303d0300d901401",
skipEstimation: true,
});
await sendRawTransaction(context, tx);
// we expect that the node hasn't crashed by here. without a fix, the previous web3 request
// would have been sufficient to crash our node. now it fails with "ExhaustsResources". if
// we can create a block, we must not have crashed.
await context.createBlock();
},
});
},
});

View file

@ -0,0 +1,257 @@
import { beforeEach, describeSuite, expect } from "@moonwall/cli";
import {
ALITH_ADDRESS,
CHARLETH_ADDRESS,
type KeyringPair,
alith,
generateKeyringPair,
} from "@moonwall/util";
// In these tests Alith will allow signer to perform calls on her behalf.
// Charleth is used as a target account when making transfers.
describeSuite({
id: "D022902",
title: "Proxy - proxy",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let signer: KeyringPair;
beforeEach(async () => {
signer = generateKeyringPair("ethereum");
await context.createBlock(
context.polkadotJs().tx.balances.transferAllowDeath(signer.address, 5n * 10n ** 18n)
);
});
it({
id: "T01",
title: "shouldn't accept unknown proxy",
test: async function () {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
const expectEvents = [context.polkadotJs().events.system.ExtrinsicFailed];
await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
ALITH_ADDRESS,
null,
context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100)
)
.signAsync(signer),
{ expectEvents, signer: alith, allowFailures: true }
);
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(0n);
},
});
it({
id: "T02",
title: "should accept known proxy",
test: async () => {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
const events1 = [
context.polkadotJs().events.system.ExtrinsicSuccess,
context.polkadotJs().events.proxy.ProxyAdded,
];
const { result } = await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(signer.address, "Any", 0),
{ signer: alith, expectEvents: events1 }
);
const proxyAdded = result?.events.find(({ event }) =>
context.polkadotJs().events.proxy.ProxyAdded.is(event)
);
expect(proxyAdded).to.not.be.undefined;
expect(proxyAdded!.event.data[2].toString()).to.be.eq("Any"); //ProxyType
const events2 = [
context.polkadotJs().events.system.ExtrinsicSuccess,
context.polkadotJs().events.proxy.ProxyExecuted,
];
const { result: result2 } = await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
alith.address,
null,
context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100)
)
.signAsync(signer),
{ signer: alith, expectEvents: events2 }
);
const proxyExecuted = result2?.events.find(({ event }) =>
context.polkadotJs().events.proxy.ProxyExecuted.is(event)
);
expect(proxyExecuted).to.not.be.undefined;
expect(proxyExecuted!.event.data[0].toString()).to.be.eq("Ok");
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(100n);
},
});
it({
id: "T03",
title: "shouldn't accept removed proxy",
test: async () => {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(signer.address, "Any", 0),
{ signer: alith, allowFailures: false }
);
await context.createBlock(
context.polkadotJs().tx.proxy.removeProxy(signer.address, "Any", 0),
{ signer: alith, allowFailures: false }
);
await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
alith.address,
null,
context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100)
)
.signAsync(signer),
{ signer: alith, expectEvents: [context.polkadotJs().events.system.ExtrinsicFailed] }
);
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(0n);
},
});
it({
id: "T04",
title: "shouldn't accept instant for delayed proxy",
test: async () => {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(signer.address, "Any", 2),
{ signer: alith, allowFailures: false }
);
await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxy(
alith.address,
null,
context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100)
)
.signAsync(signer),
{ signer: alith, expectEvents: [context.polkadotJs().events.system.ExtrinsicFailed] }
);
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(0n);
},
});
it({
id: "T05",
title: "shouldn't accept early delayed proxy",
test: async () => {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
const { result } = await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(signer.address, "Any", 6),
{ signer: alith, allowFailures: false }
);
result?.events.forEach(({ event }) => log(`1${event.method}(${event.data})`));
const transfer = context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100);
const { result: result2 } = await context.createBlock(
context.polkadotJs().tx.proxy.announce(alith.address, transfer.hash).signAsync(signer),
{
signer: alith,
expectEvents: [context.polkadotJs().events.proxy.Announced],
allowFailures: false,
}
);
result2?.events.forEach(({ event }) => log(`2${event.method}(${event.data})`));
const { result: result3 } = await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxyAnnounced(signer.address, alith.address, null, transfer)
.signAsync(signer),
{
signer: alith,
expectEvents: [context.polkadotJs().events.system.ExtrinsicFailed],
}
);
result3?.events.forEach(({ event }) => log(`3${event.method}(${event.data})`));
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(0n);
},
});
it({
id: "T06",
title: "should accept on-time delayed proxy ",
test: async () => {
const beforeCharlethBalance = await context
.viem()
.getBalance({ address: CHARLETH_ADDRESS });
await context.createBlock(
context.polkadotJs().tx.proxy.addProxy(signer.address, "Any", 2),
{ signer: alith, allowFailures: false }
);
const transfer = context.polkadotJs().tx.balances.transferAllowDeath(CHARLETH_ADDRESS, 100);
const u8a = transfer.method.toU8a();
const transfer_hash = transfer.registry.hash(u8a).toHex();
const { result: result2 } = await context.createBlock(
context.polkadotJs().tx.proxy.announce(alith.address, transfer_hash).signAsync(signer),
{
signer: alith,
expectEvents: [context.polkadotJs().events.proxy.Announced],
allowFailures: false,
}
);
const announced = result2?.events.find(({ event }) =>
context.polkadotJs().events.proxy.Announced.is(event)
);
expect(announced).to.not.be.undefined;
expect(announced!.event.data[2].toHex()).to.eq(transfer_hash);
await context.createBlock();
await context.createBlock();
// On time.
const { result: result3 } = await context.createBlock(
context
.polkadotJs()
.tx.proxy.proxyAnnounced(signer.address, alith.address, null, transfer)
.signAsync(signer),
{
signer: alith,
expectEvents: [context.polkadotJs().events.proxy.ProxyExecuted],
allowFailures: false,
}
);
const afterCharlethBalance = await context.viem().getBalance({ address: CHARLETH_ADDRESS });
expect(afterCharlethBalance - beforeCharlethBalance).to.be.eq(100n);
},
});
},
});

View file

@ -0,0 +1,33 @@
import { describeSuite, expect } from "@moonwall/cli";
import { getAddress } from "viem";
describeSuite({
id: "D023101",
title: "Receipt - Revert",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
it({
id: "T01",
title: "should generate a receipt for a reverted transaction",
test: async function () {
const { hash } = await context.deployContract!("FailingConstructor", { gas: 300000n });
const receipt = await context.viem().getTransactionReceipt({ hash });
expect(receipt.status).toBe("reverted");
expect(receipt.blockNumber).toBe(1n);
expect(getAddress(receipt.contractAddress!)).toBe(
getAddress("0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3")
);
expect(receipt.cumulativeGasUsed).toBe(54604n);
expect(getAddress(receipt.from!)).toBe(
getAddress("0xf24ff3a9cf04c71dbc94d0b566f7a27b94566cac")
);
expect(receipt.gasUsed).toBe(54604n);
expect(receipt.logs).toStrictEqual([]);
expect(receipt.transactionHash).toBe(hash);
expect(receipt.to).toBe(null);
expect(receipt.transactionIndex).toBe(0);
},
});
},
});

View file

@ -0,0 +1,61 @@
import { beforeAll, describeSuite, expect } from "@moonwall/cli";
import { BALTATHAR_ADDRESS, alith } from "@moonwall/util";
describeSuite({
id: "D023103",
title: "Receipt - Contract",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let txHash: string;
let eventContract: `0x${string}`;
beforeAll(async () => {
const { contractAddress, hash } = await context.deployContract!("EventEmitter");
eventContract = contractAddress;
txHash = hash;
});
it({
id: "T01",
title: "Should generate receipt",
test: async function () {
const block = await context.viem().getBlock({ blockNumber: 1n });
const receipt = await context
.viem()
.getTransactionReceipt({ hash: txHash as `0x${string}` });
expect(receipt.blockHash).toBe(block.hash);
expect(receipt.blockNumber).toBe(block.number);
expect(receipt.from).toBe(alith.address.toLowerCase());
expect(receipt.logs.length).toBe(1);
expect(receipt.logs[0].address).toBe(eventContract);
expect(receipt.logs[0].blockHash).toBe(block.hash);
},
});
it({
id: "T02",
title: "should calculate effective gas price",
test: async function () {
const maxFeePerGas = 10_000_000_000n * 2n;
const rawTxn = await context.createTxn!({
gas: 21000n,
libraryType: "viem",
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxFeePerGas,
to: BALTATHAR_ADDRESS,
data: "0x",
txnType: "eip1559",
});
await context.createBlock(rawTxn);
const block = await context.viem().getBlock();
const receipt = await context
.viem()
.getTransactionReceipt({ hash: block.transactions[0] as `0x${string}` });
expect(receipt.effectiveGasPrice).to.be.eq(maxFeePerGas);
},
});
},
});

View file

@ -0,0 +1,76 @@
// TODO: Constants (storage growth ratios, limits) may need adjustment for DataHaven's runtime configuration
import {
TransactionTypes,
beforeAll,
deployCreateCompiledContract,
describeSuite,
} from "@moonwall/cli";
import { createEthersTransaction } from "@moonwall/util";
import { expectEVMResult, expectOk } from "../../../../helpers";
import { type Abi, encodeFunctionData } from "viem";
describeSuite({
id: "D023403",
title: "Storage growth limit - New Entries",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let storageLoopAddress: `0x${string}`;
let storageLoopAbi: Abi;
// Number of bytes added to storage for a new entry.
const ACCOUNT_STORAGE_SIZE = 116;
// Ratio of gas to storage growth. (BlockGasLimit (15_000_000) / BlockStorageLimit (40kb))
const GAS_LIMIT_STORAGE_GROWTH_RATIO = 366;
beforeAll(async () => {
const { contractAddress, abi } = await deployCreateCompiledContract(context, "StorageLoop");
storageLoopAddress = contractAddress;
storageLoopAbi = abi;
await context.createBlock();
});
for (const txnType of TransactionTypes) {
it({
id: `T0${TransactionTypes.indexOf(txnType) + 1}`,
title: "should out of gas when gas provided is not enough to cover storage growth",
test: async function () {
// Tx is creating 5 new storage entries. So, required gas is:
// (5 * ACCOUNT_STORAGE_SIZE) * GAS_LIMIT_STORAGE_GROWTH_RATIO = 212_280
// Execute tx with insufficient gas limit
const rawSigned = await createEthersTransaction(context, {
to: storageLoopAddress,
data: encodeFunctionData({
abi: storageLoopAbi,
functionName: "store",
// for each transaction type, we add 5 new storage entries
args: [5 + 5 * TransactionTypes.indexOf(txnType)],
}),
gasLimit: 212_270,
});
const { result } = await context.createBlock(rawSigned);
// Check that the transaction failed with an out of gas error
expectEVMResult(result!.events, "Error", "OutOfGas");
},
});
it({
id: `T0${TransactionTypes.indexOf(txnType) + 4}`,
title: "should successfully execute when updating existing storage entries (no growth)",
test: async function () {
// Update 5 existing storage entries. So, required gas should be less than 212_280
const rawSigned = await createEthersTransaction(context, {
to: storageLoopAddress,
data: encodeFunctionData({
abi: storageLoopAbi,
functionName: "store",
args: [5],
}),
gasLimit: 50_000,
});
await expectOk(context.createBlock(rawSigned));
},
});
}
},
});

View file

@ -0,0 +1,123 @@
import { describeSuite, expect } from "@moonwall/cli";
import {
ALITH_ADDRESS,
DEFAULT_GENESIS_BALANCE,
baltathar,
generateKeyringPair,
} from "@moonwall/util";
import { ALITH_GENESIS_TRANSFERABLE_BALANCE, verifyLatestBlockFees } from "../../../../helpers";
import { CHARLETH_ADDRESS, ETHAN_ADDRESS } from "@moonwall/util";
describeSuite({
id: "D023601",
title: "Sudo - successful forceSetBalance",
foundationMethods: "dev",
testCases: ({ context, it }) => {
it({
id: "T01",
title: "should be able to call sudo with the right account",
test: async function () {
const { result } = await context.createBlock(
context
.polkadotJs()
.tx.sudo.sudo(context.polkadotJs().tx.balances.forceSetBalance(ETHAN_ADDRESS, 0))
);
const account = await context.polkadotJs().query.system.account(ETHAN_ADDRESS);
expect(account.data.free.toBigInt()).toBe(0n);
// DataHaven has 1 more event (NewChallengeSeed) than Moonbeam,
// coming from the StorageHub ProofsDealer pallet.
expect(result!.events.length).to.eq(8);
console.log(result!.events.map((e) => e.event.toHuman()));
expect(context.polkadotJs().events.balances.BalanceSet.is(result!.events[3].event)).to.be
.true;
expect(context.polkadotJs().events.sudo.Sudid.is(result!.events[4].event)).to.be.true;
expect(context.polkadotJs().events.balances.Deposit.is(result!.events[5].event)).to.be.true;
expect(context.polkadotJs().events.system.ExtrinsicSuccess.is(result!.events[7].event)).to
.be.true;
expect(
await context.viem().getBalance({ address: ALITH_ADDRESS }),
"diff should be null for sudo - funds are sent back"
).to.equal(ALITH_GENESIS_TRANSFERABLE_BALANCE);
},
});
it({
id: "T02",
title: "should charge the correct amount of gas when calling sudo",
test: async function () {
await context.createBlock(
context
.polkadotJs()
.tx.sudo.sudo(
context
.polkadotJs()
.tx.balances.forceSetBalance(CHARLETH_ADDRESS, DEFAULT_GENESIS_BALANCE)
)
);
await verifyLatestBlockFees(context);
},
});
it({
id: "T03",
title: "should NOT be able to call sudo with another account than sudo account",
test: async function () {
const baltathar_before = await context.polkadotJs().query.system.account(CHARLETH_ADDRESS);
const { result } = await context.createBlock(
context
.polkadotJs()
.tx.sudo.sudo(context.polkadotJs().tx.balances.forceSetBalance(CHARLETH_ADDRESS, 0))
.signAsync(baltathar),
{ allowFailures: true }
);
// Check that balance didn't change
const account = await context.polkadotJs().query.system.account(CHARLETH_ADDRESS);
expect(account.data.free.toBigInt()).toBe(DEFAULT_GENESIS_BALANCE);
expect(result!.events.length === 8).to.be.true;
expect(context.polkadotJs().events.system.NewAccount.is(result!.events[3].event)).to.be
.true;
expect(context.polkadotJs().events.balances.Endowed.is(result!.events[4].event)).to.be.true;
expect(context.polkadotJs().events.system.ExtrinsicFailed.is(result!.events[7].event)).to.be
.true;
},
});
it({
id: "T04",
title: "should not be able to call sudo with no funds",
test: async function () {
const newSigner = generateKeyringPair();
await context.createBlock(context.polkadotJs().tx.sudo.setKey(newSigner.address), {
allowFailures: false,
});
expect((await context.polkadotJs().query.sudo.key()).unwrap().toString()).toBe(
newSigner.address
);
await expect(
async () =>
await context.createBlock(
context
.polkadotJs()
.tx.sudo.sudo(context.polkadotJs().tx.balances.forceSetBalance(CHARLETH_ADDRESS, 0))
.signAsync(newSigner)
)
).rejects.toThrowError(
"1010: Invalid Transaction: Inability to pay some fees , e.g. account balance too low"
);
expect(await context.viem().getBalance({ address: CHARLETH_ADDRESS })).to.equal(
DEFAULT_GENESIS_BALANCE
);
},
});
},
});

View file

@ -0,0 +1,69 @@
import { beforeAll, describeSuite, expect, fetchCompiledContract } from "@moonwall/cli";
import { alith, createEthersTransaction, sendRawTransaction } from "@moonwall/util";
import { encodeDeployData, toHex } from "viem";
describeSuite({
id: "D023802",
title: "TxPool - Future Ethereum transaction",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let txHash: string;
let deployData: string;
beforeAll(async () => {
const { abi, bytecode } = fetchCompiledContract("MultiplyBy7");
deployData = encodeDeployData({
abi,
bytecode,
});
const rawTxn = await createEthersTransaction(context, {
data: deployData,
gasLimit: 1048576,
nonce: 1, // future nonce
});
txHash = await sendRawTransaction(context, rawTxn);
});
it({
id: "T01",
title: "should appear in the txpool inspection",
test: async function () {
const inspect = (await context
.viem()
.transport.request({ method: "txpool_inspect" })) as any;
// web3 rpc returns lowercase
const data = inspect.queued[alith.address.toLowerCase()][toHex(1)];
expect(data).to.not.be.undefined;
expect(data).to.be.equal(
"0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 10000000000 wei"
);
},
});
it({
id: "T02",
title: "should appear in the txpool content",
test: async function () {
const content = (await context
.viem()
.transport.request({ method: "txpool_content" })) as any;
// web3 rpc returns lowercase
const data = content.queued[alith.address.toLowerCase()][toHex(1)];
expect(data).toMatchObject({
blockHash: null,
blockNumber: null,
from: alith.address.toLowerCase(),
gas: "0x100000",
gasPrice: "0x2540be400",
hash: txHash,
input: deployData,
nonce: toHex(1),
to: null,
transactionIndex: null,
value: "0x0",
});
},
});
},
});

View file

@ -0,0 +1,84 @@
import { beforeAll, describeSuite, expect, fetchCompiledContract } from "@moonwall/cli";
import { ALITH_ADDRESS, createEthersTransaction, sendRawTransaction } from "@moonwall/util";
import { encodeDeployData, toHex } from "viem";
describeSuite({
id: "D023807",
title: "TxPool - Pending Ethereum transaction",
foundationMethods: "dev",
testCases: ({ context, it, log }) => {
let txHash: string;
beforeAll(async () => {
const { abi, bytecode } = fetchCompiledContract("MultiplyBy7");
const deployData = encodeDeployData({
abi,
bytecode,
});
const rawTxn = await createEthersTransaction(context, {
data: deployData,
gasLimit: 1048576,
nonce: 0,
});
txHash = await sendRawTransaction(context, rawTxn);
});
it({
id: "T01",
title: "should appear in the txpool inspection",
test: async function () {
const inspect = (await context
.viem()
.transport.request({ method: "txpool_inspect" })) as any;
// web3 rpc returns lowercase
const data = inspect.pending[ALITH_ADDRESS.toLowerCase()][toHex(0)];
expect(data).to.not.be.undefined;
expect(data).to.be.equal(
"0x0000000000000000000000000000000000000000: 0 wei + 1048576 gas x 10000000000 wei"
);
},
});
it({
id: "T02",
title: "should be marked as pending",
test: async function () {
const pendingTransaction = await context
.viem()
.getTransaction({ hash: txHash as `0x${string}` });
// pending transactions do not know yet to which block they belong to
expect(pendingTransaction).to.include({
blockNumber: null,
hash: txHash,
});
},
});
it({
id: "T03",
title: "should appear in the txpool content",
test: async function () {
const content = (await context
.viem()
.transport.request({ method: "txpool_content" })) as any;
// web3 rpc returns lowercase
const data = content.pending[ALITH_ADDRESS.toLowerCase()][toHex(0)];
expect(data).to.include({
blockHash: null,
blockNumber: null,
from: ALITH_ADDRESS.toLowerCase(),
gas: "0x100000",
gasPrice: "0x2540be400",
hash: txHash,
nonce: toHex(0),
to: null,
value: "0x0",
});
},
});
},
});