Skip to content

Commit

Permalink
feat(evm): introduce EvmInCosmosClient state lens
Browse files Browse the repository at this point in the history
  • Loading branch information
hussein-aitlahcen committed Dec 12, 2024
1 parent 841b1d7 commit 253680f
Show file tree
Hide file tree
Showing 5 changed files with 1,176 additions and 1 deletion.
296 changes: 296 additions & 0 deletions evm/contracts/clients/EvmInCosmosClient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
pragma solidity ^0.8.27;

import "@openzeppelin-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin-upgradeable/utils/PausableUpgradeable.sol";
import "solidity-bytes-utils/BytesLib.sol";

import "../core/02-client/ILightClient.sol";
import "../core/24-host/IBCStore.sol";
import "../core/24-host/IBCCommitment.sol";
import "../lib/ICS23.sol";
import "../lib/Common.sol";
import "../lib/MPTVerifier.sol";

struct Header {
uint64 l1Height;
uint64 l2Height;
bytes l2InclusionProof;
bytes l2ConsensusState;
}

struct ClientState {
uint32 l1ClientId;
uint32 l2ChainId;
uint32 l2ClientId;
uint64 latestHeight;
}

struct ConsensusState {
uint64 timestamp;
bytes32 stateRoot;
bytes32 storageRoot;
}

library EvmInCosmosLib {
uint256 public constant EVM_IBC_COMMITMENT_SLOT = 0;

error ErrNotIBC();
error ErrTrustedConsensusStateNotFound();
error ErrClientFrozen();
error ErrInvalidL1Proof();
error ErrInvalidInitialConsensusState();
error ErrInvalidMisbehaviour();

function encode(
ConsensusState memory consensusState
) internal pure returns (bytes memory) {
return abi.encode(consensusState);
}

function encode(
ClientState memory clientState
) internal pure returns (bytes memory) {
return abi.encode(clientState);
}

function commit(
ConsensusState memory consensusState
) internal pure returns (bytes32) {
return keccak256(encode(consensusState));
}

function commit(
ClientState memory clientState
) internal pure returns (bytes32) {
return keccak256(encode(clientState));
}
}

contract EvmInCosmosClient is
ILightClient,
Initializable,
UUPSUpgradeable,
OwnableUpgradeable,
PausableUpgradeable
{
using EvmInCosmosLib for *;

address private ibcHandler;

mapping(uint32 => ClientState) private clientStates;
mapping(uint32 => mapping(uint64 => ConsensusState)) private consensusStates;
mapping(uint32 => mapping(uint64 => ProcessedMoment)) private
processedMoments;

constructor() {
_disableInitializers();
}

function initialize(
address _ibcHandler,
address admin
) public initializer {
__Ownable_init(admin);
ibcHandler = _ibcHandler;
}

function createClient(
uint32 clientId,
bytes calldata clientStateBytes,
bytes calldata consensusStateBytes
) external override onlyIBC returns (ConsensusStateUpdate memory update) {
ClientState calldata clientState;
assembly {
clientState := clientStateBytes.offset
}
ConsensusState calldata consensusState;
assembly {
consensusState := consensusStateBytes.offset
}
if (clientState.latestHeight == 0 || consensusState.timestamp == 0) {
revert EvmInCosmosLib.ErrInvalidInitialConsensusState();
}
clientStates[clientId] = clientState;
consensusStates[clientId][clientState.latestHeight] = consensusState;
// Normalize to nanosecond because ibc-go recvPacket expects nanos...
processedMoments[clientId][clientState.latestHeight] = ProcessedMoment({
timestamp: block.timestamp * 1e9,
height: block.number
});
return ConsensusStateUpdate({
clientStateCommitment: clientState.commit(),
consensusStateCommitment: consensusState.commit(),
height: clientState.latestHeight
});
}

/*
* We update the L₂ client through the L₁ client.
* Given an L₂ and L₁ heights (H₂, H₁), we prove that L₂[H₂] ∈ L₁[H₁].
*/
function updateClient(
uint32 clientId,
bytes calldata clientMessageBytes
) external override onlyIBC returns (ConsensusStateUpdate memory) {
Header calldata header;
assembly {
header := clientMessageBytes.offset
}
ClientState memory clientState = clientStates[clientId];
ILightClient l1Client =
IBCStore(ibcHandler).getClient(clientState.l1ClientId);
// L₂[H₂] ∈ L₁[H₁]
if (
!l1Client.verifyMembership(
clientState.l1ClientId,
header.l1Height,
header.l2InclusionProof,
abi.encodePacked(
IBCCommitment.consensusStateCommitmentKey(
clientState.l2ClientId, header.l2Height
)
),
abi.encodePacked(keccak256(abi.encode(header.l2ConsensusState)))
)
) {
revert EvmInCosmosLib.ErrInvalidL1Proof();
}

ConsensusState calldata l2ConsensusState;
bytes calldata rawL2ConsensusState = header.l2ConsensusState;
assembly {
l2ConsensusState := rawL2ConsensusState.offset
}

if (header.l2Height > clientState.latestHeight) {
clientState.latestHeight = header.l2Height;
}

// L₂[H₂] = S₂
// We use ethereum native encoding to make it more efficient.
ConsensusState storage consensusState =
consensusStates[clientId][header.l2Height];
consensusState.timestamp = l2ConsensusState.timestamp;
consensusState.stateRoot = l2ConsensusState.stateRoot;
consensusState.storageRoot = l2ConsensusState.storageRoot;

// P[H₂] = now()
ProcessedMoment storage processed =
processedMoments[clientId][header.l2Height];
processed.timestamp = block.timestamp * 1e9;
processed.height = block.number;

// commit(S₂)
return ConsensusStateUpdate({
clientStateCommitment: clientState.commit(),
consensusStateCommitment: consensusState.commit(),
height: header.l2Height
});
}

function misbehaviour(
uint32 clientId,
bytes calldata clientMessageBytes
) external override onlyIBC {
revert EvmInCosmosLib.ErrInvalidMisbehaviour();
}

function verifyMembership(
uint32 clientId,
uint64 height,
bytes calldata proof,
bytes calldata path,
bytes calldata value
) external virtual returns (bool) {
if (isFrozenImpl(clientId)) {
revert EvmInCosmosLib.ErrClientFrozen();
}
bytes32 storageRoot = consensusStates[clientId][height].storageRoot;
bytes32 slot = keccak256(
abi.encodePacked(
keccak256(abi.encodePacked(path)), EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT
)
);
(bool exists, bytes calldata provenValue) = MPTVerifier2.verifyTrieValue(
proof, keccak256(abi.encodePacked(slot)), 32, storageRoot
);
return exists && keccak256(value) == keccak256(provenValue);
}

function verifyNonMembership(
uint32 clientId,
uint64 height,
bytes calldata proof,
bytes calldata path
) external virtual returns (bool) {
if (isFrozenImpl(clientId)) {
revert EvmInCosmosLib.ErrClientFrozen();
}
bytes32 storageRoot = consensusStates[clientId][height].storageRoot;
bytes32 slot = keccak256(
abi.encodePacked(
keccak256(abi.encodePacked(path)), EvmInCosmosLib.EVM_IBC_COMMITMENT_SLOT
)
);
(bool exists, bytes calldata provenValue) = MPTVerifier2.verifyTrieValue(
proof, keccak256(abi.encodePacked(slot)), 32, storageRoot
);
return !exists;
}

function getClientState(
uint32 clientId
) external view returns (bytes memory) {
return clientStates[clientId].encode();
}

function getConsensusState(
uint32 clientId,
uint64 height
) external view returns (bytes memory) {
return consensusStates[clientId][height].encode();
}

function getTimestampAtHeight(
uint32 clientId,
uint64 height
) external view override returns (uint64) {
return consensusStates[clientId][height].timestamp;
}

function getLatestHeight(
uint32 clientId
) external view override returns (uint64) {
return clientStates[clientId].latestHeight;
}

function isFrozen(
uint32 clientId
) external view virtual returns (bool) {
return isFrozenImpl(clientId);
}

function isFrozenImpl(
uint32 clientId
) internal view returns (bool) {
uint32 l1ClientId = clientStates[clientId].l1ClientId;
return IBCStore(ibcHandler).getClient(l1ClientId).isFrozen(l1ClientId);
}

function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}

function _onlyIBC() internal view {
if (msg.sender != ibcHandler) {
revert EvmInCosmosLib.ErrNotIBC();
}
}

modifier onlyIBC() {
_onlyIBC();
_;
}
}
Loading

0 comments on commit 253680f

Please sign in to comment.