-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Set PoV in frontier template #225
Closed
Closed
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
97f7224
Set PoV in frontier template
317bc1b
Set PoV in frontier template
28d6237
Remove TODO
77f87f6
Add pov test for frontier
9a72d9d
Fix formatting issues in tests
c335759
Fix ts lint and formatting issues
f377019
Fix evm-over-pov tests
11b732e
Set GasLimitPovSizeRatio to 4
6820150
Revert "Fix evm-over-pov tests"
7d530ff
Make pov tests pass
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity >=0.8.3; | ||
|
||
contract CallForwarder { | ||
function call( | ||
address target, | ||
bytes memory data | ||
) public returns (bool, bytes memory) { | ||
return target.call(data); | ||
} | ||
|
||
function callRange(address first, address last) public { | ||
require(first < last, "invalid range"); | ||
while (first < last) { | ||
first.call(""); | ||
first = address(uint160(first) + 1); | ||
} | ||
} | ||
|
||
function delegateCall( | ||
address target, | ||
bytes memory data | ||
) public returns (bool, bytes memory) { | ||
return target.delegatecall(data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { DevModeContext } from "@moonwall/cli"; | ||
import { ALITH_ADDRESS, alith } from "@moonwall/util"; | ||
|
||
export interface HeavyContract { | ||
deployed: boolean; | ||
account: string; | ||
key: string; | ||
} | ||
/** | ||
* @description Deploy multiple contracts to test the EVM storage limit. | ||
* @param context Context of the test | ||
* @param count Number of contracts to deploy | ||
* @returns | ||
*/ | ||
export const deployHeavyContracts = async (context: DevModeContext, first = 6000, last = 6999) => { | ||
// Generate the contract addresses | ||
const contracts = await Promise.all( | ||
new Array(last - first + 1).fill(0).map(async (_, i) => { | ||
const account = `0x${(i + first).toString(16).padStart(40, "0")}`; | ||
return { | ||
deployed: false, | ||
account, | ||
key: context.polkadotJs().query.evm.accountCodes.key(account), | ||
}; | ||
}) | ||
); | ||
|
||
// Check which contracts are already deployed | ||
for (const contract of contracts) { | ||
contract.deployed = (await context.polkadotJs().rpc.state.getStorage(contract.key))!.toString().length > 10; | ||
} | ||
|
||
// Create the contract code (24kb of zeros) | ||
const evmCode = `60006000fd${"0".repeat(24_000 * 2)}`; | ||
const storageData = `${context | ||
.polkadotJs() | ||
.registry.createType("Compact<u32>", `0x${BigInt((evmCode.length + 1) * 2).toString(16)}`) | ||
.toHex(true)}${evmCode}`; | ||
|
||
// Create the batchs of contracts to deploy | ||
const batchs = contracts | ||
.reduce( | ||
(acc, value) => { | ||
if (acc[acc.length - 1].length >= 30) acc.push([]); | ||
if (!value.deployed) acc[acc.length - 1].push([value.key, storageData]); | ||
return acc; | ||
}, | ||
[[]] as [string, string][][] | ||
) | ||
.filter((batch) => batch.length > 0); | ||
|
||
// Set the storage of the contracts | ||
let nonce = await context.viem().getTransactionCount({ address: ALITH_ADDRESS }); | ||
for (let i = 0; i < batchs.length; i++) { | ||
const batch = batchs[i]; | ||
await context.createBlock([ | ||
context.polkadotJs().tx.sudo.sudo(context.polkadotJs().tx.system.setStorage(batch)).signAsync(alith, { | ||
nonce: nonce++, | ||
}), | ||
]); | ||
} | ||
return contracts as HeavyContract[]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import "@moonbeam-network/api-augment"; | ||
import { DevModeContext, expect } from "@moonwall/cli"; | ||
import { EventRecord } from "@polkadot/types/interfaces"; | ||
import { | ||
EvmCoreErrorExitError, | ||
EvmCoreErrorExitFatal, | ||
EvmCoreErrorExitReason, | ||
EvmCoreErrorExitRevert, | ||
EvmCoreErrorExitSucceed, | ||
} from "@polkadot/types/lookup"; | ||
export type Errors = { | ||
Succeed: EvmCoreErrorExitSucceed["type"]; | ||
Error: EvmCoreErrorExitError["type"]; | ||
Revert: EvmCoreErrorExitRevert["type"]; | ||
Fatal: EvmCoreErrorExitFatal["type"]; | ||
}; | ||
|
||
export async function extractRevertReason(context: DevModeContext, responseHash: string) { | ||
const tx = (await context.ethers().provider!.getTransaction(responseHash))!; | ||
try { | ||
await context.ethers().call({ to: tx.to, data: tx.data, gasLimit: tx.gasLimit }); | ||
return null; | ||
} catch (e: any) { | ||
const errorMessage = e.info.error.message; | ||
return errorMessage.split("VM Exception while processing transaction: revert ")[1]; | ||
} | ||
} | ||
|
||
export function expectEVMResult<T extends Errors, Type extends keyof T>( | ||
events: EventRecord[], | ||
resultType: Type, | ||
reason?: T[Type] | ||
) { | ||
expect(events, `Missing events, probably failed execution`).to.be.length.at.least(1); | ||
const ethereumResult = events.find( | ||
({ event: { section, method } }) => section == "ethereum" && method == "Executed" | ||
)!.event.data[3] as EvmCoreErrorExitReason; | ||
|
||
const foundReason = ethereumResult.isError | ||
? ethereumResult.asError.type | ||
: ethereumResult.isFatal | ||
? ethereumResult.asFatal.type | ||
: ethereumResult.isRevert | ||
? ethereumResult.asRevert.type | ||
: ethereumResult.asSucceed.type; | ||
|
||
expect(ethereumResult.type, `Invalid EVM Execution - (${ethereumResult.type}.${foundReason})`).to.equal(resultType); | ||
if (reason) { | ||
if (ethereumResult.isError) { | ||
expect(ethereumResult.asError.type, `Invalid EVM Execution ${ethereumResult.type} Reason`).to.equal(reason); | ||
} else if (ethereumResult.isFatal) { | ||
expect(ethereumResult.asFatal.type, `Invalid EVM Execution ${ethereumResult.type} Reason`).to.equal(reason); | ||
} else if (ethereumResult.isRevert) { | ||
expect(ethereumResult.asRevert.type, `Invalid EVM Execution ${ethereumResult.type} Reason`).to.equal( | ||
reason | ||
); | ||
} else | ||
expect(ethereumResult.asSucceed.type, `Invalid EVM Execution ${ethereumResult.type} Reason`).to.equal( | ||
reason | ||
); | ||
} | ||
} | ||
|
||
export async function getTransactionFees(context: DevModeContext, hash: string): Promise<bigint> { | ||
const receipt = await context.viem().getTransactionReceipt({ hash: hash as `0x${string}` }); | ||
|
||
return receipt.gasUsed * receipt.effectiveGasPrice; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { BlockCreationResponse, expect } from "@moonwall/cli"; | ||
import type { EventRecord } from "@polkadot/types/interfaces"; | ||
import { ApiTypes, AugmentedEvent, AugmentedEvents, SubmittableExtrinsic } from "@polkadot/api/types"; | ||
import { IEvent } from "@polkadot/types/types"; | ||
|
||
export type ExtractTuple<P> = P extends AugmentedEvent<"rxjs", infer T> ? T : never; | ||
|
||
export async function expectOk< | ||
ApiType extends ApiTypes, | ||
Call extends SubmittableExtrinsic<ApiType> | Promise<SubmittableExtrinsic<ApiType>> | string | Promise<string>, | ||
Calls extends Call | Call[], | ||
BlockCreation extends BlockCreationResponse<ApiType, Calls extends Call[] ? Awaited<Call>[] : Awaited<Call>> | ||
>(call: Promise<BlockCreation>): Promise<BlockCreation> { | ||
const block = await call; | ||
if (Array.isArray(block.result)) { | ||
block.result.forEach((r, idx) => { | ||
expect( | ||
r.successful, | ||
`tx[${idx}] - ${r.error?.name}${ | ||
r.extrinsic | ||
? `\n\t\t${r.extrinsic.method.section}.${r.extrinsic.method.method}(${r.extrinsic.args | ||
.map((d) => d.toHuman()) | ||
.join("; ")})` | ||
: "" | ||
}` | ||
).to.be.true; | ||
}); | ||
} else { | ||
expect(block.result.successful, block.result.error?.name).to.be.true; | ||
} | ||
return block; | ||
} | ||
|
||
export function expectSubstrateEvent< | ||
ApiType extends ApiTypes, | ||
Call extends SubmittableExtrinsic<ApiType> | Promise<SubmittableExtrinsic<ApiType>> | string | Promise<string>, | ||
Calls extends Call | Call[], | ||
Event extends AugmentedEvents<ApiType>, | ||
Section extends keyof Event, | ||
Method extends keyof Event[Section], | ||
Tuple extends ExtractTuple<Event[Section][Method]> | ||
>( | ||
block: BlockCreationResponse<ApiType, Calls extends Call[] ? Awaited<Call>[] : Awaited<Call>>, | ||
section: Section, | ||
method: Method | ||
): IEvent<Tuple> { | ||
let event: EventRecord | undefined; | ||
if (Array.isArray(block.result)) { | ||
block.result.forEach((r) => { | ||
const foundEvents = r.events.filter( | ||
({ event }) => event.section.toString() == section && event.method.toString() == method | ||
); | ||
if (foundEvents.length > 0) { | ||
expect( | ||
event, | ||
`Event ${section.toString()}.${method.toString()} appeared multiple times` | ||
).toBeUndefined(); | ||
expect( | ||
foundEvents, | ||
`Event ${section.toString()}.${method.toString()} appeared multiple times` | ||
).to.be.length(1); | ||
event = foundEvents[0]; | ||
} | ||
}); | ||
} else { | ||
const foundEvents = block.result!.events!.filter( | ||
({ event }) => event.section.toString() == section && event.method.toString() == method | ||
); | ||
if (foundEvents.length > 0) { | ||
expect( | ||
foundEvents, | ||
`Event ${section.toString()}.${method.toString()} appeared multiple times` | ||
).to.be.length(1); | ||
event = foundEvents[0]; | ||
} | ||
} | ||
expect( | ||
event, | ||
`Event ${section.toString()}.${method.toString()} not found:\n${(Array.isArray(block.result) | ||
? block.result.map((r) => r.events).flat() | ||
: block.result | ||
? block.result.events | ||
: [] | ||
) | ||
.map(({ event }) => ` - ${event.section.toString()}.${event.method.toString()}\n`) | ||
.join("")}` | ||
).to.not.be.undefined; | ||
return event!.event as any; | ||
} |
109 changes: 109 additions & 0 deletions
109
test/suites/dev-frontier-template/test-pov/test-evm-over-pov.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import "@moonbeam-network/api-augment"; | ||
import { beforeAll, deployCreateCompiledContract, describeSuite, expect } from "@moonwall/cli"; | ||
import { ALITH_ADDRESS, createEthersTransaction } from "@moonwall/util"; | ||
import { Abi, encodeFunctionData } from "viem"; | ||
import { expectEVMResult } from "../../../helpers/eth-transactions.js"; | ||
import { HeavyContract, deployHeavyContracts } from "../../../helpers/contracts.js"; | ||
|
||
describeSuite({ | ||
id: "D2401", | ||
title: "PoV controlled by gasLimit", | ||
foundationMethods: "dev", | ||
testCases: ({ context, it, log }) => { | ||
let proxyAddress: `0x${string}`; | ||
let proxyAbi: Abi; | ||
let contracts: HeavyContract[]; | ||
let callData: `0x${string}`; | ||
const MAX_CONTRACTS = 20; | ||
const EXPECTED_POV_ROUGH = 50_000; // bytes | ||
|
||
beforeAll(async () => { | ||
const { contractAddress, abi } = await deployCreateCompiledContract(context, "CallForwarder"); | ||
proxyAddress = contractAddress; | ||
proxyAbi = abi; | ||
|
||
// Deploy heavy contracts (test won't use more than what is needed for reaching max pov) | ||
contracts = await deployHeavyContracts(context, 6000, 6000 + MAX_CONTRACTS); | ||
|
||
callData = encodeFunctionData({ | ||
abi: proxyAbi, | ||
functionName: "callRange", | ||
args: [contracts[0].account, contracts[MAX_CONTRACTS].account], | ||
}); | ||
}); | ||
|
||
it({ | ||
id: "T01", | ||
title: "should allow to include transaction with estimate gas to cover PoV", | ||
test: async function () { | ||
const gasEstimate = await context.viem().estimateGas({ | ||
account: ALITH_ADDRESS, | ||
to: proxyAddress, | ||
value: 0n, | ||
data: callData, | ||
}); | ||
|
||
const rawSigned = await createEthersTransaction(context, { | ||
to: proxyAddress, | ||
data: callData, | ||
txnType: "eip1559", | ||
gasLimit: gasEstimate, | ||
}); | ||
|
||
const { result, block } = await context.createBlock(rawSigned); | ||
|
||
log(`block.proofSize: ${block.proofSize} (successful: ${result?.successful})`); | ||
console.log(block); | ||
expect(block.proofSize).toBeGreaterThanOrEqual(EXPECTED_POV_ROUGH / 2.0); | ||
expect(block.proofSize).toBeLessThanOrEqual(EXPECTED_POV_ROUGH * 1.1); | ||
expect(result?.successful).to.equal(true); | ||
}, | ||
}); | ||
|
||
it({ | ||
id: "T02", | ||
title: "should allow to include transaction with enough gas limit to cover PoV", | ||
test: async function () { | ||
const rawSigned = await createEthersTransaction(context, { | ||
to: proxyAddress, | ||
data: callData, | ||
txnType: "eip1559", | ||
gasLimit: 3_000_000, | ||
}); | ||
|
||
const { result, block } = await context.createBlock(rawSigned); | ||
|
||
log(`block.proof_size: ${block.proofSize} (successful: ${result?.successful})`); | ||
expect(block.proofSize).to.be.at.least(EXPECTED_POV_ROUGH / 2.0); | ||
expect(block.proofSize).to.be.at.most(EXPECTED_POV_ROUGH * 1.1); | ||
expect(result?.successful).to.equal(true); | ||
}, | ||
}); | ||
|
||
it({ | ||
id: "T03", | ||
title: "should fail to include transaction without enough gas limit to cover PoV", | ||
test: async function () { | ||
// This execution uses only < 100k Gas in cpu execute but require 2M Gas for PoV. | ||
// We are providing only 1M Gas, so it should fail. | ||
const rawSigned = await createEthersTransaction(context, { | ||
to: proxyAddress, | ||
data: callData, | ||
txnType: "eip1559", | ||
gasLimit: 1_000_000, | ||
}); | ||
|
||
const { result, block } = await context.createBlock(rawSigned); | ||
|
||
log(`block.proof_size: ${block.proofSize} (successful: ${result?.successful})`); | ||
// The block still contain the failed (out of gas) transaction so the PoV is still included | ||
// in the block. | ||
// 1M Gas allows ~250k of PoV, so we verify we are within range. | ||
expect(block.proofSize).to.be.at.least(23_000); | ||
expect(block.proofSize).to.be.at.most(50_000); | ||
expect(result?.successful).to.equal(true); | ||
expectEVMResult(result!.events, "Error", "OutOfGas"); | ||
}, | ||
}); | ||
}, | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is specially suspicious, 1M gas should indeed account for 250K pov proof, and our tests clearly indicate they dont