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 1 commit
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
11 changes: 11 additions & 0 deletions packages/protocol/contracts/examples/xErc20Example.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../gwyneth/XChainToken.sol";

contract xErc20Example is ERC20, XChainToken {
constructor() ERC20("xERC20", "xERC") {
_mint(msg.sender, 100_000_000_000 * 10**18 );
}
}
58 changes: 58 additions & 0 deletions packages/protocol/contracts/gwyneth/Bus.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.12 <0.9.0;

import "./XChain.sol";

contract Bus is XChain {
// 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;

// Stored only on the target chain
mapping (bytes32 => bool) public consumed;

enum ProofType {
INVALID,
ASYNC,
SYNC
}

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
write(message);
} else {
revert("INVALID BUS PROOF");
}
}

function calcMessageHash(bytes memory message) internal view returns (bytes32) {
return keccak256(abi.encode(EVM.chainId(), msg.sender, message));
}
}
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;
}
}
126 changes: 126 additions & 0 deletions packages/protocol/contracts/gwyneth/XChain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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;
}

// 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();

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) {
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);
}

// 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 ?
function write(bytes memory message) public virtual notImplemented returns (uint) {}
function consume(uint fromChainId, bytes memory message, bytes calldata proof) public virtual notImplemented {}
}
33 changes: 33 additions & 0 deletions packages/protocol/contracts/gwyneth/XChainToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.12 <0.9.0;

import "./XChain.sol";

contract XChainToken is XChain {
// Only stored on L1
uint private _totalBalance;
// Stored on all chains
mapping(address => uint) public balances;

function totalBalance()
xExecuteOn(EVM.l1ChainId)
external
view
returns (uint)
{
return _totalBalance;
}

function xtransfer(address to, uint amount, uint256 fromChainId, uint256 toChainId, bytes calldata proof)
xFunction(fromChainId, toChainId, proof)
external
{
if (EVM.chainId() == fromChainId) {
balances[msg.sender] -= amount;
}
if (EVM.chainId() == toChainId) {
balances[to] += amount;
}
}
}
Loading