From 74ce3fff8b16131f78d47b0c1e2651b40b1ccba8 Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Tue, 18 Jun 2024 18:47:21 +0300 Subject: [PATCH] wip --- omnichain/example/contracts/Caller.sol | 7 +- omnichain/example/flat | 311 ++++++++++++++++++++++ omnichain/example/hardhat.config.ts | 2 + omnichain/example/tasks/deploy-caller.ts | 23 ++ omnichain/example/tasks/deposit-caller.ts | 20 ++ 5 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 omnichain/example/flat create mode 100644 omnichain/example/tasks/deploy-caller.ts create mode 100644 omnichain/example/tasks/deposit-caller.ts diff --git a/omnichain/example/contracts/Caller.sol b/omnichain/example/contracts/Caller.sol index e580b450..46917fb9 100644 --- a/omnichain/example/contracts/Caller.sol +++ b/omnichain/example/contracts/Caller.sol @@ -7,9 +7,12 @@ contract Caller { error DepositFailed(); function deposit(address universalAppAddress) public payable { - bytes memory data = abi.encodePacked(); + bytes memory data = abi.encodePacked(universalAppAddress); - (bool success, ) = tssAddress.call{value: msg.value, gas: 100000}(data); + (bool success, ) = payable(tssAddress).call{ + value: msg.value, + gas: 100000 + }(data); if (success) { emit DepositSuccess(msg.sender, universalAppAddress); diff --git a/omnichain/example/flat b/omnichain/example/flat new file mode 100644 index 00000000..169c79fc --- /dev/null +++ b/omnichain/example/flat @@ -0,0 +1,311 @@ +// Sources flattened with hardhat v2.22.3 https://hardhat.org + +// SPDX-License-Identifier: MIT + +// File @zetachain/protocol-contracts/contracts/zevm/interfaces/IZRC20.sol@v7.0.0-rc1 + +// Original license: SPDX_License_Identifier: MIT +pragma solidity 0.8.7; + +interface IZRC20 { + function totalSupply() external view returns (uint256); + + function balanceOf(address account) external view returns (uint256); + + function transfer(address recipient, uint256 amount) external returns (bool); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 amount) external returns (bool); + + function decreaseAllowance(address spender, uint256 amount) external returns (bool); + + function increaseAllowance(address spender, uint256 amount) external returns (bool); + + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function deposit(address to, uint256 amount) external returns (bool); + + function burn(address account, uint256 amount) external returns (bool); + + function withdraw(bytes memory to, uint256 amount) external returns (bool); + + function withdrawGasFee() external view returns (address, uint256); + + function PROTOCOL_FEE() external view returns (uint256); + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + event Deposit(bytes from, address indexed to, uint256 value); + event Withdrawal(address indexed from, bytes to, uint256 value, uint256 gasFee, uint256 protocolFlatFee); + event UpdatedSystemContract(address systemContract); + event UpdatedGasLimit(uint256 gasLimit); + event UpdatedProtocolFlatFee(uint256 protocolFlatFee); +} + + +// File @zetachain/protocol-contracts/contracts/zevm/interfaces/zContract.sol@v7.0.0-rc1 + +// Original license: SPDX_License_Identifier: MIT +pragma solidity 0.8.7; + +struct zContext { + bytes origin; + address sender; + uint256 chainID; +} + +interface zContract { + function onCrossChainCall( + zContext calldata context, + address zrc20, + uint256 amount, + bytes calldata message + ) external; +} + + +// File @zetachain/protocol-contracts/contracts/zevm/SystemContract.sol@v7.0.0-rc1 + +// Original license: SPDX_License_Identifier: MIT +pragma solidity 0.8.7; + + +/** + * @dev Custom errors for SystemContract + */ +interface SystemContractErrors { + error CallerIsNotFungibleModule(); + error InvalidTarget(); + error CantBeIdenticalAddresses(); + error CantBeZeroAddress(); + error ZeroAddress(); +} + +/** + * @dev The system contract it's called by the protocol to interact with the blockchain. + * Also includes a lot of tools to make easier to interact with ZetaChain. + */ +contract SystemContract is SystemContractErrors { + /// @notice Map to know the gas price of each chain given a chain id. + mapping(uint256 => uint256) public gasPriceByChainId; + /// @notice Map to know the ZRC20 address of a token given a chain id, ex zETH, zBNB etc. + mapping(uint256 => address) public gasCoinZRC20ByChainId; + // @dev: Map to know uniswap V2 pool of ZETA/ZRC20 given a chain id. This refer to the build in uniswap deployed at genesis. + mapping(uint256 => address) public gasZetaPoolByChainId; + + /// @notice Fungible address is always the same, it's on protocol level. + address public constant FUNGIBLE_MODULE_ADDRESS = 0x735b14BB79463307AAcBED86DAf3322B1e6226aB; + /// @notice Uniswap V2 addresses. + address public immutable uniswapv2FactoryAddress; + address public immutable uniswapv2Router02Address; + /// @notice Address of the wrapped ZETA to interact with Uniswap V2. + address public wZetaContractAddress; + /// @notice Address of ZEVM Zeta Connector. + address public zetaConnectorZEVMAddress; + + /// @notice Custom SystemContract errors. + event SystemContractDeployed(); + event SetGasPrice(uint256, uint256); + event SetGasCoin(uint256, address); + event SetGasZetaPool(uint256, address); + event SetWZeta(address); + event SetConnectorZEVM(address); + + /** + * @dev Only fungible module can deploy a system contract. + */ + constructor(address wzeta_, address uniswapv2Factory_, address uniswapv2Router02_) { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + wZetaContractAddress = wzeta_; + uniswapv2FactoryAddress = uniswapv2Factory_; + uniswapv2Router02Address = uniswapv2Router02_; + emit SystemContractDeployed(); + } + + /** + * @dev Deposit foreign coins into ZRC20 and call user specified contract on zEVM. + * @param context, context data for deposit. + * @param zrc20, zrc20 address for deposit. + * @param amount, amount to deposit. + * @param target, contract address to make a call after deposit. + * @param message, calldata for a call. + */ + function depositAndCall( + zContext calldata context, + address zrc20, + uint256 amount, + address target, + bytes calldata message + ) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + if (target == FUNGIBLE_MODULE_ADDRESS || target == address(this)) revert InvalidTarget(); + + IZRC20(zrc20).deposit(target, amount); + zContract(target).onCrossChainCall(context, zrc20, amount, message); + } + + /** + * @dev Sort token addresses lexicographically. Used to handle return values from pairs sorted in the order. + * @param tokenA, tokenA address. + * @param tokenB, tokenB address. + * @return token0 token1, returns sorted token addresses,. + */ + function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + if (tokenA == tokenB) revert CantBeIdenticalAddresses(); + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + if (token0 == address(0)) revert CantBeZeroAddress(); + } + + /** + * @dev Calculates the CREATE2 address for a pair without making any external calls. + * @param factory, factory address. + * @param tokenA, tokenA address. + * @param tokenB, tokenB address. + * @return pair tokens pair address. + */ + function uniswapv2PairFor(address factory, address tokenA, address tokenB) public pure returns (address pair) { + (address token0, address token1) = sortTokens(tokenA, tokenB); + pair = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encodePacked(token0, token1)), + hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash + ) + ) + ) + ) + ); + } + + /** + * @dev Fungible module updates the gas price oracle periodically. + * @param chainID, chain id. + * @param price, new gas price. + */ + function setGasPrice(uint256 chainID, uint256 price) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + gasPriceByChainId[chainID] = price; + emit SetGasPrice(chainID, price); + } + + /** + * @dev Setter for gasCoinZRC20ByChainId map. + * @param chainID, chain id. + * @param zrc20, ZRC20 address. + */ + function setGasCoinZRC20(uint256 chainID, address zrc20) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + gasCoinZRC20ByChainId[chainID] = zrc20; + emit SetGasCoin(chainID, zrc20); + } + + /** + * @dev Set the pool wzeta/erc20 address. + * @param chainID, chain id. + * @param erc20, pair for uniswap wzeta/erc20. + */ + function setGasZetaPool(uint256 chainID, address erc20) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + address pool = uniswapv2PairFor(uniswapv2FactoryAddress, wZetaContractAddress, erc20); + gasZetaPoolByChainId[chainID] = pool; + emit SetGasZetaPool(chainID, pool); + } + + /** + * @dev Setter for wrapped ZETA address. + * @param addr, wzeta new address. + */ + function setWZETAContractAddress(address addr) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + if (addr == address(0)) revert ZeroAddress(); + wZetaContractAddress = addr; + emit SetWZeta(wZetaContractAddress); + } + + /** + * @dev Setter for zetaConnector ZEVM Address + * @param addr, zeta connector new address. + */ + function setConnectorZEVMAddress(address addr) external { + if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule(); + if (addr == address(0)) revert ZeroAddress(); + zetaConnectorZEVMAddress = addr; + emit SetConnectorZEVM(zetaConnectorZEVMAddress); + } +} + + +// File @zetachain/toolkit/contracts/OnlySystem.sol@v9.0.1 + +// Original license: SPDX_License_Identifier: MIT +pragma solidity ^0.8.7; + +contract OnlySystem { + error OnlySystemContract(string); + + modifier onlySystem(SystemContract systemContract) { + if (msg.sender != address(systemContract)) { + revert OnlySystemContract( + "Only system contract can call this function" + ); + } + _; + } +} + + +// File contracts/UniversalApp.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity 0.8.7; + + + +contract UniversalApp is zContract, OnlySystem { + SystemContract public systemContract; + + constructor(address systemContractAddress) { + systemContract = SystemContract(systemContractAddress); + } + + function onCrossChainCall( + zContext calldata context, + address zrc20, + uint256 amount, + bytes calldata message + ) external virtual override onlySystem(systemContract) { + // TODO: implement the logic + } +} + + +// File contracts/Caller.sol + +// Original license: SPDX_License_Identifier: MIT +pragma solidity 0.8.7; + +contract Caller { + address constant tssAddress = 0x8531a5aB847ff5B22D855633C25ED1DA3255247e; + event DepositSuccess(address, address); + error DepositFailed(); + + function deposit(address universalAppAddress) public payable { + bytes memory data = abi.encodePacked(); + + (bool success, ) = payable(tssAddress).call{ + value: msg.value, + gas: 100000 + }(data); + + if (success) { + emit DepositSuccess(msg.sender, universalAppAddress); + } else { + revert DepositFailed(); + } + } +} diff --git a/omnichain/example/hardhat.config.ts b/omnichain/example/hardhat.config.ts index b28f6f15..3caf41fe 100644 --- a/omnichain/example/hardhat.config.ts +++ b/omnichain/example/hardhat.config.ts @@ -1,5 +1,7 @@ import "./tasks/interact"; import "./tasks/deploy"; +import "./tasks/deposit-caller"; +import "./tasks/deploy-caller"; import "@nomicfoundation/hardhat-toolbox"; import "@zetachain/toolkit/tasks"; diff --git a/omnichain/example/tasks/deploy-caller.ts b/omnichain/example/tasks/deploy-caller.ts new file mode 100644 index 00000000..3b7f2737 --- /dev/null +++ b/omnichain/example/tasks/deploy-caller.ts @@ -0,0 +1,23 @@ +import { getAddress, ParamChainName } from "@zetachain/protocol-contracts"; +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const [signer] = await hre.ethers.getSigners(); + if (signer === undefined) { + throw new Error( + `Wallet not found. Please, run "npx hardhat account --save" or set PRIVATE_KEY env variable (for example, in a .env file)` + ); + } + + const factory = await hre.ethers.getContractFactory("Caller"); + const contract = await factory.deploy(); + await contract.deployed(); + + console.log(contract.address); +}; + +task("deploy-caller", "Deploy the contract", main).addFlag( + "json", + "Output in JSON" +); diff --git a/omnichain/example/tasks/deposit-caller.ts b/omnichain/example/tasks/deposit-caller.ts new file mode 100644 index 00000000..9f8c7313 --- /dev/null +++ b/omnichain/example/tasks/deposit-caller.ts @@ -0,0 +1,20 @@ +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const main = async (args: any, hre: HardhatRuntimeEnvironment) => { + const factory = await hre.ethers.getContractFactory("Caller"); + const contract = factory.attach(args.caller); + + const tx = await contract.deposit(args.contract, { + value: hre.ethers.utils.parseUnits(args.amount, 18), + }); + await tx.wait(); + + console.log(tx.hash); +}; + +task("deposit-caller", "") + .addParam("contract", "Universal app contract address") + .addParam("caller", "Contract address") + .addParam("amount", "The amount of ETH to send") + .setAction(main);