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

POC of cx chain stream withdraw #1

Merged
merged 8 commits into from
Jan 13, 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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ cache/
artifacts/
deployments/
.openzeppelin/

abi/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ npx hardhat --network fuji ownerOf --token-id 1 --contract ONFT721Mock

OmniCounter is a simple contract with a counter. You can only *remotely* increment the counter!

* Deployment uses package `hardhat-deploy`
* `deploy` is a task created in `deploy/OmniCounter.js`. Other tasks are in `tasks/index.js`
* Endpoint contract addresses are defined in `constants/layerzeroEndpoints.json`.

1. Deploy both OmniCounters:

```shell
Expand Down
Binary file removed audit/Ackee_Audit_Solidty_Examples_Aug_8.pdf
Binary file not shown.
Binary file removed audit/Ackee_Audit_Solidty_Examples_July_27.pdf
Binary file not shown.
Binary file removed audit/Ackee_Audit_Solidty_Examples_May_3.pdf
Binary file not shown.
Binary file removed audit/Zellic_Audit_Solidity_Examples_May_21.pdf
Binary file not shown.
19 changes: 19 additions & 0 deletions contracts/examples/MFLToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract MFLToken is ERC20 {
constructor() ERC20("MFL Token", "MFL") {
_mint(msg.sender, 100 * 10**uint(decimals()));
}

function decimals() public pure override returns (uint8) {
return 8;
}

function mint() public {
require(totalSupply() + 100 * 10**uint(decimals()) <= 999999999 * 10**uint(decimals()), "Token cap reached");
_mint(msg.sender, 100 * 10**uint(decimals()));
}
}
160 changes: 160 additions & 0 deletions contracts/examples/MoveflowCrosschain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
pragma abicoder v2;

import "../lzApp/NonblockingLzApp.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title A LayerZero app for cross-chain asset streaming
contract MoveflowCrosschain is NonblockingLzApp {
mapping(bytes => address) public r2lCoinMap; // remote to local coin map
mapping(address => bytes) public l2rCoinMap; // local to remote coin map
mapping(address => address) public coinPoolMap; // local coin to pool
address public deployer;
enum PacketType {
SEND_TO_APTOS,
RECEIVE_FROM_APTOS
}

event CoinRegister(address localCoin);
event FundRecipientCx(address to, address token, uint64 amount);
event WithdrawReqCx(address from, uint16 dstChainId, uint64 streamId);

constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {
deployer = msg.sender;
}

modifier onlyDeployer() {
require(msg.sender == deployer, "Only deployer can access the function");
_;
}

function _nonblockingLzReceive(
uint16 _srcChainId,
bytes memory _srcAddress, /*_srcAddress*/
uint64, /*_nonce*/
bytes memory _payload
) internal override {
fundRecipient(_payload);
}

function fundRecipient(bytes memory payload) internal {
// Todo: nonce verification
// parse the payload
// extract 3 variables: recipient address, coin type, amount
(address to, address token, uint64 amount) = _decodeReceivePayload(payload);

// coin must be registered
require(l2rCoinMap[token].length != 0, "Coin not registered!");

// the pool must have been registgered
// CrossChainPool notSet = CrossChainPool(address(0));
require(coinPoolMap[token] != address(0x0), "Crosschain coin pool not registered!");

CrossChainPool ccPool = CrossChainPool(coinPoolMap[token]);
// balance in pool must >= amount
require(IERC20(token).balanceOf(address(ccPool)) >= amount, "Insurfficient cx coin pool balance!");

// transfer from pool to recipient
ccPool.transferTokens(to, amount);

emit FundRecipientCx(to, token, amount);
}

function registerCoin(bytes memory remoteCoin, address localCoin) onlyDeployer public {
r2lCoinMap[remoteCoin] = localCoin;
l2rCoinMap[localCoin] = remoteCoin;

// create cross chain pool for localCoin
CrossChainPool pool = new CrossChainPool(localCoin);
coinPoolMap[localCoin] = address(pool);
emit CoinRegister(localCoin);
}

function crossChainPoolDeposit(address coin, uint256 amount) public {
// the coin must have been registgered
require(l2rCoinMap[coin].length != 0, "Coin not registered!");

// the pool must have been registgered
require(coinPoolMap[coin] != address(0x0), "Crosschain coin pool not registered!");

CrossChainPool ccPool = CrossChainPool(coinPoolMap[coin]);

// sender balance must >= the amount
require(IERC20(coin).balanceOf(address(msg.sender)) >= amount, "Insurfficient sender balance!");
ccPool.depositTokens(msg.sender, amount);
}

function estimateFee(
uint16 _dstChainId,
bool _useZro,
bytes calldata _adapterParams,
uint64 _streamId
) public view returns (uint nativeFee, uint zroFee) {
bytes memory payload = abi.encodePacked(uint8(PacketType.SEND_TO_APTOS), _streamId);
return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams);
}

function withdrawCrossChainReq(uint16 _dstChainId, uint64 _streamId) public payable {
bytes memory payload = abi.encodePacked(uint8(PacketType.SEND_TO_APTOS), _streamId);
_lzSend(_dstChainId, payload, payable(msg.sender), address(0x0), bytes(""), msg.value);
emit WithdrawReqCx(msg.sender, _dstChainId, _streamId);
}

function setOracle(uint16 dstChainId, address oracle) external onlyOwner {
uint TYPE_ORACLE = 6;
lzEndpoint.setConfig(lzEndpoint.getSendVersion(address(this)), dstChainId, TYPE_ORACLE, abi.encode(oracle));
}

function getOracle(uint16 remoteChainId) external view returns (address _oracle) {
bytes memory bytesOracle = lzEndpoint.getConfig(lzEndpoint.getSendVersion(address(this)), remoteChainId, address(this), 6);
assembly {
_oracle := mload(add(bytesOracle, 32))
}
}

// receive payload: packet type(1) + receiver(32) + remote token(32) + amount(8)
function _decodeReceivePayload(bytes memory _payload) public pure
returns (
address to,
address token,
uint64 amountSD
// bool unwrap // Todo: what's unwrap
)
{
require(_payload.length == 128, "Moveflow crosschain: invalid payload length");
(uint8 packetType, address decodedTo, address decodedToken, uint64 decodedAmountSD) = abi.decode(_payload, (uint8, address, address, uint64));
require(PacketType(packetType) == PacketType.RECEIVE_FROM_APTOS, "Moveflow crosschain: invalid packet type");
return (decodedTo, decodedToken, decodedAmountSD);
}
}

contract CrossChainPool {
IERC20 public token;
address public deployer;

event DepositCx(address from, uint256 amount);
event TransferCx(address from, address to, uint256 amount);

modifier onlyDeployer() {
require(msg.sender == deployer, "Only deployer can access the function");
_;
}

constructor(address _tokenAddress) {
deployer = msg.sender;
token = IERC20(_tokenAddress);
}

function depositTokens(address sender, uint256 amount) public {
// Transfer the tokens from the sender to this contract
require(token.transferFrom(sender, address(this), amount), "Deposit failed");
emit DepositCx(sender, amount);
}

function transferTokens(address to, uint256 amount) onlyDeployer external {
require(token.transfer(to, amount), "Transfer failed");
emit TransferCx(msg.sender, to, amount);
}
}
14 changes: 14 additions & 0 deletions deploy/MFLToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = async function ({ deployments, getNamedAccounts }) {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()
console.log(`>>> your address: ${deployer}`)

await deploy("MFLToken", {
from: deployer,
args: [],
log: true,
waitConfirmations: 1,
})
}

module.exports.tags = ["MFLToken"]
20 changes: 20 additions & 0 deletions deploy/MoveflowCrosschain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json")

module.exports = async function ({ deployments, getNamedAccounts }) {
const { deploy } = deployments
const { deployer } = await getNamedAccounts()
console.log(`>>> your address: ${deployer}`)

// get the Endpoint address
const endpointAddr = LZ_ENDPOINTS[hre.network.name]
console.log(`[${hre.network.name}] Endpoint address: ${endpointAddr}`)

await deploy("MoveflowCrosschain", {
from: deployer,
args: [endpointAddr],
log: true,
waitConfirmations: 1,
})
}

module.exports.tags = ["MoveflowCrosschain"]
8 changes: 7 additions & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ require("hardhat-deploy-ethers")
require("@openzeppelin/hardhat-upgrades")
require("./tasks")

const { BSCSCAN_API_KEY } = process.env;

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
Expand Down Expand Up @@ -149,7 +151,7 @@ module.exports = {
accounts: accounts(),
},
"bsc-testnet": {
url: "https://data-seed-prebsc-1-s1.binance.org:8545/",
url: "https://go.getblock.io/7b3534a27b7947dda6070eb44789e4be/",
chainId: 97,
accounts: accounts(),
},
Expand Down Expand Up @@ -179,4 +181,8 @@ module.exports = {
accounts: accounts(),
},
},
etherscan: {
// Your API key for Etherscan
apiKey: BSCSCAN_API_KEY,
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
},
"dependencies": {
"@layerzerolabs/lz-evm-sdk-v1-0.7": "^1.5.14",
"@openzeppelin/contracts": "^4.4.1",
"@openzeppelin-3/contracts": "npm:@openzeppelin/contracts@^3.4.2-solc-0.7",
"@openzeppelin/contracts": "^4.4.1",
"@openzeppelin/contracts-upgradeable": "^4.6.0",
"@openzeppelin/hardhat-upgrades": "^1.18.3",
"erc721a": "^4.2.3",
"dotenv": "^10.0.0",
"erc721a": "^4.2.3",
"hardhat": "^2.8.0",
"hardhat-contract-sizer": "^2.1.1",
"hardhat-deploy": "^0.10.5",
Expand Down
43 changes: 43 additions & 0 deletions tasks/fundRecipient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const CHAIN_ID = require("../constants/chainIds.json")
const { getDeploymentAddresses } = require("../utils/readStatic")
const dotenv = require("dotenv");
dotenv.config();

const { getAddress } = require("ethers/lib/utils");
// const ENDPOINT_HTTP = 'https://rpc.ankr.com/bsc_testnet_chapel';
const ENDPOINT_HTTP = "https://go.getblock.io/7b3534a27b7947dda6070eb44789e4be";
// const ENDPOINT_HTTP = "https://bsc-testnet.blastapi.io/c5accb66-4edf-43b4-9e71-698b48a64ba4";
// const ENDPOINT_HTTP = "https://bsc-testnet.blockpi.network/v1/rpc/public";

const mflAddress = "0xDE3a190D9D26A8271Ae9C27573c03094A8A2c449";
const gasLimit = 8000000;

// remote aptos testnet id
const remoteChainId = 10108

module.exports = async function (taskArgs, hre) {
console.log("bsc-testnet fundRecipient");
const providerHttpAch = new ethers.providers.JsonRpcProvider(ENDPOINT_HTTP, 97);
const wallet = ethers.Wallet.fromMnemonic(process.env.MNEMONIC);
const signer = wallet.connect(providerHttpAch);
// console.log("bsc-testnet setTrustedRemote.Aptos signer: ", signer);
const nonce = await providerHttpAch.getTransactionCount(wallet.address);
console.log("nonce", nonce);
let gasPrice = (await providerHttpAch.getGasPrice());
console.log("gasPrice", gasPrice)

const localContractInstance = await ethers.getContract("MoveflowCrossschain");
const localContractInstanceRead = await ethers.getContract("MoveflowCrossschain");
const trustedRemoteRe = await localContractInstanceRead.trustedRemoteLookup(remoteChainId)
console.log("trustedRemoteRe", trustedRemoteRe);

let payload = hre.ethers.utils.solidityPack(["uint8", "address", "address", "uint64"],
[1, wallet.address, mflAddress, 13]);
console.log(payload, payload.length);

const fundRecipientRe = await(await localContractInstance.fundRecipient(payload, {
gasPrice,
gasLimit,
})).wait();
console.log("fundRecipient", fundRecipientRe);
}
11 changes: 11 additions & 0 deletions tasks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ task("incrementCounter", "increment the destination OmniCounter", require("./inc
"the target network name, ie: fuji, or mumbai, etc (from hardhat.config.js)"
)

//
task("moveflowCrosschain", "get oracle", require("./moveflowCrosschain")).addParam(
"targetNetwork",
"the target network name, ie: fuji, or mumbai, etc (from hardhat.config.js)"
)

//
task("setTrustedRemote.Aptos", "setTrustedRemote Aptos", require("./setTrustedRemote.Aptos"))
task("withdrawFrom.Aptos", "withdraw from Aptos", require("./withdrawFrom.Aptos"))
task("fundRecipient", "fundRecipient on EVM", require("./fundRecipient"))

// npx hardhat deployWireCheck --e testnet --contract ExampleOFT --proxy-contract ExampleBasedOFT --proxy-chain optimism-kovan
// npx hardhat deployWireCheck --e testnet --contract ExampleUniversalONFT721
task("deployWireCheck", "", require("./deployWireCheck"))
Expand Down
33 changes: 33 additions & 0 deletions tasks/moveflowCrosschain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const CHAIN_ID = require("../constants/chainIds.json")
const ENDPOINTS = require("../constants/layerzeroEndpoints.json")

// read status of moveflow evm contract on testnet
module.exports = async function (taskArgs, hre) {
const remoteChainId = CHAIN_ID[taskArgs.targetNetwork];
// const moveflowCrosschain = await ethers.getContract("MoveflowCrosschain")

const ENDPOINT_HTTP_ACHM = 'https://rpc.ankr.com/bsc_testnet_chapel';
const providerHttpAch = new ethers.providers.JsonRpcProvider(ENDPOINT_HTTP_ACHM, 97);
const moveflowCrosschain = await ethers.getContract("MoveflowCrossschain");
// const moveflowCrosschain = await ethers.getContract("MoveflowCrosschain", providerHttpAch.getSigner());

/*
// quote fee with default adapterParams
const adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 200000]) // default adapterParams example

// const payload = await omniCounter.PAYLOAD()
const endpoint = await ethers.getContractAt("ILayerZeroEndpoint", ENDPOINTS[hre.network.name])
const fees = await endpoint.estimateFees(remoteChainId, moveflowCrosschain.address, adapterParams, false, adapterParams)
console.log(`fees[0] (wei): ${fees[0]} / (eth): ${ethers.utils.formatEther(fees[0])}`)
*/

let tx = await moveflowCrosschain.getOracle(10108)
console.log(`✅ Message Sent [${hre.network.name}] moveflowCrosschain on destination Moveflow @ [${remoteChainId}]`)
console.log(`tx: ${tx}`)

console.log(``)
console.log(`Note: to poll/wait for the message to arrive on the destination use the command:`)
console.log(` (it may take a minute to arrive, be patient!)`)
console.log("")
console.log(` $ npx hardhat --network ${taskArgs.targetNetwork} ocPoll`)
}
Loading
Loading