Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
15 changes: 13 additions & 2 deletions .github/workflows/coverage-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,24 @@ jobs:

- run: npm ci
- run: npm run coverage:sol

- uses: hrishikesh-kadam/setup-lcov@v1

- name: Filter LCOV
run: |
lcov --remove lcov.info \
'test/*' \
'script/*' \
'src/proto/**' \
'src/core/PacketHandler.sol' \
'src/core/ContractRegistry.sol' \
Comment on lines +32 to +36
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exclude because coverage measurement is not required

-o lcov.filtered.info

- name: Report code coverage
uses: zgosalvez/github-actions-report-lcov@v5
with:
coverage-files: lcov.info
minimum-coverage: 0 # TODO: Once new tests are added, increase minimum-coverage from 0 to a realistic gate (e.g., 5–10%), and keep bumping it as coverage improves.
coverage-files: lcov.filtered.info
minimum-coverage: 100
artifact-name: code-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
update-comment: true
106 changes: 68 additions & 38 deletions script/DeployAll.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import {ILightClient} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/02
contract DeployAll is Script, Config {
Copy link
Contributor Author

@YukiTsuchida YukiTsuchida Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split the function because solhint complained about function-max-lines (I wonder why it didn't complain before? I don't know why).

// === Deployment artifacts (written back to deployments.toml) ===
IBCHandler public ibcHandler;

MockCrossContract public mockApp;
CrossSimpleModule public crossSimpleModule;
MockClient public mockClient;
Expand Down Expand Up @@ -92,65 +91,96 @@ contract DeployAll is Script, Config {
console2.log(" Initialized. port=%s, clientType=%s", portCross, mockClientType);
}

// ---------- entry ----------
function run() external {
// 1) Load config with write-back enabled (stores results after deployment)
_loadConfig(
"./deployments.toml",
/*writeBack=*/
true
);
function _readConfig()
internal
returns (
string memory mnemonic,
uint32 mnemonicIndex,
bool debugMode,
string memory portCross,
string memory mockClientType
)
{
string memory m = config.get("mnemonic").toString();
uint256 idxU256 = config.get("mnemonic_index").toUint256();
require(idxU256 < 2 ** 32, "mnemonic_index too large");
uint32 idx = uint32(idxU256);
bool dbg = config.get("debug_mode").toBool();
string memory port = config.get("port_cross").toString();
string memory cli = config.get("mock_client_type").toString();
return (m, idx, dbg, port, cli);
}

uint256 chainId = block.chainid;
function _logConfig(
uint256 chainId,
bool debugMode,
string memory portCross,
string memory mockClientType,
uint32 mnemonicIndex
) internal {
console2.log("Deploying to chain:", chainId);

// 2) Read configuration values (resolved for the current chain)
// - Required:
// string: mnemonic
// uint: mnemonic_index
// bool: debug_mode
// string: port_cross
// string: mock_client_type
string memory mnemonic = config.get("mnemonic").toString();
uint256 mnemonicIndexU256 = config.get("mnemonic_index").toUint256();
// solhint-disable-next-line gas-strict-inequalities
require(mnemonicIndexU256 <= type(uint32).max, "mnemonic_index too large");
uint32 mnemonicIndex = uint32(mnemonicIndexU256);

bool debugMode = config.get("debug_mode").toBool();
string memory portCross = config.get("port_cross").toString();
string memory mockClientType = config.get("mock_client_type").toString();

console2.log("Config:");
console2.log(" debug_mode :", debugMode);
console2.log(" port_cross :", portCross);
console2.log(" mock_client_type:", mockClientType);
console2.log(" mnemonic_index :", mnemonicIndex);
}

// 3) Derive deployer private key from mnemonic + index (Foundry cheatcode)
uint256 deployerPk = vm.deriveKey(mnemonic, mnemonicIndex);
address deployer = vm.addr(deployerPk);
console2.log("Deployer:", deployer);
function _deriveDeployer(string memory mnemonic, uint32 mnemonicIndex)
internal
returns (uint256 deployerPk, address deployer)
{
uint256 pk = vm.deriveKey(mnemonic, mnemonicIndex);
address addr = vm.addr(pk);
console2.log("Deployer:", addr);
return (pk, addr);
}

// 4) Deploy + Initialize (single broadcast session)
function _broadcastDeployAndInit(
uint256 deployerPk,
bool debugMode,
string memory portCross,
string memory mockClientType
) internal {
vm.startBroadcast(deployerPk);

ibcHandler = _deployCore();
(mockApp, crossSimpleModule, mockClient) = _deployApp(ibcHandler, debugMode);
_initialize(ibcHandler, crossSimpleModule, portCross, mockClientType, mockClient);

vm.stopBroadcast();
}

// 5) Write back: save addresses & metadata to deployments.toml
// (addresses go under <chain>.address.*, meta under <chain>.meta.*)
function _writeBack(address deployer) internal {
// save addresses & metadata to deployments.toml
// (addresses go under <chain>.address.*, meta under <chain>.meta.*)
config.set("ibc_handler", address(ibcHandler));
config.set("mock_cross_contract", address(mockApp));
config.set("cross_simple_module", address(crossSimpleModule));
config.set("mock_client", address(mockClient));

// Meta
config.set("deployer", deployer);

console2.log("\nDeployment complete! Addresses saved to deployments.toml");
}

// ---------- entry ----------
function run() external {
_loadConfig("./deployments.toml", true);

uint256 chainId = block.chainid;
(
string memory mnemonic,
uint32 mnemonicIndex,
bool debugMode,
string memory portCross,
string memory mockClientType
) = _readConfig();

_logConfig(chainId, debugMode, portCross, mockClientType, mnemonicIndex);

(uint256 deployerPk, address deployer) = _deriveDeployer(mnemonic, mnemonicIndex);

_broadcastDeployAndInit(deployerPk, debugMode, portCross, mockClientType);

_writeBack(deployer);
}
}
198 changes: 198 additions & 0 deletions test/CrossModule.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: Apache-2.0
// solhint-disable one-contract-per-file, func-name-mixedcase
pragma solidity ^0.8.20;

import "forge-std/src/Test.sol";

import "../src/core/CrossModule.sol";
import {
IIBCModule,
IIBCModuleInitializer
} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/26-router/IIBCModule.sol";
import {IIBCHandler} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/25-handler/IIBCHandler.sol";
import {Packet} from "@hyperledger-labs/yui-ibc-solidity/contracts/core/04-channel/IIBCChannel.sol";

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";

contract DummyHandler {}

contract TestableCrossModule is CrossModule {
uint256 public recvCount;
uint256 public ackCount;
uint256 public timeoutCount;
bytes public lastAckArg;

constructor(IIBCHandler h) CrossModule(h) {}

function handlePacket(
Packet memory /*packet*/
)
internal
virtual
override
returns (bytes memory)
{
++recvCount;
return bytes("ack-ok");
}

function handleAcknowledgement(
Packet memory,
/*packet*/
bytes memory acknowledgement
)
internal
virtual
override
{
++ackCount;
lastAckArg = acknowledgement;
}

function handleTimeout(
Packet calldata /*packet*/
)
internal
virtual
override
{
++timeoutCount;
}
}

contract CrossModuleTest is Test {
DummyHandler private handler;
TestableCrossModule private mod;
Packet internal _emptyPacket;

function setUp() public {
handler = new DummyHandler();
mod = new TestableCrossModule(IIBCHandler(address(handler)));
}

function test_Constructor_GrantsIbcRoleToHandler() public {
assertTrue(mod.hasRole(mod.IBC_ROLE(), address(handler)));
}

function test_SupportsInterface_IIBC_IAccessControl_And_Unsupported() public view {
assertTrue(mod.supportsInterface(type(IIBCModule).interfaceId));
assertTrue(mod.supportsInterface(type(IIBCModuleInitializer).interfaceId));
assertTrue(mod.supportsInterface(type(IAccessControl).interfaceId));
assertFalse(mod.supportsInterface(0xDEADBEEF));
}

function test_onRecvPacket_Reverts_WithoutIbcRole() public {
vm.expectRevert();
mod.onRecvPacket(_emptyPacket, address(0));
}

function test_onRecvPacket_CallsHandlerAndReturnsAck_WhenCallerHasRole() public {
vm.prank(address(handler));
bytes memory ack = mod.onRecvPacket(_emptyPacket, address(0));
assertEq(ack, bytes("ack-ok"));
assertEq(mod.recvCount(), 1);
}

function test_onAcknowledgementPacket_Reverts_WithoutIbcRole() public {
vm.expectRevert();
mod.onAcknowledgementPacket(_emptyPacket, bytes("ack"), address(0));
}

function test_onAcknowledgementPacket_CallsHandler_WhenCallerHasRole() public {
vm.prank(address(handler));
mod.onAcknowledgementPacket(_emptyPacket, bytes("ack123"), address(0));
assertEq(mod.ackCount(), 1);
assertEq(mod.lastAckArg(), bytes("ack123"));
}

function test_onTimeoutPacket_Reverts_WithoutIbcRole() public {
vm.expectRevert();
mod.onTimeoutPacket(_emptyPacket, address(0));
}

function test_onTimeoutPacket_CallsHandler_WhenCallerHasRole() public {
vm.prank(address(handler));
mod.onTimeoutPacket(_emptyPacket, address(0));
assertEq(mod.timeoutCount(), 1);
}

function test_onChanOpenInit_Reverts_WithoutIbcRole() public {
IIBCModuleInitializer.MsgOnChanOpenInit memory m;
m.version = "v1";
vm.expectRevert();
mod.onChanOpenInit(m);
}

function test_onChanOpenInit_ReturnsSelfAndVersion_WhenCallerHasRole() public {
IIBCModuleInitializer.MsgOnChanOpenInit memory m;
m.version = "v1";
vm.prank(address(handler));
(address moduleAddr, string memory version) = mod.onChanOpenInit(m);
assertEq(moduleAddr, address(mod));
assertEq(version, "v1");
}

function test_onChanOpenTry_Reverts_WithoutIbcRole() public {
IIBCModuleInitializer.MsgOnChanOpenTry memory m;
m.counterpartyVersion = "cp-v1";
vm.expectRevert();
mod.onChanOpenTry(m);
}

function test_onChanOpenTry_ReturnsSelfAndCounterpartyVersion_WhenCallerHasRole() public {
IIBCModuleInitializer.MsgOnChanOpenTry memory m;
m.counterpartyVersion = "cp-v1";
vm.prank(address(handler));
(address moduleAddr, string memory version) = mod.onChanOpenTry(m);
assertEq(moduleAddr, address(mod));
assertEq(version, "cp-v1");
}

function test_onChanOpenAck_Reverts_WithoutIbcRole() public {
IIBCModule.MsgOnChanOpenAck memory m;
vm.expectRevert();
mod.onChanOpenAck(m);
}

function test_onChanOpenAck_Succeeds_WhenCallerHasRole() public {
IIBCModule.MsgOnChanOpenAck memory m;
vm.prank(address(handler));
mod.onChanOpenAck(m); // no revert
}

function test_onChanOpenConfirm_Reverts_WithoutIbcRole() public {
IIBCModule.MsgOnChanOpenConfirm memory m;
vm.expectRevert();
mod.onChanOpenConfirm(m);
}

function test_onChanOpenConfirm_Succeeds_WhenCallerHasRole() public {
IIBCModule.MsgOnChanOpenConfirm memory m;
vm.prank(address(handler));
mod.onChanOpenConfirm(m); // no revert
}

function test_onChanCloseInit_Reverts_WithoutIbcRole() public {
IIBCModule.MsgOnChanCloseInit memory m;
vm.expectRevert();
mod.onChanCloseInit(m);
}

function test_onChanCloseInit_Succeeds_WhenCallerHasRole() public {
IIBCModule.MsgOnChanCloseInit memory m;
vm.prank(address(handler));
mod.onChanCloseInit(m); // no revert
}

function test_onChanCloseConfirm_Reverts_WithoutIbcRole() public {
IIBCModule.MsgOnChanCloseConfirm memory m;
vm.expectRevert();
mod.onChanCloseConfirm(m);
}

function test_onChanCloseConfirm_Succeeds_WhenCallerHasRole() public {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could you explain the naming convention for test cases? I don't know how snake-case and camel-case are being used. Typically, the following conventions are used.
https://getfoundry.sh/guides/best-practices/writing-tests#organizing-and-naming-tests

Copy link
Contributor Author

@YukiTsuchida YukiTsuchida Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sticking to Foundry’s vibe: test_<Function>_<Description>.
Underscores only split segments; inside a segment we use UpperCamel, e.g. ReturnsSelfAndVersionWhenCallerHasRole.
Revert cases are tagged with RevertIf/When/On_….

fix: 51a84ae

IIBCModule.MsgOnChanCloseConfirm memory m;
vm.prank(address(handler));
mod.onChanCloseConfirm(m); // no revert
}
}
Loading