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(gwyneth): incorporate booster sample contracts #26

Merged
merged 5 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions packages/protocol/contracts/examples/xErc20Example.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../gwyneth/XChainERC20Token.sol";

contract xERC20Example is XChainERC20Token {
constructor(string memory name_, string memory symbol_, address premintAddress_, uint256 premintAmount_ ) XChainERC20Token(name_, symbol_, premintAddress_, premintAmount_ ) {}
}
44 changes: 44 additions & 0 deletions packages/protocol/contracts/gwyneth/Bus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.12 <0.9.0;

import "./XChain.sol";

contract Bus is XChain {
// Stored only on the target chain
mapping (bytes32 => bool) public consumed;

function isMessageSent(bytes32 messageHash, uint busID) external view returns (bool) {
return messages[busID] == messageHash;
}

function write(bytes memory message) public override returns (uint) {
messages.push(calcMessageHash(message));
return messages.length - 1;
}

function consume(uint fromChainId, bytes memory message, bytes calldata proof) public override {
ProofType proofType = ProofType(uint16(bytes2(proof[:2])));
if (proofType == ProofType.ASYNC) {
// Decode the proof
AsyncBusProof memory busProof = abi.decode(proof[2:], (AsyncBusProof));

// Calculate the message hash
bytes32 messageHash = calcMessageHash(message);

// Do the call on the source chain to see if the message was sent there
xCallOptions(fromChainId, true, busProof.boosterCallProof);
bool isSent = this.isMessageSent(messageHash, busProof.busID);
require(isSent == true);

// Make sure this is the first and last time this message is consumed
require(consumed[messageHash] == false);
consumed[messageHash] = true;
} else if (proofType == ProofType.SYNC) {
// Sync system with shared validity (e.g.: like a SignalService shared validity thing)
write(message);
} else {
revert("INVALID BUS PROOF");
}
}
}
68 changes: 68 additions & 0 deletions packages/protocol/contracts/gwyneth/EVM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.12 <0.9.0;

// EVM library
library EVM {
// precompile addresses
address constant xCallOptionsAddress = address(0x1100);

uint constant l1ChainId = 1;
uint constant version = 1;

function xCallOnL1()
public
view
{
xCallOptions(l1ChainId);
}

function xCallOptions(uint chainID)
public
view
{
xCallOptions(chainID, true);
}

function xCallOptions(uint chainID, bool sandbox)
public
view
{
xCallOptions(chainID, sandbox, address(0), address(0));
}

function xCallOptions(uint chainID, bool sandbox, address txOrigin, address msgSender)
public
view
{
xCallOptions(chainID, sandbox, txOrigin, msgSender, 0x0, "");
}

function xCallOptions(uint chainID, bool sandbox, bytes32 blockHash, bytes memory proof)
public
view
{
xCallOptions(chainID, sandbox, address(0), address(0), blockHash, proof);
}

function xCallOptions(uint chainID, bool sandbox, address txOrigin, address msgSender, bytes32 blockHash, bytes memory proof)
public
view
{
// This precompile is not supported on L1
require(chainID != l1ChainId);

// Call the custom precompile
bytes memory input = abi.encodePacked(version, chainID, sandbox, txOrigin, msgSender, blockHash, proof);
(bool success, ) = xCallOptionsAddress.staticcall(input);
require(success);
}

function isOnL1() public view returns (bool) {
return chainId() == l1ChainId;
}

function chainId() public view returns (uint256) {
return block.chainid;
}
}
150 changes: 150 additions & 0 deletions packages/protocol/contracts/gwyneth/XChain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.12 <0.9.0;

import "./EVM.sol";

