Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Proofcast adapter #60

Closed
Closed
Show file tree
Hide file tree
Changes from 2 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
13,488 changes: 13,488 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

122 changes: 122 additions & 0 deletions packages/evm/contracts/adapters/Proofcast/ProofcastAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.20;

import { Message } from "../../interfaces/IMessage.sol";
import { MessageIdCalculator } from "../../utils/MessageIdCalculator.sol";
import { MessageHashCalculator } from "../../utils/MessageHashCalculator.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { RLPReader } from "solidity-rlp/contracts/RLPReader.sol";
import { IERC777Recipient } from "@openzeppelin/contracts/interfaces/IERC777Recipient.sol";
import { IERC1820RegistryUpgradeable } from "@openzeppelin/contracts-upgradeable/interfaces/IERC1820RegistryUpgradeable.sol";
import { BlockHashAdapter } from "../BlockHashAdapter.sol";

contract ProofcastAdapter is BlockHashAdapter, MessageIdCalculator, MessageHashCalculator, Ownable {
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved
error InvalidEventRLP();
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved
error InvalidEventContentLength(uint256);
error UnsupportedProtocolId(bytes1);
error UnsupportedChainId(uint256);
error UnexpectedEventTopic(bytes32);
error InvalidSender();
error InvalidMessageId(uint256, uint256);
error InvalidDestinationChainId(uint256);

event TeeSignerChanged(address);
event TeeSignerPendingChange(address, bytes, uint256);
event YahoInitialized(uint256, address);

string public constant PROVIDER = "proofcast";

// MessageDispatched(uint256 indexed messageId, Message message)
bytes32 public constant MESSAGE_DISPATCHED_EVENT_TOPIC =
0x218247aabc759e65b5bb92ccc074f9d62cd187259f2a0984c3c9cf91f67ff7cf;
uint256 public constant TEE_ADDRESS_CHANGE_GRACE_PERIOD = 172800; // 48 hours
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved

address public teeAddress;
address public teeAddressNew;
uint256 public teeAddressChangeGraceThreshold;
mapping(uint256 => address) public yahos;
mapping(bytes32 => bool) public pastEvents;

function initYaho(uint256 chainId, address yaho_) public onlyOwner {
require(chainId != block.chainid, "Not usable Yaho (same chainId)");
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved
yahos[chainId] = yaho_;
emit YahoInitialized(chainId, yaho_);
}

function setTeeSigner(bytes calldata pubKey, bytes memory attestation) public onlyOwner {
if (teeAddress == address(0)) {
// Setting the teeAddress the first time
teeAddress = _getAddressFromPublicKey(pubKey);
emit TeeSignerPendingChange(teeAddress, attestation, block.timestamp);
emit TeeSignerChanged(teeAddress);
} else {
// The new address will be set after a grace period of 48 hours
teeAddressNew = _getAddressFromPublicKey(pubKey);
teeAddressChangeGraceThreshold = block.timestamp + TEE_ADDRESS_CHANGE_GRACE_PERIOD;
emit TeeSignerPendingChange(teeAddressNew, attestation, teeAddressChangeGraceThreshold);
}
}

function storeHashes(bytes calldata statement, bytes memory signature) public {
if (teeAddressNew != address(0) && block.timestamp > teeAddressChangeGraceThreshold) {
teeAddress = teeAddressNew;
teeAddressNew = address(0);
emit TeeSignerChanged(teeAddress);
}
require(teeAddress != address(0), "Tee signer not set!");
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved
require(ECDSA.recover(sha256(statement), signature) == teeAddress, "Invalid signature");
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved

// Supports only EVM events
bytes1 protocolId = statement[1];
if (protocolId != 0x00) revert UnsupportedProtocolId(protocolId);

(uint256 domain, uint256[] memory ids, bytes32[] memory hashes) = _checkEventAndDecodeData(statement);
_storeHashes(domain, ids, hashes);
}

function _checkEventAndDecodeData(
bytes calldata statement
) internal view returns (uint256 domain, uint256[] memory ids, bytes32[] memory hashes) {
// Statement format:
// | version | protocol | protocol_chain_id | event id | event_bytes |
// | (1 byte) | (1 byte) | (32 bytes) | (32 bytes) | varlen |

uint16 offset = 2; // skip version, protocolId
domain = uint256(bytes32(statement[offset:(offset += 32)]));

if (yahos[domain] == address(0)) revert UnsupportedChainId(domain);

offset += 32; // skip the event id (32 bytes)
bytes memory eventBytes = statement[offset:];
RLPReader.RLPItem memory eventRLP = RLPReader.toRlpItem(eventBytes);
if (!RLPReader.isList(eventRLP)) revert InvalidEventRLP();

RLPReader.RLPItem[] memory eventContent = RLPReader.toList(eventRLP);

// Event must contain address, logs and data
if (eventContent.length != 3) revert InvalidEventContentLength(eventContent.length);

// MessageDispatched event parsing
address yahoAddress = RLPReader.toAddress(eventContent[0]);
require(yahoAddress == yahos[domain], "Invalid Yaho address");

RLPReader.RLPItem[] memory logs = RLPReader.toList(eventContent[1]);

bytes32 topic = bytes32(RLPReader.toBytes(logs[0]));
if (topic != MESSAGE_DISPATCHED_EVENT_TOPIC) revert UnexpectedEventTopic(topic);

bytes memory messageBytes = RLPReader.toBytes(eventContent[2]);
Message memory message = abi.decode(messageBytes, (Message));
uint256 expectedMessageId = calculateMessageId(domain, yahoAddress, calculateMessageHash(message));

uint256 messageId = uint256(bytes32(RLPReader.toBytes(logs[1])));
if (messageId != expectedMessageId) revert InvalidMessageId(messageId, expectedMessageId);

(ids, hashes) = abi.decode(message.data, (uint256[], bytes32[]));
gitmp01 marked this conversation as resolved.
Show resolved Hide resolved
}

function _getAddressFromPublicKey(bytes calldata pubKey) internal pure returns (address) {
return address(uint160(uint256(keccak256(pubKey[1:]))));
}
}
1 change: 1 addition & 0 deletions packages/evm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"pinst": "^3.0.0",
"prettier": "^2.8.7",
"prettier-plugin-solidity": "^1.0.0",
"rlp-stream": "^0.1.0",
"shx": "^0.3.4",
"solhint": "^3.3.7",
"solhint-plugin-prettier": "^0.0.5",
Expand Down
1 change: 1 addition & 0 deletions packages/evm/tasks/deploy/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "./hyperlane"
import "./layerzero"
import "./optimism"
import "./pnetwork"
import "./proofcast"
import "./router"
import "./sygma"
import "./telepathy"
Expand Down
69 changes: 69 additions & 0 deletions packages/evm/tasks/deploy/adapters/proofcast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { adapters } from "@hyperlane-xyz/core/dist/contracts/middleware/liquidity-layer"
import type { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
import { task } from "hardhat/config"
import type { TaskArguments } from "hardhat/types"

import type { ProofcastAdapter } from "../../../types/contracts/adapters/Proofcast/ProofcastAdapter"
import type { ProofcastAdapter__factory } from "../../../types/factories/contracts/adapters/Proofcast/ProofcastAdapter__factory"
import { verify } from "../index"

task("deploy:adapter:ProofcastAdapter")
.addFlag("verify", "whether to verify the contract on Etherscan")
.setAction(async function (taskArguments: TaskArguments, hre) {
console.log("Deploying ProofcastAdapter...")
const signers: SignerWithAddress[] = await hre.ethers.getSigners()
const ProofcastAdapterFactory: ProofcastAdapter__factory = <ProofcastAdapter__factory>(
await hre.ethers.getContractFactory("ProofcastAdapter")
)
const constructorArguments = [
taskArguments.yaho,
taskArguments.chainId,
taskArguments.publicKey,
taskArguments.attestation,
] as const
const ProofcastAdapter: ProofcastAdapter = <ProofcastAdapter>(
await ProofcastAdapterFactory.connect(signers[0]).deploy()
)
await ProofcastAdapter.deployed()
console.log("ProofcastAdapter deployed to:", ProofcastAdapter.address)

if (taskArguments.verify) await verify(hre, ProofcastAdapter, constructorArguments)
})

task("deploy:adapter:ProofcastAdapter:initYaho")
.addParam("adapter", "Proofcast adapter address")
.addParam("yaho", "address of the Yaho contract")
.addParam("chainId", "chain id of the Yaho contract")
.setAction(async function (taskArguments: TaskArguments, hre) {
console.log("Initializing Yaho...")
const signers: SignerWithAddress[] = await hre.ethers.getSigners()
const ProofcastAdapterFactory: ProofcastAdapter__factory = <ProofcastAdapter__factory>(
await hre.ethers.getContractFactory("ProofcastAdapter")
)
const proofcastAdapter = ProofcastAdapterFactory.attach(taskArguments.adapter)

const tx = await proofcastAdapter.connect(signers[0]).initYaho(taskArguments.chainId, taskArguments.yaho)
const receipt = await tx.wait(1)

console.log("Yaho initialized @", receipt.transactionHash)
})

task("deploy:adapter:ProofcastAdapter:setTeeSigner")
.addParam("adapter", "Proofcast adapter address")
.addParam("publicKey", "event attestator public key")
.addParam("attestation", "event attestator attestation payload")
.setAction(async function (taskArguments: TaskArguments, hre) {
console.log("Setting tee signer...")
const signers: SignerWithAddress[] = await hre.ethers.getSigners()
const ProofcastAdapterFactory: ProofcastAdapter__factory = <ProofcastAdapter__factory>(
await hre.ethers.getContractFactory("ProofcastAdapter")
)
const proofcastAdapter = ProofcastAdapterFactory.attach(taskArguments.adapter)

const tx = await proofcastAdapter
.connect(signers[0])
.setTeeSigner(taskArguments.publicKey, taskArguments.attestation)
const receipt = await tx.wait(1)

console.log("Tee signer set @", receipt.transactionHash)
})
Loading