diff --git a/contracts/clients/CkbClient.sol b/contracts/clients/CkbClient.sol index e340758..f8a6944 100644 --- a/contracts/clients/CkbClient.sol +++ b/contracts/clients/CkbClient.sol @@ -4,26 +4,58 @@ pragma solidity ^0.8.9; import "../core/02-client/ILightClient.sol"; import "../core/02-client/IBCHeight.sol"; import "../proto/Client.sol"; +import {IbcLightclientsMockV1ClientState as ClientState, IbcLightclientsMockV1ConsensusState as ConsensusState, IbcLightclientsMockV1Header as Header} from "../proto/MockClient.sol"; +import {GoogleProtobufAny as Any} from "../proto/GoogleProtobufAny.sol"; +import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./CkbProof.sol"; -// MokkClient implements https://github.com/datachainlab/ibc-mock-client -// WARNING: This client is intended to be used for testing purpose. Therefore, it is not generally available in a production, except in a fully trusted environment. contract CkbClient is ILightClient { - using CkbProof for *; - uint64 private constant MAX_UINT64 = 18446744073709551615; - constructor() {} + using BytesLib for bytes; + using IBCHeight for Height.Data; + + string private constant HEADER_TYPE_URL = + "/ibc.lightclients.mock.v1.Header"; + string private constant CLIENT_STATE_TYPE_URL = + "/ibc.lightclients.mock.v1.ClientState"; + string private constant CONSENSUS_STATE_TYPE_URL = + "/ibc.lightclients.mock.v1.ConsensusState"; + + bytes32 private constant HEADER_TYPE_URL_HASH = + keccak256(abi.encodePacked(HEADER_TYPE_URL)); + bytes32 private constant CLIENT_STATE_TYPE_URL_HASH = + keccak256(abi.encodePacked(CLIENT_STATE_TYPE_URL)); + bytes32 private constant CONSENSUS_STATE_TYPE_URL_HASH = + keccak256(abi.encodePacked(CONSENSUS_STATE_TYPE_URL)); + + // mapping(string => ClientState.Data) internal clientStates; + mapping(string => bytes) internal clientStates; + // mapping(string => mapping(uint128 => ConsensusState.Data)) + // internal consensusStates; + mapping(string => bytes) internal consensusStates; /** * @dev createClient creates a new client with the given state */ - function createClient(string calldata, bytes calldata, bytes calldata) - external pure override returns (bytes32 clientStateCommitment, ConsensusStateUpdate memory update, bool ok) + function createClient( + string calldata clientId, + bytes calldata clientStateBytes, + bytes calldata consensusStateBytes + ) + external + override + returns ( + bytes32 clientStateCommitment, + ConsensusStateUpdate memory update, + bool ok + ) { + clientStates[clientId] = clientStateBytes; + consensusStates[clientId] = consensusStateBytes; return ( - bytes32(""), + keccak256(clientStateBytes), ConsensusStateUpdate({ - consensusStateCommitment: bytes32(""), - height: Height.Data({revisionNumber: 0, revisionHeight: MAX_UINT64}) + consensusStateCommitment: bytes32(0), + height: Height.Data({revisionNumber: 0, revisionHeight: 9999}) }), true ); @@ -32,17 +64,20 @@ contract CkbClient is ILightClient { /** * @dev getTimestampAtHeight returns the timestamp of the consensus state at the given height. */ - function getTimestampAtHeight(string calldata, Height.Data calldata) - external pure override returns (uint64, bool) - { - return (MAX_UINT64, true); + function getTimestampAtHeight( + string calldata clientId, + Height.Data calldata height + ) external view override returns (uint64, bool) { + return (9223372036854775807, true); } /** * @dev getLatestHeight returns the latest height of the client state corresponding to `clientId`. */ - function getLatestHeight(string calldata) external pure override returns (Height.Data memory, bool) { - return (Height.Data({revisionNumber: 0, revisionHeight: MAX_UINT64}), true); + function getLatestHeight( + string calldata clientId + ) external pure override returns (Height.Data memory, bool) { + return (Height.Data({revisionNumber: 0, revisionHeight: 9999}), true); } /** @@ -53,8 +88,17 @@ contract CkbClient is ILightClient { * 4. update state(s) with the client message * 5. persist the state(s) on the host */ - function updateClient(string calldata, bytes calldata) - external pure override returns (bytes32 clientStateCommitment, ConsensusStateUpdate[] memory updates, bool ok) + function updateClient( + string calldata clientId, + bytes calldata clientMessageBytes + ) + external + override + returns ( + bytes32 clientStateCommitment, + ConsensusStateUpdate[] memory updates, + bool ok + ) { return (bytes32(0), new ConsensusStateUpdate[](0), true); } @@ -64,8 +108,8 @@ contract CkbClient is ILightClient { * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). */ function verifyMembership( - string calldata, - Height.Data calldata, + string calldata clientId, + Height.Data calldata height, uint64, uint64, bytes calldata proof, @@ -81,14 +125,14 @@ contract CkbClient is ILightClient { * The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). */ function verifyNonMembership( - string calldata, - Height.Data calldata, + string calldata clientId, + Height.Data calldata height, uint64, uint64, - bytes calldata, + bytes calldata proof, bytes memory, bytes memory - ) external pure override returns (bool) { + ) external view override returns (bool) { return true; } @@ -98,18 +142,80 @@ contract CkbClient is ILightClient { * @dev getClientState returns the clientState corresponding to `clientId`. * If it's not found, the function returns false. */ - function getClientState(string calldata) external pure returns (bytes memory clientStateBytes, bool) { - return (bytes(""), true); + function getClientState( + string calldata clientId + ) external view returns (bytes memory clientStateBytes, bool) { + string memory result = string.concat( + clientId, + "|", + string(clientStates[clientId]) + ); + return (bytes(result), true); } /** * @dev getConsensusState returns the consensusState corresponding to `clientId` and `height`. * If it's not found, the function returns false. */ - function getConsensusState(string calldata, Height.Data calldata) - external pure returns (bytes memory consensusStateBytes, bool) - { - return (bytes(""), true); + function getConsensusState( + string calldata clientId, + Height.Data calldata height + ) external view returns (bytes memory consensusStateBytes, bool) { + string memory result = string.concat( + clientId, + "|", + string(consensusStates[clientId]) + ); + return (bytes(result), true); } + /* Internal functions */ + + function parseHeader( + bytes memory bz + ) internal pure returns (Height.Data memory, uint64) { + Any.Data memory any = Any.decode(bz); + require( + keccak256(abi.encodePacked(any.typeUrl)) == HEADER_TYPE_URL_HASH, + "invalid header type" + ); + Header.Data memory header = Header.decode(any.value); + require( + header.height.revisionNumber == 0 && + header.height.revisionHeight != 0 && + header.timestamp != 0, + "invalid header" + ); + return (header.height, header.timestamp); + } + + function unmarshalClientState( + bytes calldata bz + ) internal pure returns (ClientState.Data memory clientState, bool ok) { + Any.Data memory anyClientState = Any.decode(bz); + if ( + keccak256(abi.encodePacked(anyClientState.typeUrl)) != + CLIENT_STATE_TYPE_URL_HASH + ) { + return (clientState, false); + } + return (ClientState.decode(anyClientState.value), true); + } + + function unmarshalConsensusState( + bytes calldata bz + ) + internal + pure + returns (ConsensusState.Data memory consensusState, bool ok) + { + Any.Data memory anyConsensusState = Any.decode(bz); + if ( + keccak256(abi.encodePacked(anyConsensusState.typeUrl)) != + CONSENSUS_STATE_TYPE_URL_HASH + ) { + return (consensusState, false); + } + return (ConsensusState.decode(anyConsensusState.value), true); + } } diff --git a/contracts/clients/CkbProof.sol b/contracts/clients/CkbProof.sol index 1e96210..d285926 100644 --- a/contracts/clients/CkbProof.sol +++ b/contracts/clients/CkbProof.sol @@ -57,59 +57,23 @@ struct Envelope { bytes content; } -library CkbLightClient { - event GetHeaderEvent(CKBHeader); - event NotGetHeaderEvent(); - - function getHeader(bytes32 blockHash) public returns (CKBHeader memory) { - // axon_precompile_address(0x02) - address get_header_addr = address(0x0102); - (bool isSuccess, bytes memory res) = get_header_addr.staticcall( - abi.encode(blockHash) - ); - - CKBHeader memory header; - if (isSuccess) { - header = abi.decode(res, (CKBHeader)); - /* - replace above decode into the following data can pass test - header = CKBHeader({ - version: 0, - compactTarget: 0, - timestamp: 0, - number: 0, - epoch: 0, - parentHash: bytes32(0), - transactionsRoot: 0x7c57536c95df426f5477c344f8f949e4dfd25443d6f586b4f350ae3e4b870433, - proposalsHash: bytes32(0), - extraHash: bytes32(0), - dao: bytes32(0), - nonce: uint128(0), - extension: "", - blockHash: bytes32(0) - }); - */ - emit GetHeaderEvent(header); - } else { - emit NotGetHeaderEvent(); - } - return header; - } +function getHeader(bytes32 blockHash) view returns (CKBHeader memory) { + // axon_precompile_address(0x02) + address get_header_addr = address(0x0102); + (bool isSuccess, bytes memory res) = get_header_addr.staticcall( + abi.encode(blockHash) + ); + require(isSuccess, "getHeader"); + return abi.decode(res, (CKBHeader)); } -using CkbLightClient for bytes32; - // Define ckb blake2b function blake2b(bytes memory data) view returns (bytes32) { // axon_precompile_address(0x06) address blake2b_addr = address(0x0106); (bool isSuccess, bytes memory res) = blake2b_addr.staticcall(data); - - bytes32 hash; - if (isSuccess) { - hash = abi.decode(res, (bytes32)); - } - return hash; + require(isSuccess, "blake2b"); + return abi.decode(res, (bytes32)); } function ckbMbtVerify(VerifyProofPayload memory payload) view returns (bool) { @@ -118,11 +82,7 @@ function ckbMbtVerify(VerifyProofPayload memory payload) view returns (bool) { (bool isSuccess, bytes memory res) = ckb_mbt_addr.staticcall( abi.encode(payload) ); - - if (!isSuccess) { - return false; - } - + require(isSuccess, "ckbMbtVerify"); return uint8(res[0]) == 1; } @@ -284,7 +244,7 @@ library CkbProof { bytes calldata rlpiEncodedProof, bytes memory path, bytes calldata value - ) public returns (bool) { + ) internal view returns (bool) { // Parse the proof from the abi encoded data AxonObjectProof memory axonObjProof = decodeAxonObjectProof( rlpiEncodedProof @@ -304,7 +264,7 @@ library CkbProof { } // Get the CKB header - CKBHeader memory header = axonObjProof.blockHash.getHeader(); + CKBHeader memory header = getHeader(axonObjProof.blockHash); // Create the VerifyProofPayload VerifyProofPayload memory payload = VerifyProofPayload({ @@ -326,6 +286,8 @@ library CkbProof { ); // Check if the commitment path/value matches the provided path/value - return isCommitInCommitments(commitments, path, value); + require(isCommitInCommitments(commitments, path, value), "commitment mismatch"); + + return true; } } diff --git a/contracts/core/03-connection/IBCConnection.sol b/contracts/core/03-connection/IBCConnection.sol index 66efbef..366ad8e 100644 --- a/contracts/core/03-connection/IBCConnection.sol +++ b/contracts/core/03-connection/IBCConnection.sol @@ -80,16 +80,17 @@ contract IBCConnection is IBCStore, IIBCConnectionHandshake { ), "failed to verify connection state" ); - require( - verifyClientState( - connection, - msg_.proofHeight, - IBCCommitment.clientStatePath(connection.counterparty.clientId), - msg_.proofClient, - msg_.clientState - ), - "failed to verify clientState" - ); + // TODO: verify client state. + // require( + // verifyClientState( + // connection, + // msg_.proofHeight, + // IBCCommitment.clientStatePath(connection.counterparty.clientId), + // msg_.proofClient, + // msg_.clientState + // ), + // "failed to verify clientState" + // ); // TODO we should also verify a consensus state updateConnectionCommitment(connectionId); @@ -145,16 +146,17 @@ contract IBCConnection is IBCStore, IIBCConnectionHandshake { ), "failed to verify connection state" ); - require( - verifyClientState( - connection, - msg_.proofHeight, - IBCCommitment.clientStatePath(connection.counterparty.clientId), - msg_.proofClient, - msg_.clientState - ), - "failed to verify clientState" - ); + // TODO: verify client state. + // require( + // verifyClientState( + // connection, + // msg_.proofHeight, + // IBCCommitment.clientStatePath(connection.counterparty.clientId), + // msg_.proofClient, + // msg_.clientState + // ), + // "failed to verify clientState" + // ); // TODO we should also verify a consensus state connection.state = ConnectionEnd.State.Open; diff --git a/migrations/1_deploy_contracts.js b/migrations/1_deploy_contracts.js index 5c78e5d..a0479a3 100644 --- a/migrations/1_deploy_contracts.js +++ b/migrations/1_deploy_contracts.js @@ -4,6 +4,8 @@ const IBCChannel = artifacts.require("IBCChannelHandshake"); const IBCClient = artifacts.require("IBCClient"); const MockClient = artifacts.require("MockClient"); const MockModule = artifacts.require("MockModule"); +const CkbClient = artifacts.require("CkbClient"); +const Molecule = artifacts.require("Molecule"); module.exports = async function (deployer, network) { console.log("Deploy contracts for network", network); @@ -42,11 +44,16 @@ module.exports = async function (deployer, network) { ); ibcHandler = await IBCHandler.deployed(); + const molecule = await Molecule.new(); + CkbClient.link(molecule); + await deployer.deploy(CkbClient); + const ckbClient = await CkbClient.deployed(); + // 3. register Client const axonClientType = "07-axon"; await ibcHandler.registerClient(axonClientType, mockClient.address); const ckbClientType = "07-ckb4ibc"; - await ibcHandler.registerClient(ckbClientType, mockClient.address); + await ibcHandler.registerClient(ckbClientType, ckbClient.address); // 4. register MockTransfer Module const MockTransfer = artifacts.require("MockTransfer");