Skip to content

Instantly share code, notes, and snippets.

@0xvangrim
Created August 28, 2025 06:06
Show Gist options
  • Save 0xvangrim/f3e8b0675bc48c29038e0ea080619e75 to your computer and use it in GitHub Desktop.
Save 0xvangrim/f3e8b0675bc48c29038e0ea080619e75 to your computer and use it in GitHub Desktop.
POC issue #611 for Malda
// SPDX-License-Identifier: BSL-1.1
pragma solidity =0.8.28;
// tests
import {mToken_Unit_Shared} from "../shared/mToken_Unit_Shared.t.sol";
import {ImTokenOperationTypes} from "src/interfaces/ImToken.sol";
contract mErc20Host_ProofEnvChainSpec_DoS is mToken_Unit_Shared {
function setUp() public virtual override {
super.setUp();
// Allow foreign chain (Ethereum mainnet = 1) as a valid source
mWethHost.updateAllowedChain(1, true);
}
function _buildJournal(
address sender,
address market,
uint256 accAmountIn,
uint256 accAmountOut,
uint32 srcChainId,
uint32 dstChainId,
bool l1Inclusion
) internal pure returns (bytes memory) {
// | Offset | Length | Data Type |
// | 0 | 20 | address sender |
// | 20 | 20 | address market |
// | 40 | 32 | uint256 accAmountIn |
// | 72 | 32 | uint256 accAmountOut |
// | 104 | 4 | uint32 chainId |
// | 108 | 4 | uint32 dstChainId |
// | 112 | 1 | bool L1inclusion |
bytes memory single = abi.encodePacked(
sender,
market,
accAmountIn,
accAmountOut,
srcChainId,
dstChainId,
l1Inclusion
);
bytes[] memory arr = new bytes[](1);
arr[0] = single;
return abi.encode(arr);
}
// 1) With a foreign chainId, a bad proof (e.g., due to wrong ChainSpec) reverts at verifier → DoS
function test_EthereumChainId_InvalidProof_Reverts_mintExternal()
external
whenMarketIsListed(address(mWethHost))
whenNotPaused(address(mWethHost), ImTokenOperationTypes.OperationType.Mint)
{
address user = address(this);
uint32 srcChainId = 1; // Ethereum mainnet
uint32 dstChainId = uint32(block.chainid); // Linea host
uint256 mintAmt = 1_000e18;
bytes memory journalData = _buildJournal(user, address(mWethHost), mintAmt, mintAmt, srcChainId, dstChainId, true);
uint256[] memory amounts = new uint256[](1);
amounts[0] = mintAmt;
uint256[] memory minOut = new uint256[](1);
minOut[0] = 0;
// Simulate invalid proof from backend (e.g., built with wrong ChainSpec)
verifierMock.setStatus(true);
// Expect revert inside verifier.verifyInput → host call DoS
vm.expectRevert();
mWethHost.mintExternal(journalData, bytes(""), amounts, minOut, user);
}
// 2) Same request with verifier accepting the proof succeeds and updates acc inPerChain
function test_EthereumChainId_ValidProof_Succeeds_mintExternal()
external
whenMarketIsListed(address(mWethHost))
whenNotPaused(address(mWethHost), ImTokenOperationTypes.OperationType.Mint)
{
address user = address(this);
uint32 srcChainId = 1; // Ethereum mainnet
uint32 dstChainId = uint32(block.chainid); // Linea host
uint256 mintAmt = 500e18;
// Before: no inflow recorded (indexed by source chain id)
(uint256 inBefore, uint256 outBefore) = mWethHost.getProofData(user, srcChainId);
assertEq(inBefore, 0);
assertEq(outBefore, 0);
bytes memory journalData = _buildJournal(user, address(mWethHost), mintAmt, mintAmt, srcChainId, dstChainId, true);
uint256[] memory amounts = new uint256[](1);
amounts[0] = mintAmt;
uint256[] memory minOut = new uint256[](1);
minOut[0] = 0;
// Simulate valid proof
verifierMock.setStatus(false);
mWethHost.mintExternal(journalData, bytes(""), amounts, minOut, user);
// After: inflow for srcChainId is recorded for user
(uint256 inAfter, uint256 outAfter) = mWethHost.getProofData(user, srcChainId);
assertEq(inAfter, mintAmt);
assertEq(outAfter, 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment