Skip to content

Commit

Permalink
refactor(evm): changes messageId and messageHash calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
allemanfredi committed Nov 8, 2023
1 parent 60cb18a commit 8489e0a
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 140 deletions.
31 changes: 21 additions & 10 deletions packages/evm/contracts/Yaho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ contract Yaho is IYaho, MessageHashCalculator, MessageIdCalculator {
bytes32[] memory messageHashes = new bytes32[](messageIds.length);
for (uint256 i = 0; i < messageIds.length; i++) {
bytes32 expectedMessageHash = hashes[messageIds[i]];
messageHashes[i] = calculateMessageHash(messages[i], address(this));
messageHashes[i] = calculateMessageHash(messages[i], messageIds[i], address(this));
if (messageHashes[i] != expectedMessageHash)
revert MessageHashMismatch(messageHashes[i], expectedMessageHash);
toChainIds[i] = messages[i].toChainId;
Expand Down Expand Up @@ -153,16 +153,27 @@ contract Yaho is IYaho, MessageHashCalculator, MessageIdCalculator {
) internal returns (bytes32 messageId, bytes32 messageHash) {
bool isHeaderReporter = msg.sender == headerReporter;
address from = isHeaderReporter ? address(0) : msg.sender;

// NOTE: in case of isHeaderReporter = true -> to = address(0)
Message memory message = Message(block.chainid, toChainId, from, to, data);
messageHash = calculateMessageHash(message, address(this));
bytes32 salt = keccak256(
abi.encode(
isHeaderReporter ? MESSAGE_BHR : MESSAGE_MPI,
isHeaderReporter ? bytes(abi.encode(0)) : abi.encode(blockhash(block.number), gasleft())
)
);
messageId = calculateMessageId(salt, messageHash);
if (isHeaderReporter) {
(uint256 blockNumber, ) = abi.decode(data, (uint256, bytes32));
Message memory message = Message(block.chainid, toChainId, from, to, data);
messageId = calculateMessageId(
block.chainid,
address(this),
keccak256(abi.encode(blockNumber, MESSAGE_BHR))
);
messageHash = calculateMessageHash(message, messageId, address(this));
} else {
Message memory message = Message(block.chainid, toChainId, from, to, data);
messageId = calculateMessageId(
block.chainid,
address(this),
keccak256(abi.encode(blockhash(block.number), gasleft()))
);
messageHash = calculateMessageHash(message, messageId, address(this));
}

hashes[messageId] = messageHash;
emit MessageDispatched(messageId, from, toChainId, to, data);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/evm/contracts/Yaru.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import { IYaru } from "./interfaces/IYaru.sol";
import { IHashi, IOracleAdapter } from "./interfaces/IHashi.sol";
import { Message } from "./interfaces/IMessage.sol";
import { MessageHashCalculator } from "./utils/MessageHashCalculator.sol";
import { MessageIdCalculator } from "./utils/MessageIdCalculator.sol";
import { IJushinki } from "./interfaces/IJushinki.sol";

contract Yaru is IYaru, MessageHashCalculator, MessageIdCalculator, ReentrancyGuard, Ownable {
contract Yaru is IYaru, MessageHashCalculator, ReentrancyGuard, Ownable {
address public immutable hashi;
address public immutable headerVault;

Expand All @@ -37,8 +36,9 @@ contract Yaru is IYaru, MessageHashCalculator, MessageIdCalculator, ReentrancyGu
bytes[] memory returnDatas = new bytes[](messages.length);
for (uint256 i = 0; i < messages.length; i++) {
Message memory message = messages[i];
bytes32 messageHash = calculateMessageHash(message, _yahos[message.fromChainId]);

bytes32 messageId = messageIds[i];
bytes32 messageHash = calculateMessageHash(message, messageId, _yahos[message.fromChainId]);

if (message.toChainId != block.chainid)
revert MessageFailure(messageId, abi.encode(keccak256("InvalidToChainId")));
Expand Down
3 changes: 2 additions & 1 deletion packages/evm/contracts/adapters/AMB/AMBAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ pragma solidity ^0.8.17;

import { IAMB } from "./IAMB.sol";
import { OracleAdapter } from "../OracleAdapter.sol";
import { BlockHashOracleAdapter } from "../BlockHashOracleAdapter.sol";

contract AMBAdapter is OracleAdapter {
contract AMBAdapter is OracleAdapter, BlockHashOracleAdapter {
IAMB public amb;
address public messageRelay;
bytes32 public chainId;
Expand Down
77 changes: 77 additions & 0 deletions packages/evm/contracts/adapters/BlockHashOracleAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol";
import { Message } from "../interfaces/IMessage.sol";
import { OracleAdapter } from "./OracleAdapter.sol";

abstract contract BlockHashOracleAdapter is OracleAdapter {
using RLPReader for RLPReader.RLPItem;

error InvalidBlockHeaderRLP();
error InvalidBlockHeaderLength(uint256 length);
error ConflictingBlockMessageHash(
uint256 blockNumber,
bytes32 reportedBlockMessageHash,
bytes32 storedBlockMessageHash
);

/// @dev Proves and stores valid ancestral block hashes for a given chain ID.
/// @param fromChainId The ID of the chain to prove block hashes for.
/// @param blockHeaders The RLP encoded block headers to prove the hashes for.
/// @param yaho Yaho address.
/// @notice Block headers should be ordered by descending block number and should start with a known block header.
function proveAncestralBlockHashes(uint256 fromChainId, bytes[] memory blockHeaders, address yaho) external {
for (uint256 i = 0; i < blockHeaders.length; i++) {
RLPReader.RLPItem memory blockHeaderRLP = RLPReader.toRlpItem(blockHeaders[i]);

if (!blockHeaderRLP.isList()) revert InvalidBlockHeaderRLP();

RLPReader.RLPItem[] memory blockHeaderContent = blockHeaderRLP.toList();

// A block header should have between 15 and 17 elements (baseFee and withdrawalsRoot have been added later)
if (blockHeaderContent.length < 15 || blockHeaderContent.length > 17)
revert InvalidBlockHeaderLength(blockHeaderContent.length);

bytes32 blockParent = bytes32(blockHeaderContent[0].toUint());
uint256 blockNumber = uint256(blockHeaderContent[8].toUint());

bytes32 reportedBlockHash = keccak256(blockHeaders[i]);
bytes32 reportedMessageId = calculateMessageId(
fromChainId,
yaho,
keccak256(abi.encode(blockNumber, MESSAGE_BHR))
);
Message memory reportedMessage = Message(
fromChainId,
block.chainid,
address(0),
address(0),
abi.encode(blockNumber, reportedBlockHash)
);

bytes32 reportedBlockMessageHash = calculateMessageHash(reportedMessage, reportedMessageId, yaho);
bytes32 storedBlockMessageHash = hashes[fromChainId][reportedMessageId];

if (reportedBlockMessageHash != storedBlockMessageHash)
revert ConflictingBlockMessageHash(blockNumber, reportedBlockMessageHash, storedBlockMessageHash);

Message memory message = Message(
fromChainId,
block.chainid,
address(0),
address(0),
abi.encode(blockNumber - 1, blockParent)
);

bytes32 messageId = calculateMessageId(
fromChainId,
yaho,
keccak256(abi.encode(blockNumber - 1, MESSAGE_BHR))
);
bytes32 messageHash = calculateMessageHash(message, messageId, yaho);

_storeHash(fromChainId, messageId, messageHash);
}
}
}
8 changes: 4 additions & 4 deletions packages/evm/contracts/adapters/DendrETH/DendrETHAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract DendrETHAdapter is OracleAdapter {

/// @notice Stores the block header for a given block only if it exists
// in the DendrETH Light Client for the chainId.
function storeBlockHeader(
/*function storeBlockHeader(
uint32 _chainId,
uint64 _slot,
uint256 _blockNumber,
Expand Down Expand Up @@ -94,9 +94,9 @@ contract DendrETHAdapter is OracleAdapter {
}
_storeBlockNumberAndBlockHeader(_chainId, _blockNumber, _blockHash, _yaho);
}
}*/

function _storeBlockNumberAndBlockHeader(
/*function _storeBlockNumberAndBlockHeader(
uint256 fromChainId,
uint256 blockNumber,
bytes32 blockHeader,
Expand All @@ -113,5 +113,5 @@ contract DendrETHAdapter is OracleAdapter {
bytes32 messageId = calculateMessageId(keccak256(abi.encode(MESSAGE_BHR, bytes(abi.encode(0)))), messageHash);
_storeHash(fromChainId, messageId, messageHash);
}
}*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract TelepathyAdapter is OracleAdapter {

/// @notice Stores the block header for a given block only if it exists in the Telepathy
/// Light Client for the chainId.
function storeBlockHeader(
/*function storeBlockHeader(
uint32 _chainId,
uint64 _slot,
uint256 _blockNumber,
Expand Down Expand Up @@ -63,5 +63,5 @@ contract TelepathyAdapter is OracleAdapter {
bytes32 messageId = calculateMessageId(keccak256(abi.encode(MESSAGE_BHR, bytes(abi.encode(0)))), messageHash);
_storeHash(uint256(_chainId), messageId, messageHash);
}
}*/
}
29 changes: 0 additions & 29 deletions packages/evm/contracts/headers/HeaderVault.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IHeaderVault } from "../interfaces/IHeaderVault.sol";

contract HeaderVault is IHeaderVault, Ownable {
using RLPReader for RLPReader.RLPItem;

mapping(uint256 => mapping(uint256 => bytes32)) private _blockHeaders;
address public yaru;

Expand Down Expand Up @@ -37,30 +34,4 @@ contract HeaderVault is IHeaderVault, Ownable {
emit NewBlock(fromChainId, blockNumber, blockHeader);
return bytes(abi.encode(true));
}

function proveAncestralBlockHashes(uint256 fromChainId, bytes[] memory blockHeaders) external {
for (uint256 i = 0; i < blockHeaders.length; i++) {
RLPReader.RLPItem memory blockHeaderRLP = RLPReader.toRlpItem(blockHeaders[i]);

if (!blockHeaderRLP.isList()) revert InvalidBlockHeaderRLP();

RLPReader.RLPItem[] memory blockHeaderContent = blockHeaderRLP.toList();

// NOTE: A block header should have between 15 and 17 elements (baseFee and withdrawalsRoot have been added later)
if (blockHeaderContent.length < 15 || blockHeaderContent.length > 17)
revert InvalidBlockHeaderLength(blockHeaderContent.length);

bytes32 blockParent = bytes32(blockHeaderContent[0].toUint());
uint256 blockNumber = uint256(blockHeaderContent[8].toUint());

bytes32 reportedBlockHash = keccak256(blockHeaders[i]);
bytes32 storedBlockHash = _blockHeaders[fromChainId][blockNumber];

if (reportedBlockHash != storedBlockHash)
revert ConflictingBlockHeader(blockNumber, reportedBlockHash, storedBlockHash);

_blockHeaders[fromChainId][blockNumber - 1] = blockParent;
emit NewBlock(fromChainId, blockNumber - 1, blockParent);
}
}
}
7 changes: 0 additions & 7 deletions packages/evm/contracts/interfaces/IHeaderVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,12 @@ import { IJushinki } from "./IJushinki.sol";

interface IHeaderVault is IJushinki {
event NewBlock(uint256 indexed fromChainId, uint256 indexed blockNumber, bytes32 blockHeader);
event HeaderReporterEnabled(uint256 fromChainId, address headerReporter);
event HeaderReporterDisabled(uint256 fromChainId, address headerReporter);

error InvalidBlockHeaderLength(uint256 length);
error InvalidBlockHeaderRLP();
error ConflictingBlockHeader(uint256 blockNumber, bytes32 reportedBlockHash, bytes32 storedBlockHash);
error NotYaru(address currentYaru, address expectedYaru);
error InvalidHeaderReporter(uint256 fromChainId, address headerReporter, address expectedHeaderReporter);
error YaruAlreadyInitialized(address yaru);

function initializeYaru(address yaru_) external;

function getBlockHeader(uint256 chainId, uint256 blockNumber) external view returns (bytes32);

function proveAncestralBlockHashes(uint256 fromChainId, bytes[] memory blockHeaders) external;
}
3 changes: 2 additions & 1 deletion packages/evm/contracts/test/MockOracleAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
pragma solidity ^0.8.17;

import { OracleAdapter } from "../adapters/OracleAdapter.sol";
import { BlockHashOracleAdapter } from "../adapters/BlockHashOracleAdapter.sol";

contract MockOracleAdapter is OracleAdapter {
contract MockOracleAdapter is OracleAdapter, BlockHashOracleAdapter {
error LengthMismatch(address emitter);

function setHashes(uint256 domain, bytes32[] memory ids, bytes32[] memory _hashes) external {
Expand Down
9 changes: 7 additions & 2 deletions packages/evm/contracts/utils/MessageHashCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { Message } from "../interfaces/IMessage.sol";
contract MessageHashCalculator {
/// @dev Calculates the hash of a given message.
/// @param message Message that was/will be dispatched.
/// @param messageId Message if of the message that will be dispatched.
/// @param dispatcherAddress Contract that did/will dispatch the given message.
function calculateMessageHash(Message memory message, address dispatcherAddress) public pure returns (bytes32) {
return keccak256(abi.encode(message, dispatcherAddress));
function calculateMessageHash(
Message memory message,
bytes32 messageId,
address dispatcherAddress
) public pure returns (bytes32) {
return keccak256(abi.encode(message, messageId, dispatcherAddress));
}
}
11 changes: 8 additions & 3 deletions packages/evm/contracts/utils/MessageIdCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ import { Message } from "../interfaces/IMessage.sol";

contract MessageIdCalculator {
/// @dev Calculates the ID of a given message.
/// @param fromChainId Source chain id
/// @param dispatcherAddress Source chain id
/// @param salt Message salt.
/// @param messageHash Message Hash that was/will be dispatched.
function calculateMessageId(bytes32 salt, bytes32 messageHash) public pure returns (bytes32) {
return keccak256(abi.encode(salt, messageHash));
function calculateMessageId(
uint256 fromChainId,
address dispatcherAddress,
bytes32 salt
) public pure returns (bytes32) {
return keccak256(abi.encode(fromChainId, dispatcherAddress, salt));
}
}
8 changes: 6 additions & 2 deletions packages/evm/test/04_Yaho.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AbiCoder } from "@ethersproject/abi"
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"
import { expect } from "chai"
Expand All @@ -8,6 +9,8 @@ import { Chains, ZERO_ADDRESS } from "./constants"
import { toBytes32 } from "./utils"
import Message from "./utils/Message"

const abiCoder = new ethers.utils.AbiCoder()

let messageRelay: Contract,
owner: SignerWithAddress,
yaho: Contract,
Expand Down Expand Up @@ -289,11 +292,12 @@ describe("Yaho", function () {
})

it("dispatch a message using header reporter", async function () {
const tx = await yaho.connect(fakeHeaderReporter).dispatchMessage(Chains.Mainnet, fakeTo1.address, "0x01")
const data = abiCoder.encode(["uint256", "bytes32"], [1, toBytes32(1)])
const tx = await yaho.connect(fakeHeaderReporter).dispatchMessage(Chains.Mainnet, fakeTo1.address, data)
const [message1] = Message.fromReceipt(await tx.wait(1))
await expect(tx)
.to.emit(yaho, "MessageDispatched")
.withArgs(anyValue, ZERO_ADDRESS, Chains.Mainnet, fakeTo1.address, "0x01")
.withArgs(anyValue, ZERO_ADDRESS, Chains.Mainnet, fakeTo1.address, data)
expect(await yaho.hashes(message1.id)).to.not.be.eq(toBytes32(0))
})
})
Expand Down
Loading

0 comments on commit 8489e0a

Please sign in to comment.