diff --git a/packages/protocol/contracts/examples/xErc20Example.sol b/packages/protocol/contracts/examples/xErc20Example.sol index fbc63988d4a3..be1cf562b5d7 100644 --- a/packages/protocol/contracts/examples/xErc20Example.sol +++ b/packages/protocol/contracts/examples/xErc20Example.sol @@ -1,11 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "../gwyneth/XChainToken.sol"; +import "../gwyneth/XChainERC20Token.sol"; -contract xErc20Example is ERC20, XChainToken { - constructor() ERC20("xERC20", "xERC") { - _mint(msg.sender, 100_000_000_000 * 10**18 ); - } +contract xERC20Example is XChainERC20Token { + constructor(string memory name_, string memory symbol_, address premintAddress_, uint256 premintAmount_ ) XChainERC20Token(name_, symbol_, premintAddress_, premintAmount_ ) {} } \ No newline at end of file diff --git a/packages/protocol/contracts/gwyneth/Bus.sol b/packages/protocol/contracts/gwyneth/Bus.sol index 5df0211caad5..231949750ff7 100644 --- a/packages/protocol/contracts/gwyneth/Bus.sol +++ b/packages/protocol/contracts/gwyneth/Bus.sol @@ -35,7 +35,7 @@ contract Bus is XChain { require(consumed[messageHash] == false); consumed[messageHash] = true; } else if (proofType == ProofType.SYNC) { - // Sync system with shared validity + // Sync system with shared validity (e.g.: like a SignalService shared validity thing) write(message); } else { revert("INVALID BUS PROOF"); diff --git a/packages/protocol/contracts/gwyneth/XChain.sol b/packages/protocol/contracts/gwyneth/XChain.sol index 197bfea9e0bd..5544d6382bb7 100644 --- a/packages/protocol/contracts/gwyneth/XChain.sol +++ b/packages/protocol/contracts/gwyneth/XChain.sol @@ -74,6 +74,7 @@ contract XChain { // - 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 @@ -137,19 +138,13 @@ contract XChain { // 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 returns (uint) { - messages.push(calcMessageHash(message)); - return messages.length - 1; - } + 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 virtual { - ProofType proofType = ProofType(uint16(bytes2(proof[:2]))); - - if (proofType != ProofType.ASYNC) { - revert NO_NEED_BUS_PROOF_ALL_ASYNC(); - } - write(message); - } + function consume(uint256 /*fromChainId*/, bytes memory message, bytes calldata proof) public notImplemented virtual {} } \ No newline at end of file diff --git a/packages/protocol/contracts/gwyneth/XChainERC20Token.sol b/packages/protocol/contracts/gwyneth/XChainERC20Token.sol new file mode 100644 index 000000000000..38536bb49b47 --- /dev/null +++ b/packages/protocol/contracts/gwyneth/XChainERC20Token.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.12 <0.9.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "./XChain.sol"; + +// The reason we need this is because i realized we need to somehow 'override' some of the functions we have in ERC20, and since the balances need to be affected in ERC20 and XChainToken, it is not possible with the current standard, except if we linearize the inheritance (ERC20 -> XChainToken -> TokenImplementation) +contract XChainERC20Token is XChain, ERC20 { + // Only stored on L1 + // @Brecht -> Shall we overwrite in our xERC20Example the totalSupply() of ERC20 ? And use this var instead of the ERC20's _totalSupply + // Not sure in this because i guess it shall serve the same purpose as totalSupply(), also it is a completely different interaction (on xChain) than on the canonical chain, but the totalSupply shall be the same IMO. + //meeting meinutes: We can get rid of this. + uint private _totalBalance; + // Stored on all chains + // This lead me to realize we need thi sinheritance: + // Somehow this has to overwrite (or rather be used) in the ERC20 contract, right ? Like with the balanceOf(addr), otherwise the erc20 is not 'notified'. + // What if we have a function in child ERC20.. which needs to be implemented, like modifyERC20Balance(); + // Example: + // BOb does an xTransfer to Alice from cahin A to chain B. It is is OK but it shall translate into an ERC20 balance change too, not only in this contract but in the ERC20 contract which is with the prev. inheritance was not possible. + /*New variables - overriden from ERC20 since we want them to be modifiable*/ + mapping(address => uint) private _balances; // -> Need to redefine and override functions + uint256 private _totalSupply; // -> Need to redefine and override functions + + constructor(string memory name_, string memory symbol_, address premintAddress_, uint256 premintAmount_ ) ERC20(name_, symbol_) { + _mint(premintAddress_, premintAmount_); + } + + // xtransfer for async case (proof needed) + 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; + } + } + + // xtransfer for async case + function xtransfer(address to, uint amount, uint256 fromChainId, uint256 toChainId) + external + { + require(EVM.chainId() == fromChainId, "ASYNC_CASE, only call it on source chain"); + + _balances[msg.sender] -= amount; + // We need to do xCallOptions (incoprpotate the minting on the dest chain) + // We chack we are on the corect sourvce chain and then we do evm. + EVM.xCallOptions(toChainId); + this.xmint(to, amount); + } + + // DO a mind-puzzle with Brecht if this is really solving the problems of Alice sending Bob from chainA to chainB some tokens!! + // Mint function -> Should only be called by the SC itself. + function xmint(address to, uint amount) + external + { + // Only be called by itself (internal bookikeeping) + require(msg.sender == address(this), "NOT_ALLOWED"); + _balances[to] += amount; + } + + /* Overrides of ERC20 */ + //Change totalSupply and apply xExecuteOn modifier + function totalSupply() //Is it the same as totalSupply() if so, i think that shall be fine! + xExecuteOn(EVM.l1ChainId) //why it has an xExecuteOn modifier ? And why it is applied only here ? + public + view + override + returns (uint256) + { + return _totalSupply; + } + + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev Moves `amount` of tokens from `from` to `to`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer(address from, address to, uint256 amount) internal virtual override { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by + // decrementing then incrementing. + _balances[to] += amount; + } + + emit Transfer(from, to, amount); + + _afterTokenTransfer(from, to, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual override { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + unchecked { + // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above. + _balances[account] += amount; + } + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual override { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + // Overflow not possible: amount <= accountBalance <= totalSupply. + _totalSupply -= amount; + } + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } +} \ No newline at end of file diff --git a/packages/protocol/contracts/gwyneth/XChainToken.sol b/packages/protocol/contracts/gwyneth/XChainToken.sol deleted file mode 100644 index 4c939c3f88ac..000000000000 --- a/packages/protocol/contracts/gwyneth/XChainToken.sol +++ /dev/null @@ -1,33 +0,0 @@ -// 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; - } - } -} \ No newline at end of file diff --git a/packages/protocol/scripts/L2_txn_simulation/CreateXChainTxn.s.sol b/packages/protocol/scripts/L2_txn_simulation/CreateXChainTxn.s.sol new file mode 100644 index 000000000000..088e6ec880ee --- /dev/null +++ b/packages/protocol/scripts/L2_txn_simulation/CreateXChainTxn.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import "forge-std/console2.sol"; + +import "../../contracts/examples/xERC20Example.sol"; + +contract CreateXChainTxn is Script { + address public Bob_deployer_and_xchain_sender = 0x8943545177806ED17B9F23F0a21ee5948eCaa776; //Also .env PRIV_KEY is tied to Bob + address public Alice_xchain_receiver = 0xE25583099BA105D9ec0A67f5Ae86D90e50036425; + + function run() external { + vm.startBroadcast(); + + //Deploy a contract and mints 100k for Bob + xERC20Example exampleXChainToken = new xERC20Example("xChainExample", "xCE", Bob_deployer_and_xchain_sender, 100_000 * 1e18); + + // ChainId to send to + uint256 dummyChainId = 12346; // Does not matter at this point + + console2.log("Sender balance (before sending):", exampleXChainToken.balanceOf(Bob_deployer_and_xchain_sender)); + exampleXChainToken.xtransfer(Alice_xchain_receiver, 2 * 1e18, block.chainid, dummyChainId); + + console2.log("Sender balance:", exampleXChainToken.balanceOf(Bob_deployer_and_xchain_sender)); + console2.log("Receiver balance:", exampleXChainToken.balanceOf(Alice_xchain_receiver)); + + vm.stopBroadcast(); + } +} diff --git a/packages/protocol/scripts/L2_txn_simulation/readme.md b/packages/protocol/scripts/L2_txn_simulation/readme.md index 2728ab26b631..3088ed0b3d7c 100644 --- a/packages/protocol/scripts/L2_txn_simulation/readme.md +++ b/packages/protocol/scripts/L2_txn_simulation/readme.md @@ -1,4 +1,4 @@ -# Create / simulate L2 transactions +# Create / simulate L2 transactions (propose transaction and an xtransfer of a dummy xChainToken) In order to test the L2 node execution hook functionality, we need create valid L2 transactions and submit those to TaikoL1 - where a hook will be built in, to listen the proposeBlock and execute those transactions. This folder is to create L2 transactions (using the same pre-funded accounts Kurtosis is setting up by default) and submit it to our "L1" while using the local taiko_reth image as the EL. @@ -15,6 +15,7 @@ kurtosis run github.com/ethpandaops/ethereum-package --args-file YOUR_PATH_TO_NE ```shell forge script --rpc-url http://127.0.0.1:PORT scripts/DeployL1Locally.s.sol -vvvv --broadcast --private-key PK --legacy ``` +# ProposeBlock ## 1. Create and print L2 transactions ("off-chain") @@ -44,3 +45,10 @@ curl http://127.0.0.1:YOUR_PORT \ ``` +# Send a dummy xChainToken + +In order to send cross-chain transactions with `xCallOptions()`, when the network is up and running, deploy an `xChainERC20Token` contract and fire away an `xtransfer()` transaction. + +```shell +forge script --rpc-url http://127.0.0.1:YOUR_PORT scripts/L2_txn_simulation/CreateXChainTxn.s.sol -vvvv --broadcast --private-key PK_IN_ENV_FILE --legacy +``` \ No newline at end of file