contract XChain {
struct XChainCallProof {
uint chainID;
uint blockID;
bytes callProof;
}

struct AsyncBusProof {
uint busID;
bytes boosterCallProof;
}

struct AsyncBusProofV2 {
uint blockNumber;
uint busID;
}

enum ProofType {
INVALID,
ASYNC,
SYNC
}

// Messages are stored only on the source chain for ASYNC messages.
// In SYNC mode, the message is stored on both the source and the target chain.
bytes32[] public messages;

// Only stored on L1
// Currently getBlockHash() is not supported via the new Taiko Gwyneth
//ITaiko public taiko;
// todo (@Brecht): XChain has a bus property but Bus is an XChain (inherits). It does not make too much sense to me, or maybe i'm missing the point ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bus might still be needed for async transactions to pass data from one chain to the other, but I guess to keep things simple let's assume synchronous everywhere for now.

//Bus public bus;

// Event that is logged when a transaction on a chain also needs to be executed on another chain
event ExecuteNextOn(uint chainID, address from, address target, bytes callData);

error FUNC_NOT_IMPLEMENTED();
error NO_NEED_BUS_PROOF_ALL_ASYNC();

function init(/*ITaiko _taiko*/)
internal
{
//taiko = _taiko;
}

modifier notImplemented() {
revert FUNC_NOT_IMPLEMENTED();
_;
}

// xExecuteOn functions need
// - to be external
modifier xExecuteOn(uint chainID) {
if (EVM.chainId() == chainID) {
_;
} else {
EVM.xCallOptions(chainID, true);
(bool success, bytes memory data) = address(this).staticcall(msg.data);
require(success);
// Just pass through the return data
assembly {
return(add(data, 32), mload(data))
}
}
}

// xFunctions functions need
// - to be external
// - to have `bytes proof` as the last function argument
modifier xFunction(uint fromChainId, uint toChainId, bytes calldata proof) {
// Current code is written with async case ! (This is outdated there, no need to run if running in sync. comp mode)
if (fromChainId != toChainId) {
// Remove the proof data from the message data
// Bytes arays are padded to 32 bytes and start with a 32 byte length value
uint messageLength = msg.data.length - ((proof.length + 31) / 32 + 1) * 32;
bytes memory message = msg.data;
assembly {
mstore(message, messageLength)
}

// Use the bus to communicate between chains
if (EVM.chainId() == fromChainId) {
uint busID = write(message);

// Always suggest doing an async proof for now on the target chain
AsyncBusProofV2 memory asyncProof = AsyncBusProofV2({
busID: busID,
blockNumber: block.number
});
bytes memory encodedProof = abi.encode(asyncProof);
bytes memory callData = bytes(string.concat(string(new bytes(0x0001)), string(message), string(encodedProof)));
emit ExecuteNextOn(toChainId, address(0), address(this), callData);
} else if (EVM.chainId() == toChainId) {
consume(fromChainId, message, proof);
} else {
revert();
}
}
_;
}

// These could also be exposed using a precompile because we could get them from public input,
// but that requires extra work so let's just fetch them from L1 for now
function getBlockHash(uint chainID, uint blockID) external view xExecuteOn(EVM.l1ChainId) returns (bytes32) {
// todo(@Brecht): Currently not supported or well, at least TaikoL1 does not have it with the current design.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, outdated! Hmmmm only needed for async case so let's skip for now.

//return taiko.getBlockHash(chainID, blockID);
}

function calcMessageHash(bytes memory message) internal view returns (bytes32) {
return keccak256(abi.encode(EVM.chainId(), msg.sender, message));
}

// Supports setting the call options using any L2 in the booster network.
// This is done by first checking the validity of the blockhash of the specified L2.
function xCallOptions(uint chainID, bool sandbox, bytes memory proof) internal view {
// Decode the proof
XChainCallProof memory chainCallProof = abi.decode(proof, (XChainCallProof));
require(chainID == chainCallProof.chainID);

// If the source chain isn't L1, go fetch the block header of the L2 stored on L1
bytes32 blockHash = 0x0;
if (chainID != EVM.l1ChainId) {

blockHash = this.getBlockHash(chainID, chainCallProof.blockID);
}

// Do the call on the specified chain
EVM.xCallOptions(chainID, sandbox, blockHash, chainCallProof.callProof);
}

// todo (@Brecht):
// There was a circular reference (XBus inherits from XChain, while also XChain has a XBus property, so i made these to compile)
// They will be inherited in XBus, but basically XBus can be incorporated into XChain, no ?

// Question (Brecht):
//- Shall we put back these functionalities to bus ?
//- Shall we remove (as i did here) the ownership of the bus - then use the previous implementation ? (notImplemented modifier) and overwrite in the child "bus" ?

// Currently, supposingly there is "synchronous composability", so let's assume a synchronous world
function write(bytes memory message) public virtual notImplemented returns (uint) {}

// Even tho the function just passes thru to write(), it is needed to bus-compatibility, where the consume function will differ
function consume(uint256 /*fromChainId*/, bytes memory message, bytes calldata proof) public notImplemented virtual {}
}
Loading
Loading