diff --git a/evm/evm.nix b/evm/evm.nix index c1b4f912ad..7c732fdda2 100644 --- a/evm/evm.nix +++ b/evm/evm.nix @@ -466,9 +466,11 @@ _: { 'contracts/Multicall.sol' \ 'contracts/clients/Verifier.sol' \ 'contracts/apps/ucs/00-pingpong/*' \ + 'contracts/lib/*' \ 'contracts/core/OwnableIBCHandler.sol' \ 'contracts/core/24-host/IBCCommitment.sol' \ 'contracts/core/25-handler/IBCHandler.sol' \ + 'contracts/clients/ICS23MembershipVerifier.sol' \ 'tests/*' genhtml lcov.info.pruned -o $out --branch-coverage mv lcov.info.pruned $out/lcov.info @@ -519,7 +521,7 @@ _: { runtimeInputs = [ self'.packages.forge ]; text = '' ${ensureAtRepositoryRoot} - FOUNDRY_LIBS=["${evmLibs}"] FOUNDRY_PROFILE="test" FOUNDRY_TEST="evm/tests/src" forge test -vvvv --match-path evm/tests/src/05-app/Relay.t.sol --gas-report "$@" + FOUNDRY_LIBS=["${evmLibs}"] FOUNDRY_PROFILE="test" FOUNDRY_TEST="evm/tests/src" forge test -vvvv --match-path evm/tests/src/02-client/CosmosInCosmosClient.t.sol --gas-report "$@" ''; }; @@ -630,4 +632,4 @@ _: { }) networks ); }; -} +} \ No newline at end of file diff --git a/evm/tests/src/02-client/CometblsClient.t.sol b/evm/tests/src/02-client/CometblsClient.t.sol new file mode 100644 index 0000000000..2ea8cded5d --- /dev/null +++ b/evm/tests/src/02-client/CometblsClient.t.sol @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "../core/IBCHandler.sol"; +import "../core/Relay.sol"; +import "../../../contracts/clients/CometblsClient.sol"; +import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; +import "solady/utils/LibString.sol"; +import "@openzeppelin/token/ERC20/ERC20.sol"; +import "solidity-stringutils/strings.sol"; +import "solidity-bytes-utils/BytesLib.sol"; + +contract MockCometblsClient is CometblsClient { + bool private zkpVerificationResult = true; + + function setZKPVerificationResult(bool result) external { + zkpVerificationResult = result; + } + + function internalVerifyZKP( + bytes calldata zkpBytes, + bytes31 chainId, + bytes32 trustedValidatorsHash, + SignedHeader calldata header + ) internal view override returns (bool) { + // You can add additional logic to inspect the inputs if needed + return zkpVerificationResult; + } + +} +contract CometblsClientTest is Test { + MockCometblsClient cometblsClient; + address admin = address(0xABcD); + address ibcHandler = address(0x1234); + + function setUp() public { + // Deploy the MockCometblsClient implementation + MockCometblsClient implementation = new MockCometblsClient(); + + // Deploy the proxy and initialize it with the implementation + ERC1967Proxy proxy = new ERC1967Proxy( + address(implementation), + abi.encodeWithSelector( + CometblsClient.initialize.selector, + ibcHandler, + admin + ) + ); + + // Cast the proxy as the CometblsClient + cometblsClient = MockCometblsClient(address(proxy)); + } + + function test_initialize_ok() public { + // Verify the ibcHandler address + // assertEq(cometblsClient.ibcHandler(), ibcHandler); + + // Verify the admin address + assertEq(cometblsClient.owner(), admin); + } + + function test_createClient_success() public { + uint32 clientId = 1; + + // Encode the client state + ClientState memory clientState = ClientState({ + chainId: bytes31("test-chain"), + trustingPeriod: 86400, // 1 day in seconds + maxClockDrift: 300, // 5 minutes + frozenHeight: 0, + latestHeight: 100, + contractAddress: keccak256("test") + }); + bytes memory clientStateBytes = abi.encode(clientState); + + // Encode the consensus state + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app"), + nextValidatorsHash: keccak256("validators") + }); + bytes memory consensusStateBytes = abi.encode(consensusState); + + vm.prank(ibcHandler); // Simulate call from the IBC handler + cometblsClient.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Verify the client state was stored + bytes memory storedClientState = cometblsClient.getClientState(clientId); + assertEq(keccak256(storedClientState), keccak256(clientStateBytes), "Client state mismatch"); + + // Verify the consensus state was stored + bytes memory storedConsensusState = cometblsClient.getConsensusState(clientId, 100); + assertEq(keccak256(storedConsensusState), keccak256(consensusStateBytes), "Consensus state mismatch"); + } + + function misbehaviour_common(uint256 vm_warp, uint64 trustingPeriod) public { + uint32 clientId = 1; + + vm.warp(vm_warp); + + cometblsClient.setZKPVerificationResult(true); + // Mock client and consensus state + ClientState memory clientState = ClientState({ + chainId: bytes31("test-chain"), + trustingPeriod: trustingPeriod, + maxClockDrift: trustingPeriod, + frozenHeight: 0, + latestHeight: 99, + contractAddress: keccak256("test") + }); + bytes memory clientStateBytes = abi.encode(clientState); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app"), + nextValidatorsHash: keccak256("validatorsA") + }); + bytes memory consensusStateBytes = abi.encode(consensusState); + + vm.prank(ibcHandler); + cometblsClient.createClient(clientId, clientStateBytes, consensusStateBytes); + vm.stopPrank(); + clientState.latestHeight = 100; + clientStateBytes = abi.encode(clientState); + vm.prank(ibcHandler); + cometblsClient.createClient(clientId, clientStateBytes, consensusStateBytes); + vm.stopPrank(); + } + + + function test_misbehaviour_freezesClient() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + vm.prank(ibcHandler); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + + + // Verify the client is frozen + bytes memory storedClientState = cometblsClient.getClientState(clientId); + ClientState memory frozenState = abi.decode(storedClientState, (ClientState)); + assertEq(frozenState.frozenHeight, 1, "Client was not frozen"); + } + + + function test_misbehaviour_freezesClient_fraud() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp-1), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidMisbehaviour.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_fraud_different_hash() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidMisbehaviour.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_headers_seq() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 99, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp-1), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidMisbehaviourHeadersSequence.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + // function test_misbehaviour_freezesClient_ErrInvalidInitialConsensusState() public { + // vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidInitialConsensusState.selector)); + // misbehaviour_common(0); + // } + + function test_misbehaviour_freezesClient_ErrInvalidMisbehaviourHeadersSequence() public { + + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 99, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 200, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidMisbehaviourHeadersSequence.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_ErrUntrustedHeightLTETrustedHeight() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 99, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 98, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrUntrustedHeightLTETrustedHeight.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_ErrUntrustedTimestampLTETrustedTimestamp() public { + misbehaviour_common(1000000000000000, 0); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(900000), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrUntrustedTimestampLTETrustedTimestamp.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_ErrHeaderExpired() public { + misbehaviour_common(1000000000000000, 0); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(10000000), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrHeaderExpired.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + + function test_misbehaviour_freezesClient_ErrMaxClockDriftExceeded() public { + misbehaviour_common(1000000, 0); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrMaxClockDriftExceeded.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_ErrInvalidUntrustedValidatorsHash() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsB"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsB"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidUntrustedValidatorsHash.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + function test_misbehaviour_freezesClient_ErrInvalidZKP() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + // Mock headers for misbehavior + Header memory headerA = Header({ + signedHeader: SignedHeader({ + height: 101, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appA") + }), + trustedHeight: 100, + zeroKnowledgeProof: bytes("proofA") + }); + + Header memory headerB = Header({ + signedHeader: SignedHeader({ + height: 100, + secs: uint64(block.timestamp), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("validatorsB"), + appHash: keccak256("appB") + }), + trustedHeight: 99, + zeroKnowledgeProof: bytes("proofA") + }); + + cometblsClient.setZKPVerificationResult(false); + vm.prank(ibcHandler); + vm.expectRevert(abi.encodeWithSelector(CometblsClientLib.ErrInvalidZKP.selector)); + cometblsClient.misbehaviour(clientId, abi.encode(headerA, headerB)); + vm.stopPrank(); + } + + + + function encodeMemory( + ConsensusState memory consensusState + ) internal pure returns (bytes memory) { + return abi.encode( + consensusState.timestamp, + consensusState.appHash, + consensusState.nextValidatorsHash + ); + } + + function encodeMemory( + ClientState memory clientState + ) internal pure returns (bytes memory) { + return abi.encode( + clientState.chainId, + clientState.trustingPeriod, + clientState.maxClockDrift, + clientState.frozenHeight, + clientState.latestHeight, + clientState.contractAddress + ); + } + + + function commit( + ConsensusState memory consensusState + ) internal pure returns (bytes32) { + return keccak256(encodeMemory(consensusState)); + } + + + function commit( + ClientState memory clientState + ) public pure returns (bytes32) { + return keccak256(encodeMemory(clientState)); + } + + function test_updateClient_success() public { + misbehaviour_common(1000000, 8640000000000000000); + uint32 clientId = 1; + + ClientState memory clientState = ClientState({ + chainId: bytes31("test-chain"), + trustingPeriod: 8640000000000000000, + maxClockDrift: 8640000000000000000, + frozenHeight: 0, + latestHeight: 99, + contractAddress: keccak256("test") + }); + + bytes memory clientMessageBytes = abi.encode( + SignedHeader({ + height: 101, + secs: uint64(block.timestamp + 1), + nanos: 0, + validatorsHash: keccak256("validatorsA"), + nextValidatorsHash: keccak256("newValidators"), + appHash: keccak256("newApp") + }), + 100, + bytes("proof") + ); + + // Step 4: Update the client + vm.prank(ibcHandler); + ConsensusStateUpdate memory update = + cometblsClient.updateClient(clientId, clientMessageBytes); + + // Step 5: Verify the updates + // Ensure the latest height is updated + assertEq(cometblsClient.getLatestHeight(clientId), 101, "Latest height mismatch"); + + // Ensure the consensus state is updated + bytes memory storedConsensusState = cometblsClient.getConsensusState(clientId, 101); + ConsensusState memory updatedConsensusState = + CometblsClientLib.decodeConsensusStateMemory(storedConsensusState); + + assertEq(updatedConsensusState.timestamp, uint64((block.timestamp+1)*1e9), "Consensus state timestamp mismatch"); + assertEq(updatedConsensusState.appHash, keccak256("newApp"), "Consensus state appHash mismatch"); + assertEq(updatedConsensusState.nextValidatorsHash, keccak256("newValidators"), "Consensus state nextValidatorsHash mismatch"); + + // Ensure the commitments are correct + bytes32 expectedClientStateCommitment = commit(clientState); + bytes32 expectedConsensusStateCommitment = commit(updatedConsensusState); + + assertEq(update.consensusStateCommitment, expectedConsensusStateCommitment, "Consensus state commitment mismatch"); + assertEq(update.height, 100, "Height mismatch"); + } + + function verifyMembership( + bytes32 root, + bytes calldata proof, + bytes memory prefix, + bytes memory path, + bytes calldata value + ) public pure returns (bool) { + return true; + } + + +} diff --git a/evm/tests/src/02-client/CosmosInCosmosClient.t.sol b/evm/tests/src/02-client/CosmosInCosmosClient.t.sol new file mode 100644 index 0000000000..a19f6a2a0b --- /dev/null +++ b/evm/tests/src/02-client/CosmosInCosmosClient.t.sol @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "../core/IBCHandler.sol"; +import "../core/Relay.sol"; +import "../../../contracts/clients/CosmosInCosmosClient.sol"; +import "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; +import "solady/utils/LibString.sol"; +import "@openzeppelin/token/ERC20/ERC20.sol"; +import "solidity-stringutils/strings.sol"; +import "solidity-bytes-utils/BytesLib.sol"; + + +contract MockLightClient is ILightClient { + bool isFrozenVar = false; + bool globalVerifyMembership = true; + + function verifyMembership( + uint32 clientId, + uint64 height, + bytes calldata proof, + bytes calldata path, + bytes calldata value + ) public view override returns (bool) { + return globalVerifyMembership; + } + + function createClient( + uint32 clientId, + bytes calldata clientStateBytes, + bytes calldata consensusStateBytes + ) external returns (ConsensusStateUpdate memory update) { + return ConsensusStateUpdate({ + clientStateCommitment: bytes32(0), + consensusStateCommitment: bytes32(0), + height: 0 + }); + } + function getClientState( + uint32 clientId + ) external view returns (bytes memory) { + return abi.encodePacked("test"); + } + + function getTimestampAtHeight( + uint32 clientId, + uint64 height + ) external view returns (uint64) { + return 0; + } + + function getLatestHeight( + uint32 clientId + ) external view returns (uint64 height) { + return 0; + } + + function updateClient( + uint32 clientId, + bytes calldata clientMessageBytes + ) external returns (ConsensusStateUpdate memory update) { + return ConsensusStateUpdate({ + clientStateCommitment: bytes32(0), + consensusStateCommitment: bytes32(0), + height: 0 + }); + } + + function verifyNonMembership( + uint32 clientId, + uint64 height, + bytes calldata proof, + bytes calldata path + ) external pure override returns (bool) { + return true; + } + + function getConsensusState( + uint32 clientId, + uint64 height + ) external view returns (bytes memory) { + return abi.encodePacked(uint64(0), keccak256("app")); + } + + function isFrozen( + uint32 clientId + ) external view returns (bool) { + return isFrozenVar; + } + + function setIsFrozenReturn(bool isFrozen) public { + isFrozenVar = isFrozen; + } + + function setVerifyMembership(bool verify_membership) public { + globalVerifyMembership = verify_membership; + } + + function misbehaviour( + uint32 clientId, + bytes calldata clientMessageBytes + ) external { } +} + +contract MockIbcStore { + address public client; + function getClient(uint32 clientId) external view returns (ILightClient) { + return ILightClient(client); + } + function setClient(address _client) public { + client = _client; + } +} + +contract CosmosInCosmosClientTest is Test { + CosmosInCosmosClient client; + address admin = address(0xABcD); + address ibcHandler;// = address(0x1234); + MockIbcStore ibcStore; + MockLightClient lightClient; + + function setUp() public { + // Deploy and initialize the CosmosInCosmosClient contract + ibcStore = new MockIbcStore(); + ibcHandler = address(ibcStore); + CosmosInCosmosClient implementation = new CosmosInCosmosClient(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(implementation), + abi.encodeWithSelector( + CosmosInCosmosClient.initialize.selector, + ibcHandler, + admin + ) + ); + client = CosmosInCosmosClient(address(proxy)); + lightClient = new MockLightClient(); + ibcStore.setClient(address(lightClient)); + } + + function test_initialize_ok() public { + // Verify initialization + assertEq(client.owner(), admin); + } + + function test_createClient_success() public { + uint32 clientId = 1; + + // Encode the client state + + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + // Encode the consensus state + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(ibcHandler); // Simulate call from the IBC handler + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Verify client state + bytes memory storedClientState = client.getClientState(clientId); + assertEq(keccak256(storedClientState), keccak256(clientStateBytes), "Client state mismatch"); + uint64 latestHeight = client.getLatestHeight(clientId); + assertEq(latestHeight, clientState.latestHeight, "Latest height mismatch"); + + // Verify consensus state + bytes memory storedConsensusState = client.getConsensusState(clientId, 100); + assertEq(keccak256(storedConsensusState), keccak256(consensusStateBytes), "Consensus state mismatch"); + uint64 timestamp_at_height = client.getTimestampAtHeight(clientId, 100); + assertEq(timestamp_at_height, consensusState.timestamp, "Timestamp mismatch"); + } + + function test_createClient_ErrInvalidInitialConsensusState() public { + uint32 clientId = 1; + + // Encode the client state + + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 0 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + // Encode the consensus state + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(ibcHandler); // Simulate call from the IBC handler + vm.expectRevert(abi.encodeWithSelector(CosmosInCosmosLib.ErrInvalidInitialConsensusState.selector)); + + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + } + + + function test_updateClient_success() public { + uint32 clientId = 1; + + // Mock the initial client and consensus states + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(address(ibcHandler)); + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Prepare update client message + Header memory header = Header({ + l1Height: 101, + l2Height: 102, + l2InclusionProof: bytes("proof"), + l2ConsensusState: abi.encode(TendermintConsensusState({ + timestamp: uint64(block.timestamp + 1), + appHash: keccak256("newApp"), + nextValidatorsHash: keccak256("newValidators") + })) + }); + bytes memory clientMessageBytes = abi.encode( + header.l1Height, + header.l2Height, + header.l2InclusionProof, + header.l2ConsensusState + ); + + lightClient.setVerifyMembership(true); + // Update client + vm.prank(address(ibcHandler)); + ConsensusStateUpdate memory update = client.updateClient(clientId, clientMessageBytes); + + // // Verify updated consensus state + bytes memory updatedConsensusState = client.getConsensusState(clientId, 102); + + // Decode the updated consensus state + ConsensusState memory decodedConsensusState = abi.decode( + updatedConsensusState, + (ConsensusState) + ); + + assertEq(decodedConsensusState.timestamp, uint64(block.timestamp + 1), "Consensus state timestamp mismatch"); + assertEq(decodedConsensusState.appHash, keccak256("newApp"), "Consensus state appHash mismatch"); + } + + + function test_updateClient_revert_invalid_proof() public { + uint32 clientId = 1; + + // Mock the initial client and consensus states + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(address(ibcHandler)); + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Prepare update client message + Header memory header = Header({ + l1Height: 101, + l2Height: 102, + l2InclusionProof: bytes("proof"), + l2ConsensusState: abi.encode(TendermintConsensusState({ + timestamp: uint64(block.timestamp + 1), + appHash: keccak256("newApp"), + nextValidatorsHash: keccak256("newValidators") + })) + }); + bytes memory clientMessageBytes = abi.encode( + header.l1Height, + header.l2Height, + header.l2InclusionProof, + header.l2ConsensusState + ); + + lightClient.setVerifyMembership(false); + // Update client + vm.prank(address(ibcHandler)); + vm.expectRevert(abi.encodeWithSelector(CosmosInCosmosLib.ErrInvalidL1Proof.selector)); + client.updateClient(clientId, clientMessageBytes); + } + + function test_misbehavuour_error() public { + vm.prank(ibcHandler); // Simulate call from the IBC handler + vm.expectRevert(abi.encodeWithSelector(CosmosInCosmosLib.ErrInvalidMisbehaviour.selector)); + client.misbehaviour(1, bytes("")); + } + + function test_isFrozenImpl() public { + uint32 clientId = 1; + + // Mock the initial client and consensus states + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(address(ibcHandler)); + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Prepare update client message + Header memory header = Header({ + l1Height: 101, + l2Height: 102, + l2InclusionProof: bytes("proof"), + l2ConsensusState: abi.encode(TendermintConsensusState({ + timestamp: uint64(block.timestamp + 1), + appHash: keccak256("newApp"), + nextValidatorsHash: keccak256("newValidators") + })) + }); + bytes memory clientMessageBytes = abi.encode( + header.l1Height, + header.l2Height, + header.l2InclusionProof, + header.l2ConsensusState + ); + + // Update client + vm.prank(address(ibcHandler)); + lightClient.setIsFrozenReturn(true); + bool isFrozen = client.isFrozen(clientId); + assertTrue(isFrozen, "Client should be frozen"); + } + + function test_verifyMembershipIsFrozen() public { + uint32 clientId = 1; + + // Mock the initial client and consensus states + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(address(ibcHandler)); + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Prepare update client message + Header memory header = Header({ + l1Height: 101, + l2Height: 102, + l2InclusionProof: bytes("proof"), + l2ConsensusState: abi.encode(TendermintConsensusState({ + timestamp: uint64(block.timestamp + 1), + appHash: keccak256("newApp"), + nextValidatorsHash: keccak256("newValidators") + })) + }); + bytes memory clientMessageBytes = abi.encode( + header.l1Height, + header.l2Height, + header.l2InclusionProof, + header.l2ConsensusState + ); + + // Update client + lightClient.setIsFrozenReturn(true); + vm.prank(address(ibcHandler)); + vm.expectRevert(abi.encodeWithSelector(CosmosInCosmosLib.ErrClientFrozen.selector)); + client.verifyMembership(1, 1, bytes(""), bytes(""), bytes("")); + } + + + function test_verifyNonMembershipIsFrozen() public { + uint32 clientId = 1; + + // Mock the initial client and consensus states + ClientState memory clientState = ClientState({ + l2ChainId: "test-chain", + l1ClientId: 2, + l2ClientId: 3, + latestHeight: 100 + }); + bytes memory clientStateBytes = abi.encode( + clientState.l2ChainId, + clientState.l1ClientId, + clientState.l2ClientId, + clientState.latestHeight + ); + + ConsensusState memory consensusState = ConsensusState({ + timestamp: uint64(block.timestamp), + appHash: keccak256("app") + }); + bytes memory consensusStateBytes = abi.encode( + consensusState.timestamp, + consensusState.appHash + ); + + vm.prank(address(ibcHandler)); + client.createClient(clientId, clientStateBytes, consensusStateBytes); + + // Prepare update client message + Header memory header = Header({ + l1Height: 101, + l2Height: 102, + l2InclusionProof: bytes("proof"), + l2ConsensusState: abi.encode(TendermintConsensusState({ + timestamp: uint64(block.timestamp + 1), + appHash: keccak256("newApp"), + nextValidatorsHash: keccak256("newValidators") + })) + }); + bytes memory clientMessageBytes = abi.encode( + header.l1Height, + header.l2Height, + header.l2InclusionProof, + header.l2ConsensusState + ); + + // Update client + lightClient.setIsFrozenReturn(true); + vm.prank(address(ibcHandler)); + vm.expectRevert(abi.encodeWithSelector(CosmosInCosmosLib.ErrClientFrozen.selector)); + client.verifyNonMembership(1, 1, bytes(""), bytes("")); + } +}