├── .env.example ├── resources └── LayerZeroLogo.png ├── audit ├── Ackee_Audit_Solidty_Examples_Aug_8.pdf ├── Ackee_Audit_Solidty_Examples_May_3.pdf ├── Ackee_Audit_Solidty_Examples_July_27.pdf └── Zellic_Audit_Solidity_Examples_May_21.pdf ├── .solhint.json ├── tasks ├── getSigners.js ├── approveERC1155.js ├── setMinDstGas.js ├── oftMint.js ├── ownerOf.js ├── ocPoll.js ├── sendONFT1155.js ├── omniCounterSetOracle.js ├── ocGetOracle.js ├── onftMint.js ├── pingPongSetTrustedRemote.js ├── isFailedMessage.js ├── ping.js ├── sendProxyONFT1155.js ├── omniCounterIncrementWithParamsV1.js ├── isStoredPayload.js ├── incrementCounter.js ├── onftSend.js ├── omniCounterIncrementWithParamsV2.js ├── checkWireUp.js ├── batchSendONFT1155.js ├── batchSendProxyONFT1155.js ├── getMessageFailedEvent.js ├── setTrustedRemote.js ├── oftSend.js ├── oftv2Send.js ├── getStoredPayloadEvent.js ├── deployWireCheck.js ├── cachedSwapSavedParse.js └── checkWireUpAll.js ├── contracts ├── token │ ├── oft │ │ ├── v1 │ │ │ ├── interfaces │ │ │ │ ├── IOFT.sol │ │ │ │ └── IOFTCore.sol │ │ │ ├── mocks │ │ │ │ └── OFTMock.sol │ │ │ ├── ProxyOFT.sol │ │ │ ├── OFT.sol │ │ │ ├── OFTCore.sol │ │ │ └── NativeOFT.sol │ │ └── v2 │ │ │ ├── mocks │ │ │ ├── OFTV2Mock.sol │ │ │ └── OFTStakingMockV2.sol │ │ │ ├── interfaces │ │ │ ├── IOFTReceiverV2.sol │ │ │ ├── IOFTV2.sol │ │ │ └── ICommonOFT.sol │ │ │ ├── fee │ │ │ ├── IOFTWithFee.sol │ │ │ ├── OFTWithFee.sol │ │ │ ├── Fee.sol │ │ │ ├── BaseOFTWithFee.sol │ │ │ ├── ProxyOFTWithFee.sol │ │ │ └── NativeOFTWithFee.sol │ │ │ ├── README.md │ │ │ ├── OFTV2.sol │ │ │ ├── BaseOFTV2.sol │ │ │ ├── ProxyOFTV2.sol │ │ │ └── NativeOFTV2.sol │ ├── onft721 │ │ ├── interfaces │ │ │ ├── IONFT721.sol │ │ │ └── IONFT721Core.sol │ │ ├── mocks │ │ │ ├── ONFT721Mock.sol │ │ │ ├── ONFT721AMock.sol │ │ │ └── ERC721Mock.sol │ │ ├── ONFT721.sol │ │ ├── ProxyONFT721.sol │ │ └── ONFT721A.sol │ └── onft1155 │ │ ├── interfaces │ │ ├── IONFT1155.sol │ │ └── IONFT1155Core.sol │ │ ├── mocks │ │ └── ERC1155Mock.sol │ │ ├── ONFT1155.sol │ │ ├── ProxyONFT1155.sol │ │ └── ONFT1155Core.sol ├── mocks │ └── ERC20Mock.sol ├── lzApp │ ├── interfaces │ │ ├── ILayerZeroReceiver.sol │ │ ├── ILayerZeroUserApplicationConfig.sol │ │ └── ILayerZeroEndpoint.sol │ ├── libs │ │ └── LzLib.sol │ └── NonblockingLzApp.sol ├── examples │ ├── OmniCounter.sol │ ├── PingPong.sol │ └── GasDrop.sol └── libraries │ └── ExcessivelySafeCall.sol ├── constants ├── environments.json ├── chainIds.json └── layerzeroEndpoints.json ├── .github └── workflows │ └── main.yaml ├── utils ├── helpers.js ├── readStatic.js └── getAddresses.js ├── deploy ├── ERC1155.js ├── OFT.js ├── ONFT1155.js ├── ProxyOFT.js ├── OmniCounter.js ├── ExampleOFT.js ├── ProxyONFT1155.js ├── ONFT721.js ├── ExampleOFTV2.js └── PingPong.js ├── docs └── base-layerzero-builder-notes.md ├── .prettierrc.js ├── package.json ├── .gitignore ├── test ├── examples │ ├── OmniCounter.test.js │ └── PingPong.test.js └── oft │ └── v2 │ └── ComposableProxyOFTV2.test.js └── hardhat.config.js /.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC="test test test test test test test test test test test junk" 2 | -------------------------------------------------------------------------------- /resources/LayerZeroLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stalim17/endpoint-v1-solidity-examples/HEAD/resources/LayerZeroLogo.png -------------------------------------------------------------------------------- /audit/Ackee_Audit_Solidty_Examples_Aug_8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stalim17/endpoint-v1-solidity-examples/HEAD/audit/Ackee_Audit_Solidty_Examples_Aug_8.pdf -------------------------------------------------------------------------------- /audit/Ackee_Audit_Solidty_Examples_May_3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stalim17/endpoint-v1-solidity-examples/HEAD/audit/Ackee_Audit_Solidty_Examples_May_3.pdf -------------------------------------------------------------------------------- /audit/Ackee_Audit_Solidty_Examples_July_27.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stalim17/endpoint-v1-solidity-examples/HEAD/audit/Ackee_Audit_Solidty_Examples_July_27.pdf -------------------------------------------------------------------------------- /audit/Zellic_Audit_Solidity_Examples_May_21.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stalim17/endpoint-v1-solidity-examples/HEAD/audit/Zellic_Audit_Solidity_Examples_May_21.pdf -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "rules": { 4 | "avoid-suicide": "error", 5 | "avoid-sha3": "warn", 6 | "max-line-length": ["warn", 300] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tasks/getSigners.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | const signers = await ethers.getSigners() 3 | for (let i = 0; i < taskArgs.n; ++i) { 4 | console.log(`${i}) ${signers[i].address}`) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/interfaces/IOFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "./IOFTCore.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | 8 | /** 9 | * @dev Interface of the OFT standard 10 | */ 11 | interface IOFT is IOFTCore, IERC20 { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/token/onft721/interfaces/IONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "./IONFT721Core.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 7 | 8 | /** 9 | * @dev Interface of the ONFT standard 10 | */ 11 | interface IONFT721 is IONFT721Core, IERC721 { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/token/onft1155/interfaces/IONFT1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "./IONFT1155Core.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 7 | 8 | /** 9 | * @dev Interface of the ONFT standard 10 | */ 11 | interface IONFT1155 is IONFT1155Core, IERC1155 { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /constants/environments.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainnet": [ 3 | "ethereum", 4 | "bsc", 5 | "avalanche", 6 | "polygon", 7 | "arbitrum", 8 | "optimism", 9 | "fantom" 10 | ], 11 | "testnet": [ 12 | "goerli", 13 | "bsc-testnet", 14 | "fuji", 15 | "mumbai", 16 | "arbitrum-goerli", 17 | "optimism-goerli", 18 | "fantom-testnet" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /contracts/mocks/ERC20Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | // this is a MOCK 7 | contract ERC20Mock is ERC20 { 8 | constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} 9 | 10 | function mint(address _to, uint _amount) public { 11 | _mint(_to, _amount); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tasks/approveERC1155.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | const ERC1155 = await ethers.getContractFactory("ERC1155") 3 | const erc1155 = await ERC1155.attach(taskArgs.addr) 4 | const proxyONFT1155 = await ethers.getContract("ProxyONFT1155") 5 | let tx = await (await erc1155.setApprovalForAll(proxyONFT1155.address, true)).wait() 6 | console.log(`setApprovalForAll tx: ${tx.transactionHash}`) 7 | } 8 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/mocks/OFTV2Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../OFTV2.sol"; 6 | 7 | // @dev mock OFTV2 demonstrating how to inherit OFTV2 8 | contract OFTV2Mock is OFTV2 { 9 | constructor(address _layerZeroEndpoint, uint _initialSupply, uint8 _sharedDecimals) OFTV2("ExampleOFT", "OFT", _sharedDecimals, _layerZeroEndpoint) { 10 | _mint(_msgSender(), _initialSupply); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: unit tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: Use Node.js 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: '18' 16 | 17 | - name: Install dependencies 18 | run: yarn install 19 | 20 | - name: Run unit tests 21 | run: yarn test 22 | -------------------------------------------------------------------------------- /tasks/setMinDstGas.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | const contract = await ethers.getContract(taskArgs.contract) 5 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 6 | const tx = await contract.setMinDstGas(dstChainId, taskArgs.packetType, taskArgs.minGas) 7 | 8 | console.log(`[${hre.network.name}] setMinDstGas tx hash ${tx.hash}`) 9 | await tx.wait() 10 | } 11 | -------------------------------------------------------------------------------- /tasks/oftMint.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | let owner = (await ethers.getSigners())[0] 3 | let toAddress = owner.address 4 | let qty = ethers.utils.parseEther(taskArgs.qty) 5 | 6 | const oftMock = await ethers.getContract("OFTMock") 7 | 8 | let tx = await ( 9 | await oftMock.mintTokens(toAddress, qty) 10 | ).wait() 11 | console.log(`✅ OFT minted [${hre.network.name}] to: [${toAddress}] qty: [${qty}]`) 12 | } 13 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/mocks/OFTMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../OFT.sol"; 6 | 7 | // @dev example implementation inheriting a OFT 8 | contract OFTMock is OFT { 9 | constructor(address _layerZeroEndpoint) OFT("MockOFT", "OFT", _layerZeroEndpoint) {} 10 | 11 | // @dev WARNING public mint function, do not use this in production 12 | function mintTokens(address _to, uint256 _amount) external { 13 | _mint(_to, _amount); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /constants/chainIds.json: -------------------------------------------------------------------------------- 1 | { 2 | "ethereum": 101, 3 | "bsc": 102, 4 | "avalanche": 106, 5 | "polygon": 109, 6 | "arbitrum": 110, 7 | "optimism": 111, 8 | "fantom": 112, 9 | 10 | "goerli": 10121, 11 | "bsc-testnet": 10102, 12 | "fuji": 10106, 13 | "mumbai": 10109, 14 | "arbitrum-goerli": 10143, 15 | "optimism-goerli": 10132, 16 | "fantom-testnet": 10112, 17 | "meter-testnet": 10156, 18 | "zksync-testnet": 10165 19 | } -------------------------------------------------------------------------------- /tasks/ownerOf.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | let contract = await ethers.getContract(taskArgs.contract) 3 | let tokenId = taskArgs.tokenId 4 | 5 | try { 6 | let address = await contract.ownerOf(tokenId) 7 | console.log(`✅ [${hre.network.name}] ownerOf(${tokenId})`) 8 | console.log(` Owner address: ${address}`) 9 | } catch (e) { 10 | if (e?.reason) { 11 | console.log(e.reason) 12 | } else { 13 | console.log(e) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | const { BigNumber } = require("ethers") 2 | const { ethers, network } = require("hardhat") 3 | 4 | deployNew = async (contractName, params = []) => { 5 | const C = await ethers.getContractFactory(contractName) 6 | return C.deploy(...params) 7 | } 8 | 9 | deployNewFromAbi = async(abi, bytecode, signer, params) => { 10 | const C = new ethers.ContractFactory(abi, bytecode, signer) 11 | if (params) { 12 | return C.deploy(...params) 13 | } else { 14 | return C.deploy() 15 | } 16 | } 17 | 18 | module.exports = { 19 | deployNew, 20 | deployNewFromAbi 21 | } -------------------------------------------------------------------------------- /tasks/ocPoll.js: -------------------------------------------------------------------------------- 1 | function sleep(millis) { 2 | return new Promise((resolve) => setTimeout(resolve, millis)) 3 | } 4 | 5 | module.exports = async function (taskArgs, hre) { 6 | console.log(`owner:`, (await ethers.getSigners())[0].address) 7 | const omniCounter = await ethers.getContract("OmniCounter") 8 | console.log(`omniCounter: ${omniCounter.address}`) 9 | 10 | while (true) { 11 | let counter = await omniCounter.counter() 12 | console.log(`[${hre.network.name}] ${new Date().toISOString().split("T")[1].split(".")[0]} counter... ${counter}`) 13 | await sleep(1000) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tasks/sendONFT1155.js: -------------------------------------------------------------------------------- 1 | const CHAIN_IDS = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | const signers = await ethers.getSigners() 5 | const owner = signers[0] 6 | const onft1155 = await ethers.getContract("ONFT1155") 7 | const dstChainId = CHAIN_IDS[taskArgs.targetNetwork] 8 | let tx = await ( 9 | await onft1155.send(dstChainId, owner.address, taskArgs.tokenId, taskArgs.quantity, owner.address, ethers.constants.AddressZero, "0x", { 10 | value: ethers.utils.parseEther(taskArgs.msgValue), 11 | }) 12 | ).wait() 13 | console.log(`send tx: ${tx.transactionHash}`) 14 | } 15 | -------------------------------------------------------------------------------- /deploy/ERC1155.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | await deploy("ERC1155Mock", { 12 | from: deployer, 13 | args: ["test,com"], 14 | log: true, 15 | waitConfirmations: 1, 16 | }) 17 | } 18 | 19 | module.exports.tags = ["ERC1155Mock"] 20 | -------------------------------------------------------------------------------- /deploy/OFT.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | await deploy("OFT", { 12 | from: deployer, 13 | args: ["Name", "Symbol", lzEndpointAddress, 0], 14 | log: true, 15 | waitConfirmations: 1, 16 | }) 17 | } 18 | 19 | module.exports.tags = ["OFT"] 20 | -------------------------------------------------------------------------------- /deploy/ONFT1155.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | await deploy("ONFT1155", { 12 | from: deployer, 13 | args: ["ipfs:/", lzEndpointAddress], 14 | log: true, 15 | waitConfirmations: 1, 16 | }) 17 | } 18 | 19 | module.exports.tags = ["ONFT1155"] 20 | -------------------------------------------------------------------------------- /tasks/omniCounterSetOracle.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 5 | // get local contract instance 6 | const omniCounter = await ethers.getContract("OmniCounter") 7 | console.log(`omniCounter.address: ${omniCounter.address}`) 8 | 9 | // set the config for this UA to use the specified Oracle 10 | let tx = await (await omniCounter.setOracle(dstChainId, taskArgs.oracle)).wait() 11 | console.log(`... set Oracle[${taskArgs.oracle}] for [${hre.network.name}] OmniCounter -> dst [${dstChainId}]`) 12 | console.log(`tx: ${tx.transactionHash}`) 13 | } 14 | -------------------------------------------------------------------------------- /deploy/ProxyOFT.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | await deploy("ProxyOFT", { 12 | from: deployer, 13 | args: [lzEndpointAddress, "0x000000000000000000"], 14 | log: true, 15 | waitConfirmations: 1, 16 | }) 17 | } 18 | 19 | module.exports.tags = ["ProxyOFT"] 20 | -------------------------------------------------------------------------------- /deploy/OmniCounter.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | // get the Endpoint address 9 | const endpointAddr = LZ_ENDPOINTS[hre.network.name] 10 | console.log(`[${hre.network.name}] Endpoint address: ${endpointAddr}`) 11 | 12 | await deploy("OmniCounter", { 13 | from: deployer, 14 | args: [endpointAddr], 15 | log: true, 16 | waitConfirmations: 1, 17 | }) 18 | } 19 | 20 | module.exports.tags = ["OmniCounter"] 21 | -------------------------------------------------------------------------------- /deploy/ExampleOFT.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | 7 | console.log(`>>> your address: ${deployer}`) 8 | 9 | // get the Endpoint address 10 | const endpointAddr = LZ_ENDPOINTS[hre.network.name] 11 | console.log(`[${hre.network.name}] LayerZero Endpoint address: ${endpointAddr}`) 12 | 13 | await deploy("OFTMock", { 14 | from: deployer, 15 | args: [endpointAddr], 16 | log: true, 17 | waitConfirmations: 1, 18 | }) 19 | } 20 | 21 | module.exports.tags = ["ExampleOFT"] 22 | -------------------------------------------------------------------------------- /contracts/lzApp/interfaces/ILayerZeroReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | interface ILayerZeroReceiver { 6 | // @notice LayerZero endpoint will invoke this function to deliver the message on the destination 7 | // @param _srcChainId - the source endpoint identifier 8 | // @param _srcAddress - the source sending contract address from the source chain 9 | // @param _nonce - the ordered message nonce 10 | // @param _payload - the signed payload is the UA bytes has encoded to be sent 11 | function lzReceive( 12 | uint16 _srcChainId, 13 | bytes calldata _srcAddress, 14 | uint64 _nonce, 15 | bytes calldata _payload 16 | ) external; 17 | } 18 | -------------------------------------------------------------------------------- /deploy/ProxyONFT1155.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | await deploy("ProxyONFT1155", { 12 | from: deployer, 13 | args: [lzEndpointAddress, "0x76BE3b62873462d2142405439777e971754E8E77"], 14 | log: true, 15 | waitConfirmations: 1, 16 | }) 17 | } 18 | 19 | module.exports.tags = ["ProxyONFT1155"] 20 | -------------------------------------------------------------------------------- /tasks/ocGetOracle.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | const TYPE_ORACLE = 6 4 | 5 | module.exports = async function (taskArgs, hre) { 6 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 7 | // get the local contract 8 | const omniCounter = await ethers.getContract("OmniCounter") 9 | console.log(`omniCounter.address: ${omniCounter.address}`) 10 | 11 | // set the config for this UA to use the specified Oracle 12 | let data = await omniCounter.getConfig( 13 | 0, // unused 14 | dstChainId, 15 | omniCounter.address, 16 | TYPE_ORACLE 17 | ) 18 | 19 | console.log(`✅ Oracle for src (${hre.network.name}) -> dst [${dstChainId}]: ${data.replace("000000000000000000000000", "")}`) 20 | } 21 | -------------------------------------------------------------------------------- /tasks/onftMint.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | let contract = await ethers.getContract(taskArgs.contract) 3 | 4 | try { 5 | let tx = await (await contract.mint(taskArgs.toAddress, taskArgs.tokenId)).wait() 6 | console.log(`✅ [${hre.network.name}] mint()`) 7 | console.log(` tx: ${tx.transactionHash}`) 8 | let onftTokenId = await ethers.provider.getTransactionReceipt(tx.transactionHash) 9 | console.log(` ONFT nftId: ${parseInt(Number(onftTokenId.logs[0].topics[3]))}`) 10 | } catch (e) { 11 | if (e.error?.message.includes("ONFT: Max limit reached")) { 12 | console.log("*ONFT: Max limit reached*") 13 | } else { 14 | console.log(e) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/token/onft721/mocks/ONFT721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../ONFT721.sol"; 6 | 7 | contract ONFT721Mock is ONFT721 { 8 | constructor( 9 | string memory _name, 10 | string memory _symbol, 11 | uint _minGasToStore, 12 | address _layerZeroEndpoint 13 | ) ONFT721(_name, _symbol, _minGasToStore, _layerZeroEndpoint) {} 14 | 15 | function mint(address _tokenOwner, uint _newId) external payable { 16 | _safeMint(_tokenOwner, _newId); 17 | } 18 | 19 | function rawOwnerOf(uint tokenId) public view returns (address) { 20 | if (_exists(tokenId)) { 21 | return ownerOf(tokenId); 22 | } 23 | return address(0); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tasks/pingPongSetTrustedRemote.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | const { getDeploymentAddresses } = require("../utils/readStatic") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 6 | const dstPingPongAddr = getDeploymentAddresses(taskArgs.targetNetwork)["PingPong"] 7 | // get local contract instance 8 | const pingPong = await ethers.getContract("PingPong") 9 | console.log(`[source] pingPong.address: ${pingPong.address}`) 10 | 11 | let tx = await (await pingPong.setTrustedRemote(dstChainId, dstPingPongAddr)).wait() 12 | console.log(`✅ [${hre.network.name}] PingPong.setTrustedRemote( ${dstChainId}, ${dstPingPongAddr} )`) 13 | console.log(`...tx: ${tx.transactionHash}`) 14 | } 15 | -------------------------------------------------------------------------------- /deploy/ONFT721.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const { deploy } = deployments 5 | const { deployer } = await getNamedAccounts() 6 | console.log(`>>> your address: ${deployer}`) 7 | 8 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 9 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 10 | 11 | const name = "ONFT721Mock" 12 | const symbol = "SYM" 13 | const minGasToStore = 100000 14 | 15 | await deploy("ONFT721Mock", { 16 | from: deployer, 17 | args: [name, symbol, minGasToStore, lzEndpointAddress], 18 | log: true, 19 | waitConfirmations: 1, 20 | }) 21 | } 22 | 23 | module.exports.tags = ["ONFT721"] 24 | -------------------------------------------------------------------------------- /contracts/token/onft721/mocks/ONFT721AMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | import "../ONFT721A.sol"; 6 | 7 | // DISCLAIMER: This contract can only be deployed on one chain when deployed and calling 8 | // setTrustedRemotes with remote contracts. This is due to the sequential way 721A mints tokenIds. 9 | // This contract must be the first minter of each token id 10 | contract ONFT721AMock is ONFT721A { 11 | constructor( 12 | string memory _name, 13 | string memory _symbol, 14 | uint _minGasToTransferAndStore, 15 | address _layerZeroEndpoint 16 | ) ONFT721A(_name, _symbol, _minGasToTransferAndStore, _layerZeroEndpoint) {} 17 | 18 | function mint(uint _amount) external payable { 19 | _safeMint(msg.sender, _amount, ""); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/token/onft721/mocks/ERC721Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | // for mock purposes only, no limit on minting functionality 8 | contract ERC721Mock is ERC721 { 9 | constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} 10 | 11 | string public baseTokenURI; 12 | 13 | function mint(address to, uint tokenId) public { 14 | _safeMint(to, tokenId, ""); 15 | } 16 | 17 | function transfer(address to, uint tokenId) public { 18 | _safeTransfer(msg.sender, to, tokenId, ""); 19 | } 20 | 21 | function isApprovedOrOwner(address spender, uint tokenId) public view virtual returns (bool) { 22 | return _isApprovedOrOwner(spender, tokenId); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tasks/isFailedMessage.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | console.log({ taskArgs }) 3 | const nonBlockingApp = await ethers.getContractAt("NonblockingLzApp", taskArgs.desAddress) 4 | 5 | // concat remote and local address 6 | let remoteAndLocal = hre.ethers.utils.solidityPack(["address", "address"], [taskArgs.srcAddress, taskArgs.desAddress]) 7 | 8 | let bool = await nonBlockingApp.failedMessages(taskArgs.srcChainId, remoteAndLocal, taskArgs.nonce) 9 | console.log(`failedMessages: ${bool}`) 10 | } 11 | 12 | // npx hardhat failedMessage --network fuji --src-chain-id TBD --src-address TBD --des-address TBD --nonce TBD 13 | // npx hardhat failedMessage --network fuji --src-chain-id 101 --src-address 0x165192f89ea752f597203eeb14e8f5538bce799d --des-address 0x9add6f279394f7f3c7a61d3426a7f45e149261a4 --nonce 10 14 | -------------------------------------------------------------------------------- /tasks/ping.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | const { getDeploymentAddresses } = require("../utils/readStatic") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const targetNetwork = taskArgs.targetNetwork 6 | const totalPings = taskArgs.n 7 | 8 | const dstChainId = CHAIN_ID[targetNetwork] 9 | const dstPingPongAddr = getDeploymentAddresses(targetNetwork)["PingPong"] 10 | 11 | // get local contract instance 12 | const pingPong = await ethers.getContract("PingPong") 13 | console.log(`[source] pingPong.address: ${pingPong.address}`) 14 | 15 | let tx = await (await pingPong.ping(dstChainId, totalPings)).wait() 16 | 17 | console.log(`✅ Pings started! [${hre.network.name}] pinging with target chain [${dstChainId}] @ [${dstPingPongAddr}]`) 18 | console.log(`...tx: ${tx.transactionHash}`) 19 | } 20 | -------------------------------------------------------------------------------- /deploy/ExampleOFTV2.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | const { ethers } = require("hardhat") 3 | 4 | module.exports = async function ({ deployments, getNamedAccounts }) { 5 | const { deploy } = deployments 6 | const { deployer } = await getNamedAccounts() 7 | console.log(`>>> your address: ${deployer}`) 8 | 9 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 10 | console.log(`[${hre.network.name}] Endpoint Address: ${lzEndpointAddress}`) 11 | const globalSupply = ethers.utils.parseUnits("1000000", 18) 12 | const sharedDecimals = 6 13 | 14 | await deploy("OFTV2Mock", { 15 | from: deployer, 16 | args: [lzEndpointAddress, globalSupply, sharedDecimals], 17 | log: true, 18 | waitConfirmations: 1, 19 | }) 20 | } 21 | 22 | module.exports.tags = ["ExampleOFTV2"] 23 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/interfaces/IOFTReceiverV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | interface IOFTReceiverV2 { 6 | /** 7 | * @dev Called by the OFT contract when tokens are received from source chain. 8 | * @param _srcChainId The chain id of the source chain. 9 | * @param _srcAddress The address of the OFT token contract on the source chain. 10 | * @param _nonce The nonce of the transaction on the source chain. 11 | * @param _from The address of the account who calls the sendAndCall() on the source chain. 12 | * @param _amount The amount of tokens to transfer. 13 | * @param _payload Additional data with no specified format. 14 | */ 15 | function onOFTReceived(uint16 _srcChainId, bytes calldata _srcAddress, uint64 _nonce, bytes32 _from, uint _amount, bytes calldata _payload) external; 16 | } 17 | -------------------------------------------------------------------------------- /docs/base-layerzero-builder-notes.md: -------------------------------------------------------------------------------- 1 | # Base Builder Notes on LayerZero Solidity Examples 2 | 3 | LayerZero makes true cross-chain communication possible — and for Base builders, this repo is one of the best learning entry points. 4 | 5 | --- 6 | 7 | ### 1. Why It Matters 8 | Base thrives on interoperability. 9 | Being able to pass messages between Base, Arbitrum, and Ethereum Mainnet lets builders design richer dApps: multi-chain governance, asset bridges, or shared liquidity pools. 10 | 11 | --- 12 | 13 | ### 2. Deployment Notes 14 | - Always check the latest **Endpoint address** for Base from the LayerZero docs before deploying. 15 | - For testing, Base Sepolia (`chainId 84532`) can pair with Sepolia testnets of other L2s. 16 | - Update your Hardhat config: 17 | ```js 18 | base: { 19 | url: "https://base-mainnet.g.alchemy.com/v2/", 20 | chainId: 8453, 21 | accounts: [PRIVATE_KEY] 22 | } 23 | -------------------------------------------------------------------------------- /contracts/token/onft1155/mocks/ERC1155Mock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 6 | 7 | // for mock purposes only, no limit on minting functionality 8 | contract ERC1155Mock is ERC1155 { 9 | constructor(string memory uri_) ERC1155(uri_) {} 10 | 11 | function mint( 12 | address _to, 13 | uint _tokenId, 14 | uint _amount 15 | ) public { 16 | _mint(_to, _tokenId, _amount, ""); 17 | } 18 | 19 | function mintBatch( 20 | address _to, 21 | uint[] memory _tokenIds, 22 | uint[] memory _amounts 23 | ) public { 24 | _mintBatch(_to, _tokenIds, _amounts, ""); 25 | } 26 | 27 | function transfer( 28 | address _to, 29 | uint _tokenId, 30 | uint _amount 31 | ) public { 32 | _safeTransferFrom(msg.sender, _to, _tokenId, _amount, ""); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | files: "*.sol", 5 | options: { 6 | bracketSpacing: false, 7 | printWidth: 145, 8 | tabWidth: 4, 9 | useTabs: false, 10 | singleQuote: false, 11 | explicitTypes: "never", 12 | }, 13 | }, 14 | { 15 | files: "*.ts", 16 | options: { 17 | printWidth: 145, 18 | semi: false, 19 | tabWidth: 4, 20 | useTabs: false, 21 | trailingComma: "es5", 22 | }, 23 | }, 24 | { 25 | files: "*.js", 26 | options: { 27 | printWidth: 145, 28 | semi: false, 29 | tabWidth: 4, 30 | useTabs: false, 31 | trailingComma: "es5", 32 | }, 33 | }, 34 | ], 35 | } 36 | -------------------------------------------------------------------------------- /constants/layerzeroEndpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "ethereum": "0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675", 3 | "bsc": "0x3c2269811836af69497E5F486A85D7316753cf62", 4 | "avalanche": "0x3c2269811836af69497E5F486A85D7316753cf62", 5 | "polygon": "0x3c2269811836af69497E5F486A85D7316753cf62", 6 | "arbitrum": "0x3c2269811836af69497E5F486A85D7316753cf62", 7 | "optimism": "0x3c2269811836af69497E5F486A85D7316753cf62", 8 | "fantom": "0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7", 9 | 10 | "goerli": "0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23", 11 | "bsc-testnet": "0x6Fcb97553D41516Cb228ac03FdC8B9a0a9df04A1", 12 | "fuji": "0x93f54D755A063cE7bB9e6Ac47Eccc8e33411d706", 13 | "mumbai": "0xf69186dfBa60DdB133E91E9A4B5673624293d8F8", 14 | "arbitrum-goerli": "0x6aB5Ae6822647046626e83ee6dB8187151E1d5ab", 15 | "optimism-goerli": "0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1", 16 | "fantom-testnet": "0x7dcAD72640F835B0FA36EFD3D6d3ec902C7E5acf", 17 | "meter-testnet": "0x3De2f3D1Ac59F18159ebCB422322Cb209BA96aAD", 18 | "zksync-testnet": "0x093D2CF57f764f09C3c2Ac58a42A2601B8C79281" 19 | } -------------------------------------------------------------------------------- /deploy/PingPong.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function ({ deployments, getNamedAccounts }) { 4 | const owner = (await ethers.getSigners())[0] 5 | const { deploy } = deployments 6 | const { deployer } = await getNamedAccounts() 7 | console.log(`>>> your address: ${deployer}`) 8 | 9 | // get the Endpoint address 10 | const endpointAddr = LZ_ENDPOINTS[hre.network.name] 11 | console.log(`[${hre.network.name}] Endpoint address: ${endpointAddr}`) 12 | 13 | let pingPong = await deploy("PingPong", { 14 | from: deployer, 15 | args: [endpointAddr], 16 | log: true, 17 | waitConfirmations: 1, 18 | }) 19 | 20 | let eth = "0.05" 21 | let tx = await ( 22 | await owner.sendTransaction({ 23 | to: pingPong.address, 24 | value: ethers.utils.parseEther(eth), 25 | }) 26 | ).wait() 27 | console.log(`send it [${eth}] ether | tx: ${tx.transactionHash}`) 28 | } 29 | 30 | module.exports.tags = ["PingPong"] 31 | -------------------------------------------------------------------------------- /tasks/sendProxyONFT1155.js: -------------------------------------------------------------------------------- 1 | const CHAIN_IDS = require("../constants/chainIds.json") 2 | const ENDPOINTS = require("../constants/layerzeroEndpoints.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const signers = await ethers.getSigners() 6 | const owner = signers[0] 7 | const proxyONFT1155 = await ethers.getContract("ProxyONFT1155") 8 | const dstChainId = CHAIN_IDS[taskArgs.targetNetwork] 9 | 10 | const endpoint = await ethers.getContractAt("ILayerZeroEndpoint", ENDPOINTS[hre.network.name]) 11 | let fees = await endpoint.estimateFees(dstChainId, proxyONFT1155.address, "0x", false, "0x") 12 | console.log(`fees[0]: ${fees[0]}`) 13 | 14 | let tx = await ( 15 | await proxyONFT1155.send( 16 | dstChainId, 17 | owner.address, 18 | taskArgs.tokenId, 19 | taskArgs.quantity, 20 | owner.address, 21 | ethers.constants.AddressZero, 22 | "0x", 23 | { value: fees[0] } 24 | ) 25 | ).wait() 26 | console.log(`send tx: ${tx.transactionHash}`) 27 | } 28 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/interfaces/IOFTV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "./ICommonOFT.sol"; 6 | 7 | /** 8 | * @dev Interface of the IOFT core standard 9 | */ 10 | interface IOFTV2 is ICommonOFT { 11 | 12 | /** 13 | * @dev send `_amount` amount of token to (`_dstChainId`, `_toAddress`) from `_from` 14 | * `_from` the owner of token 15 | * `_dstChainId` the destination chain identifier 16 | * `_toAddress` can be any size depending on the `dstChainId`. 17 | * `_amount` the quantity of tokens in wei 18 | * `_refundAddress` the address LayerZero refunds if too much message fee is sent 19 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 20 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 21 | */ 22 | function sendFrom(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, LzCallParams calldata _callParams) external payable; 23 | 24 | function sendAndCall(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, bytes calldata _payload, uint64 _dstGasForCall, LzCallParams calldata _callParams) external payable; 25 | } 26 | -------------------------------------------------------------------------------- /utils/readStatic.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const fs = require("fs") 3 | 4 | function getDeploymentAddresses(networkName) { 5 | const PROJECT_ROOT = path.resolve(__dirname, "..") 6 | const DEPLOYMENT_PATH = path.resolve(PROJECT_ROOT, "deployments") 7 | 8 | let folderName = networkName 9 | if (networkName === "hardhat") { 10 | folderName = "localhost" 11 | } 12 | 13 | const networkFolderName = fs.readdirSync(DEPLOYMENT_PATH).filter((f) => f === folderName)[0] 14 | if (networkFolderName === undefined) { 15 | throw new Error("missing deployment files for endpoint " + folderName) 16 | } 17 | 18 | let rtnAddresses = {} 19 | const networkFolderPath = path.resolve(DEPLOYMENT_PATH, folderName) 20 | const files = fs.readdirSync(networkFolderPath).filter((f) => f.includes(".json")) 21 | files.forEach((file) => { 22 | const filepath = path.resolve(networkFolderPath, file) 23 | const data = JSON.parse(fs.readFileSync(filepath)) 24 | const contractName = file.split(".")[0] 25 | rtnAddresses[contractName] = data.address 26 | }) 27 | 28 | return rtnAddresses 29 | } 30 | 31 | module.exports = { 32 | getDeploymentAddresses 33 | } -------------------------------------------------------------------------------- /tasks/omniCounterIncrementWithParamsV1.js: -------------------------------------------------------------------------------- 1 | const { getDeploymentAddresses } = require("../utils/readStatic") 2 | const CHAIN_ID = require("../constants/chainIds.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | console.log(`[gasAmount]: `, taskArgs.gasAmount) 6 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 7 | const dstAddr = getDeploymentAddresses(taskArgs.targetNetwork)["OmniCounter"] 8 | const omniCounter = await ethers.getContract("OmniCounter") 9 | 10 | let tx = await ( 11 | await omniCounter.incrementCounterWithAdapterParamsV1( 12 | dstChainId, 13 | dstAddr, 14 | taskArgs.gasAmount, 15 | { value: ethers.utils.parseEther("1") } // estimate/guess 16 | ) 17 | ).wait() 18 | 19 | console.log( 20 | `✅ Message Sent [${hre.network.name}] omniCounterIncrementWithParamsV1 on destination OmniCounter @ [${dstChainId}] [${dstAddr}]` 21 | ) 22 | console.log(`tx: ${tx.transactionHash}`) 23 | 24 | console.log(``) 25 | console.log(`Note: to poll/wait for the message to arrive on the destination use the command:`) 26 | console.log("") 27 | console.log(` $ npx hardhat --network ${taskArgs.targetNetwork} omniCounterPoll`) 28 | } 29 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/IOFTWithFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "../interfaces/ICommonOFT.sol"; 6 | 7 | /** 8 | * @dev Interface of the IOFT core standard 9 | */ 10 | interface IOFTWithFee is ICommonOFT { 11 | 12 | /** 13 | * @dev send `_amount` amount of token to (`_dstChainId`, `_toAddress`) from `_from` 14 | * `_from` the owner of token 15 | * `_dstChainId` the destination chain identifier 16 | * `_toAddress` can be any size depending on the `dstChainId`. 17 | * `_amount` the quantity of tokens in wei 18 | * `_minAmount` the minimum amount of tokens to receive on dstChain 19 | * `_refundAddress` the address LayerZero refunds if too much message fee is sent 20 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 21 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 22 | */ 23 | function sendFrom(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, LzCallParams calldata _callParams) external payable; 24 | 25 | function sendAndCall(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, bytes calldata _payload, uint64 _dstGasForCall, LzCallParams calldata _callParams) external payable; 26 | } 27 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/README.md: -------------------------------------------------------------------------------- 1 | ## Clarification on OFT Versions 2 | 3 | ### OFTV1.2 4 | 5 | > [!IMPORTANT] 6 | > Please note that this repo contains OFTV1.2, and is NOT the LayerZero V2 OFT Standard, but rather a second version of OFT built on Endpoint V1. 7 | 8 | We recommend new developers use the [LayerZero V2 OFT Standard](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol) (found in the LayerZero V2 repo) over both the Endpoint V1 OFT V1 and Endpoint V1 OFT V1.2 implementations, as the protocol update comes with improved interfaces, gas optimizations, and greater composability. 9 | 10 | #### When to use LayerZero V2 OFT 11 | 12 | With the release of LayerZero V2, you should consider only using this V2 OFT Standard for your deployments. LayerZero V2 offers developers a smoother developer experience and optimizations to core protocol contracts. 13 | 14 | Read the full [LayerZero V2 Overview](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) to learn more. 15 | 16 | #### When to use LayerZero V1 OFT V1.2 17 | 18 | What if you want to build an Omnichain Fungible Token that supports EVMs and non-EVMs (e.g., Aptos)? In this case, you should use our Endpoint V1 OFT V1.2 which supports both. This version has fees, shared decimals, and composability built in. This Endpoint V1 version of OFT is currently being used in projects such as BTCb. 19 | -------------------------------------------------------------------------------- /tasks/isStoredPayload.js: -------------------------------------------------------------------------------- 1 | const ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | const EndpointAbi = [ 5 | "function storedPayload(uint16, bytes) external view returns (uint64, address, bytes32)", 6 | "function hasStoredPayload(uint16, bytes calldata) external view returns (bool)", 7 | "function retryPayload(uint16, bytes, bytes)", 8 | ] 9 | 10 | // console.log({taskArgs}) 11 | const endpoint = await ethers.getContractAt(EndpointAbi, ENDPOINTS[hre.network.name]) 12 | 13 | // concat remote and local address 14 | let remoteAndLocal = hre.ethers.utils.solidityPack(["address", "address"], [taskArgs.srcAddress, taskArgs.desAddress]) 15 | 16 | let bool = await endpoint.hasStoredPayload(taskArgs.srcChainId, remoteAndLocal) 17 | console.log(bool) 18 | if (bool && taskArgs.clear) { 19 | let payload = "0x" + taskArgs.payload 20 | let tx = await (await endpoint.retryPayload(taskArgs.srcChainId, remoteAndLocal, payload)).wait() 21 | console.log(`tx: ${tx.transactionHash}`) 22 | } 23 | } 24 | 25 | // npx hardhat storedPayload --network polygon --src-chain-id TBD --src-address TBD --des-address TBD 26 | // npx hardhat storedPayload --network polygon --src-chain-id 101 --src-address 0x165192f89ea752f597203eeb14e8f5538bce799d --des-address 0x9add6f279394f7f3c7a61d3426a7f45e149261a4 27 | -------------------------------------------------------------------------------- /tasks/incrementCounter.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | const ENDPOINTS = require("../constants/layerzeroEndpoints.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const remoteChainId = CHAIN_ID[taskArgs.targetNetwork] 6 | const omniCounter = await ethers.getContract("OmniCounter") 7 | 8 | // quote fee with default adapterParams 9 | const adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 200000]) // default adapterParams example 10 | 11 | const payload = await omniCounter.PAYLOAD() 12 | const endpoint = await ethers.getContractAt("ILayerZeroEndpoint", ENDPOINTS[hre.network.name]) 13 | const fees = await endpoint.estimateFees(remoteChainId, omniCounter.address, payload, false, adapterParams) 14 | console.log(`fees[0] (wei): ${fees[0]} / (eth): ${ethers.utils.formatEther(fees[0])}`) 15 | 16 | let tx = await (await omniCounter.incrementCounter(remoteChainId, { value: fees[0] })).wait() 17 | console.log(`✅ Message Sent [${hre.network.name}] incrementCounter on destination OmniCounter @ [${remoteChainId}]`) 18 | console.log(`tx: ${tx.transactionHash}`) 19 | 20 | console.log(``) 21 | console.log(`Note: to poll/wait for the message to arrive on the destination use the command:`) 22 | console.log(` (it may take a minute to arrive, be patient!)`) 23 | console.log("") 24 | console.log(` $ npx hardhat --network ${taskArgs.targetNetwork} ocPoll`) 25 | } 26 | -------------------------------------------------------------------------------- /contracts/token/onft1155/ONFT1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./interfaces/IONFT1155.sol"; 6 | import "./ONFT1155Core.sol"; 7 | import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 8 | 9 | // NOTE: this ONFT contract has no public minting logic. 10 | // must implement your own minting logic in child classes 11 | contract ONFT1155 is ONFT1155Core, ERC1155, IONFT1155 { 12 | constructor(string memory _uri, address _lzEndpoint) ERC1155(_uri) ONFT1155Core(_lzEndpoint) {} 13 | 14 | function supportsInterface(bytes4 interfaceId) public view virtual override(ONFT1155Core, ERC1155, IERC165) returns (bool) { 15 | return interfaceId == type(IONFT1155).interfaceId || super.supportsInterface(interfaceId); 16 | } 17 | 18 | function _debitFrom( 19 | address _from, 20 | uint16, 21 | bytes memory, 22 | uint[] memory _tokenIds, 23 | uint[] memory _amounts 24 | ) internal virtual override { 25 | address spender = _msgSender(); 26 | require(spender == _from || isApprovedForAll(_from, spender), "ONFT1155: send caller is not owner nor approved"); 27 | _burnBatch(_from, _tokenIds, _amounts); 28 | } 29 | 30 | function _creditTo( 31 | uint16, 32 | address _toAddress, 33 | uint[] memory _tokenIds, 34 | uint[] memory _amounts 35 | ) internal virtual override { 36 | _mintBatch(_toAddress, _tokenIds, _amounts, ""); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/lzApp/interfaces/ILayerZeroUserApplicationConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | interface ILayerZeroUserApplicationConfig { 6 | // @notice set the configuration of the LayerZero messaging library of the specified version 7 | // @param _version - messaging library version 8 | // @param _chainId - the chainId for the pending config change 9 | // @param _configType - type of configuration. every messaging library has its own convention. 10 | // @param _config - configuration in the bytes. can encode arbitrary content. 11 | function setConfig( 12 | uint16 _version, 13 | uint16 _chainId, 14 | uint _configType, 15 | bytes calldata _config 16 | ) external; 17 | 18 | // @notice set the send() LayerZero messaging library version to _version 19 | // @param _version - new messaging library version 20 | function setSendVersion(uint16 _version) external; 21 | 22 | // @notice set the lzReceive() LayerZero messaging library version to _version 23 | // @param _version - new messaging library version 24 | function setReceiveVersion(uint16 _version) external; 25 | 26 | // @notice Only when the UA needs to resume the message flow in blocking mode and clear the stored payload 27 | // @param _srcChainId - the chainId of the source chain 28 | // @param _srcAddress - the contract address of the source contract at the source chain 29 | function forceResumeReceive(uint16 _srcChainId, bytes calldata _srcAddress) external; 30 | } 31 | -------------------------------------------------------------------------------- /tasks/onftSend.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | const signers = await ethers.getSigners() 5 | const owner = signers[0] 6 | const toAddress = owner.address 7 | const tokenId = taskArgs.tokenId 8 | // get remote chain id 9 | const remoteChainId = CHAIN_ID[taskArgs.targetNetwork] 10 | 11 | // get local contract 12 | const onft = await ethers.getContract(taskArgs.contract) 13 | 14 | // quote fee with default adapterParams 15 | const adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 200000]) // default adapterParams example 16 | 17 | const fees = await onft.estimateSendFee(remoteChainId, toAddress, tokenId, false, adapterParams) 18 | const nativeFee = fees[0] 19 | console.log(`native fees (wei): ${nativeFee}`) 20 | 21 | const tx = await onft.sendFrom( 22 | owner.address, // 'from' address to send tokens 23 | remoteChainId, // remote LayerZero chainId 24 | toAddress, // 'to' address to send tokens 25 | tokenId, // tokenId to send 26 | owner.address, // refund address (if too much message fee is sent, it gets refunded) 27 | ethers.constants.AddressZero, // address(0x0) if not paying in ZRO (LayerZero Token) 28 | adapterParams, // flexible bytes array to indicate messaging adapter services 29 | { value: nativeFee.mul(5).div(4) } 30 | ) 31 | console.log(`✅ [${hre.network.name}] sendFrom tx: ${tx.hash}`) 32 | await tx.wait() 33 | } 34 | -------------------------------------------------------------------------------- /tasks/omniCounterIncrementWithParamsV2.js: -------------------------------------------------------------------------------- 1 | const { getDeploymentAddresses } = require("../utils/readStatic") 2 | const CHAIN_ID = require("../constants/chainIds.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const dstChainId = CHAIN_ID[taskArgs.targetNetwork] 6 | console.log(` 7 | [destination]: ${getDeploymentAddresses(taskArgs.targetNetwork)}, 8 | [gasAmount]: ${taskArgs.gasAmount}, 9 | [airDropEthQty]: ${taskArgs.airDropEthQty}, 10 | [airDropAddr]: ${taskArgs.airDropAddr} 11 | `) 12 | const dstAddr = getDeploymentAddresses(taskArgs.targetNetwork)["OmniCounter"] 13 | const omniCounter = await ethers.getContract("OmniCounter") 14 | console.log(`[source] omniCounter.address: ${omniCounter.address}`) 15 | let tx = await ( 16 | await omniCounter.incrementCounterWithAdapterParamsV2( 17 | dstChainId, 18 | dstAddr, 19 | taskArgs.gasAmount, 20 | taskArgs.airDropEthQty, 21 | taskArgs.airDropAddr, 22 | { value: ethers.utils.parseEther("1") } // estimate/guess 23 | ) 24 | ).wait() 25 | console.log( 26 | `✅ Message Sent [${hre.network.name}] omniCounterIncrementWithParamsV2 on destination OmniCounter @ [${dstChainId}] [${dstAddr}]` 27 | ) 28 | console.log(`tx: ${tx.transactionHash}`) 29 | 30 | console.log(``) 31 | console.log(`Note: to poll/wait for the message to arrive on the destination use the command:`) 32 | console.log("") 33 | console.log(` $ npx hardhat --network ${taskArgs.targetNetwork} omniCounterPoll`) 34 | } 35 | -------------------------------------------------------------------------------- /utils/getAddresses.js: -------------------------------------------------------------------------------- 1 | const environments = require("../constants/environments.json") 2 | const fs = require("fs") 3 | 4 | const environmentArg = process.argv[2] 5 | const contractCsvArg = process.argv[3] 6 | 7 | async function getAddresses(environment, contractCsv) { 8 | let contracts = contractCsv.split(",") 9 | const promises = [] 10 | for (const contract of contracts) { 11 | promises.push("\n" + contract) 12 | const networks = environments[environment]; 13 | for (const network of networks) { 14 | let fileName = `deployments/${network}/${contract[0].toUpperCase() + contract.substring(1)}.json`; 15 | if(fs.existsSync(fileName)) { 16 | promises.push(getAddressForNetwork(fileName, network)) 17 | } 18 | } 19 | } 20 | const resolvedPromises = await Promise.all(promises) 21 | resolvedPromises.forEach((networkAddressStr) => { 22 | console.log(networkAddressStr) 23 | }) 24 | } 25 | 26 | function getAddressForNetwork(file, network) { 27 | return new Promise((res) => { 28 | fs.readFile(file, (error, content) => { 29 | if (content === undefined) { 30 | console.log(`File: ${file} does not exsist`) 31 | return 32 | } 33 | res(`${network}: ${JSON.parse(content).address}`) 34 | }) 35 | }) 36 | } 37 | 38 | // to run: node getAddresses ${ENVIRONMENT} ${CONTRACT_CSV} 39 | // example: node getAddresses testnet Relayer,Endpoint,UltraLightNode 40 | getAddresses(environmentArg, contractCsvArg).then((res) => console.log("\nComplete!")) 41 | -------------------------------------------------------------------------------- /tasks/checkWireUp.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | const environments = require("../constants/environments.json") 3 | 4 | function TrustedRemoteTestnet() { 5 | this.goerli 6 | this.bscTestnet 7 | this.fuji 8 | this.mumbai 9 | this.arbitrumGoerli 10 | this.optimismGoerli 11 | this.fantomTestnet 12 | } 13 | 14 | function TrustedRemote() { 15 | this.ethereum 16 | this.bsc 17 | this.avalanche 18 | this.polygon 19 | this.arbitrum 20 | this.optimism 21 | this.fantom 22 | } 23 | 24 | module.exports = async function (taskArgs) { 25 | const environment = hre.network.name 26 | const environmentArray = environments[taskArgs.e] 27 | 28 | let trustedRemoteTable = {} 29 | 30 | trustedRemoteTable[environment] = taskArgs.e === "mainnet" ? new TrustedRemote() : new TrustedRemoteTestnet() 31 | 32 | await Promise.all( 33 | environmentArray.map(async (env) => { 34 | try { 35 | const contract = await ethers.getContract(taskArgs.contract) 36 | const dstChainId = CHAIN_ID[env] 37 | let envToCamelCase = env.replace(/-./g, (m) => m[1].toUpperCase()) 38 | trustedRemoteTable[environment][envToCamelCase] = await contract.trustedRemoteLookup(dstChainId) 39 | } catch (error) { 40 | //catch error because checkWireUpAll is reading console log as input 41 | } 42 | }) 43 | ) 44 | if (JSON.stringify(trustedRemoteTable[environment]).length > 2) { 45 | console.log(JSON.stringify(trustedRemoteTable[environment])) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/ProxyOFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./OFTCore.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract ProxyOFT is OFTCore { 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 internal immutable innerToken; 12 | 13 | constructor(address _lzEndpoint, address _token) OFTCore(_lzEndpoint) { 14 | innerToken = IERC20(_token); 15 | } 16 | 17 | function circulatingSupply() public view virtual override returns (uint) { 18 | unchecked { 19 | return innerToken.totalSupply() - innerToken.balanceOf(address(this)); 20 | } 21 | } 22 | 23 | function token() public view virtual override returns (address) { 24 | return address(innerToken); 25 | } 26 | 27 | function _debitFrom( 28 | address _from, 29 | uint16, 30 | bytes memory, 31 | uint _amount 32 | ) internal virtual override returns (uint) { 33 | require(_from == _msgSender(), "ProxyOFT: owner is not send caller"); 34 | uint before = innerToken.balanceOf(address(this)); 35 | innerToken.safeTransferFrom(_from, address(this), _amount); 36 | return innerToken.balanceOf(address(this)) - before; 37 | } 38 | 39 | function _creditTo( 40 | uint16, 41 | address _toAddress, 42 | uint _amount 43 | ) internal virtual override returns (uint) { 44 | uint before = innerToken.balanceOf(_toAddress); 45 | innerToken.safeTransfer(_toAddress, _amount); 46 | return innerToken.balanceOf(_toAddress) - before; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/interfaces/ICommonOFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | /** 8 | * @dev Interface of the IOFT core standard 9 | */ 10 | interface ICommonOFT is IERC165 { 11 | 12 | struct LzCallParams { 13 | address payable refundAddress; 14 | address zroPaymentAddress; 15 | bytes adapterParams; 16 | } 17 | 18 | /** 19 | * @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`) 20 | * _dstChainId - L0 defined chain id to send tokens too 21 | * _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 22 | * _amount - amount of the tokens to transfer 23 | * _useZro - indicates to use zro to pay L0 fees 24 | * _adapterParam - flexible bytes array to indicate messaging adapter services in L0 25 | */ 26 | function estimateSendFee(uint16 _dstChainId, bytes32 _toAddress, uint _amount, bool _useZro, bytes calldata _adapterParams) external view returns (uint nativeFee, uint zroFee); 27 | 28 | function estimateSendAndCallFee(uint16 _dstChainId, bytes32 _toAddress, uint _amount, bytes calldata _payload, uint64 _dstGasForCall, bool _useZro, bytes calldata _adapterParams) external view returns (uint nativeFee, uint zroFee); 29 | 30 | /** 31 | * @dev returns the circulating amount of tokens on current chain 32 | */ 33 | function circulatingSupply() external view returns (uint); 34 | 35 | /** 36 | * @dev returns the address of the ERC20 token 37 | */ 38 | function token() external view returns (address); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/OFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 7 | import "./interfaces/IOFT.sol"; 8 | import "./OFTCore.sol"; 9 | 10 | // override decimal() function is needed 11 | contract OFT is OFTCore, ERC20, IOFT { 12 | constructor( 13 | string memory _name, 14 | string memory _symbol, 15 | address _lzEndpoint 16 | ) ERC20(_name, _symbol) OFTCore(_lzEndpoint) {} 17 | 18 | function supportsInterface(bytes4 interfaceId) public view virtual override(OFTCore, IERC165) returns (bool) { 19 | return interfaceId == type(IOFT).interfaceId || interfaceId == type(IERC20).interfaceId || super.supportsInterface(interfaceId); 20 | } 21 | 22 | function token() public view virtual override returns (address) { 23 | return address(this); 24 | } 25 | 26 | function circulatingSupply() public view virtual override returns (uint) { 27 | return totalSupply(); 28 | } 29 | 30 | function _debitFrom( 31 | address _from, 32 | uint16, 33 | bytes memory, 34 | uint _amount 35 | ) internal virtual override returns (uint) { 36 | address spender = _msgSender(); 37 | if (_from != spender) _spendAllowance(_from, spender, _amount); 38 | _burn(_from, _amount); 39 | return _amount; 40 | } 41 | 42 | function _creditTo( 43 | uint16, 44 | address _toAddress, 45 | uint _amount 46 | ) internal virtual override returns (uint) { 47 | _mint(_toAddress, _amount); 48 | return _amount; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/token/onft721/ONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./interfaces/IONFT721.sol"; 6 | import "./ONFT721Core.sol"; 7 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 8 | 9 | // NOTE: this ONFT contract has no public minting logic. 10 | // must implement your own minting logic in child classes 11 | contract ONFT721 is ONFT721Core, ERC721, IONFT721 { 12 | constructor( 13 | string memory _name, 14 | string memory _symbol, 15 | uint _minGasToTransfer, 16 | address _lzEndpoint 17 | ) ERC721(_name, _symbol) ONFT721Core(_minGasToTransfer, _lzEndpoint) {} 18 | 19 | function supportsInterface(bytes4 interfaceId) public view virtual override(ONFT721Core, ERC721, IERC165) returns (bool) { 20 | return interfaceId == type(IONFT721).interfaceId || super.supportsInterface(interfaceId); 21 | } 22 | 23 | function _debitFrom( 24 | address _from, 25 | uint16, 26 | bytes memory, 27 | uint _tokenId 28 | ) internal virtual override { 29 | require(_isApprovedOrOwner(_msgSender(), _tokenId), "ONFT721: send caller is not owner nor approved"); 30 | require(ERC721.ownerOf(_tokenId) == _from, "ONFT721: send from incorrect owner"); 31 | _transfer(_from, address(this), _tokenId); 32 | } 33 | 34 | function _creditTo( 35 | uint16, 36 | address _toAddress, 37 | uint _tokenId 38 | ) internal virtual override { 39 | require(!_exists(_tokenId) || (_exists(_tokenId) && ERC721.ownerOf(_tokenId) == address(this))); 40 | if (!_exists(_tokenId)) { 41 | _safeMint(_toAddress, _tokenId); 42 | } else { 43 | _transfer(address(this), _toAddress, _tokenId); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/examples/OmniCounter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | pragma abicoder v2; 5 | 6 | import "../lzApp/NonblockingLzApp.sol"; 7 | 8 | /// @title A LayerZero example sending a cross chain message from a source chain to a destination chain to increment a counter 9 | contract OmniCounter is NonblockingLzApp { 10 | bytes public constant PAYLOAD = "\x01\x02\x03\x04"; 11 | uint public counter; 12 | 13 | constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {} 14 | 15 | function _nonblockingLzReceive( 16 | uint16, 17 | bytes memory, 18 | uint64, 19 | bytes memory 20 | ) internal override { 21 | counter += 1; 22 | } 23 | 24 | function estimateFee( 25 | uint16 _dstChainId, 26 | bool _useZro, 27 | bytes calldata _adapterParams 28 | ) public view returns (uint nativeFee, uint zroFee) { 29 | return lzEndpoint.estimateFees(_dstChainId, address(this), PAYLOAD, _useZro, _adapterParams); 30 | } 31 | 32 | function incrementCounter(uint16 _dstChainId) public payable { 33 | _lzSend(_dstChainId, PAYLOAD, payable(msg.sender), address(0x0), bytes(""), msg.value); 34 | } 35 | 36 | function setOracle(uint16 dstChainId, address oracle) external onlyOwner { 37 | uint TYPE_ORACLE = 6; 38 | // set the Oracle 39 | lzEndpoint.setConfig(lzEndpoint.getSendVersion(address(this)), dstChainId, TYPE_ORACLE, abi.encode(oracle)); 40 | } 41 | 42 | function getOracle(uint16 remoteChainId) external view returns (address _oracle) { 43 | bytes memory bytesOracle = lzEndpoint.getConfig(lzEndpoint.getSendVersion(address(this)), remoteChainId, address(this), 6); 44 | assembly { 45 | _oracle := mload(add(bytesOracle, 32)) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tasks/batchSendONFT1155.js: -------------------------------------------------------------------------------- 1 | const CHAIN_IDS = require("../constants/chainIds.json") 2 | const ENDPOINTS = require("../constants/layerzeroEndpoints.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const signers = await ethers.getSigners() 6 | const owner = signers[0] 7 | const onft1155 = await ethers.getContract("ONFT1155") 8 | const dstChainId = CHAIN_IDS[taskArgs.targetNetwork] 9 | 10 | const tokenIds = taskArgs.tokenIds.split(",") 11 | const quantities = taskArgs.quantities.split(",") 12 | console.log(tokenIds) 13 | console.log(quantities) 14 | 15 | const payload = 16 | "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" 17 | const endpoint = await ethers.getContractAt("ILayerZeroEndpoint", ENDPOINTS[hre.network.name]) 18 | let fees = await endpoint.estimateFees(dstChainId, onft1155.address, payload, false, "0x") 19 | console.log(`fees[0] (wei): ${fees[0]}`) 20 | 21 | let tx = await ( 22 | await onft1155.sendBatch(dstChainId, owner.address, tokenIds, quantities, owner.address, ethers.constants.AddressZero, "0x", { 23 | value: fees[0], 24 | }) 25 | ).wait() 26 | console.log(`send tx: ${tx.transactionHash}`) 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@layerzerolabs/solidity-examples", 3 | "version": "1.1.1", 4 | "license": "MIT", 5 | "files": [ 6 | "artifacts/", 7 | "contracts/" 8 | ], 9 | "description": "example contracts", 10 | "main": "index.js", 11 | "scripts": { 12 | "test": "npx hardhat test --parallel", 13 | "prettier": "prettier --write test/**/*.js && prettier --write test/*/*/*.js && prettier --write deploy/*.js && prettier --write tasks/*.js && prettier --write contracts/**/*.sol && prettier --write contracts/**/**/*.sol && prettier --write contracts/**/**/**/*.sol", 14 | "lint": "yarn prettier && solhint 'contracts/*.sol' && solhint 'contracts/**/*.sol' && solhint 'contracts/**/**/*.sol' && solhint 'contracts/**/**/**/*.sol'" 15 | }, 16 | "dependencies": { 17 | "@layerzerolabs/lz-evm-sdk-v1-0.7": "^1.5.14", 18 | "@openzeppelin/contracts": "^4.4.1", 19 | "@openzeppelin-3/contracts": "npm:@openzeppelin/contracts@^3.4.2-solc-0.7", 20 | "@openzeppelin/contracts-upgradeable": "^4.6.0", 21 | "@openzeppelin/hardhat-upgrades": "^1.18.3", 22 | "erc721a": "^4.2.3", 23 | "dotenv": "^10.0.0", 24 | "hardhat": "^2.8.0", 25 | "hardhat-contract-sizer": "^2.1.1", 26 | "hardhat-deploy": "^0.10.5", 27 | "hardhat-deploy-ethers": "^0.3.0-beta.13", 28 | "hardhat-gas-reporter": "^1.0.6" 29 | }, 30 | "devDependencies": { 31 | "@layerzerolabs/prettier-plugin-solidity": "^1.0.0-beta.19", 32 | "@nomiclabs/hardhat-ethers": "^2.0.3", 33 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 34 | "@nomiclabs/hardhat-waffle": "^2.0.1", 35 | "chai": "^4.3.4", 36 | "eslint-config-prettier": "^8.3.0", 37 | "eslint-plugin-prettier": "^3.4.1", 38 | "ethereum-waffle": "^3.4.0", 39 | "ethers": "^5.5.2", 40 | "prettier": "^2.4.1", 41 | "solhint": "^3.3.6", 42 | "solidity-coverage": "^0.7.17" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tasks/batchSendProxyONFT1155.js: -------------------------------------------------------------------------------- 1 | const CHAIN_IDS = require("../constants/chainIds.json") 2 | const ENDPOINTS = require("../constants/layerzeroEndpoints.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | const signers = await ethers.getSigners() 6 | const owner = signers[0] 7 | const proxyONFT1155 = await ethers.getContract("ProxyONFT1155") 8 | const dstChainId = CHAIN_IDS[taskArgs.targetNetwork] 9 | 10 | const tokenIds = taskArgs.tokenIds.split(",") 11 | const quantities = taskArgs.quantities.split(",") 12 | console.log(tokenIds) 13 | console.log(quantities) 14 | 15 | const payload = 16 | "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000014f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" 17 | const endpoint = await ethers.getContractAt("ILayerZeroEndpoint", ENDPOINTS[hre.network.name]) 18 | let fees = await endpoint.estimateFees(dstChainId, proxyONFT1155.address, payload, false, "0x") 19 | console.log(`fees[0] (wei): ${fees[0]}`) 20 | 21 | let tx = await ( 22 | await proxyONFT1155.sendBatch(dstChainId, owner.address, tokenIds, quantities, owner.address, ethers.constants.AddressZero, "0x", { 23 | value: fees[0], 24 | }) 25 | ).wait() 26 | console.log(`send tx: ${tx.transactionHash}`) 27 | } 28 | -------------------------------------------------------------------------------- /contracts/token/onft721/ProxyONFT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; 7 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 8 | import "./ONFT721Core.sol"; 9 | 10 | contract ProxyONFT721 is ONFT721Core, IERC721Receiver { 11 | using ERC165Checker for address; 12 | 13 | IERC721 public immutable token; 14 | 15 | constructor( 16 | uint _minGasToTransfer, 17 | address _lzEndpoint, 18 | address _proxyToken 19 | ) ONFT721Core(_minGasToTransfer, _lzEndpoint) { 20 | require(_proxyToken.supportsInterface(type(IERC721).interfaceId), "ProxyONFT721: invalid ERC721 token"); 21 | token = IERC721(_proxyToken); 22 | } 23 | 24 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 25 | return interfaceId == type(IERC721Receiver).interfaceId || super.supportsInterface(interfaceId); 26 | } 27 | 28 | function _debitFrom( 29 | address _from, 30 | uint16, 31 | bytes memory, 32 | uint _tokenId 33 | ) internal virtual override { 34 | require(_from == _msgSender(), "ProxyONFT721: owner is not send caller"); 35 | token.safeTransferFrom(_from, address(this), _tokenId); 36 | } 37 | 38 | // TODO apply same changes from regular ONFT721 39 | function _creditTo( 40 | uint16, 41 | address _toAddress, 42 | uint _tokenId 43 | ) internal virtual override { 44 | token.safeTransferFrom(address(this), _toAddress, _tokenId); 45 | } 46 | 47 | function onERC721Received( 48 | address _operator, 49 | address, 50 | uint, 51 | bytes memory 52 | ) public virtual override returns (bytes4) { 53 | // only allow `this` to transfer token from others 54 | if (_operator != address(this)) return bytes4(0); 55 | return IERC721Receiver.onERC721Received.selector; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tasks/getMessageFailedEvent.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (taskArgs, hre) { 2 | let blockStart = (await ethers.provider.getTransaction(taskArgs.txStart)).blockNumber 3 | let blockEnd = 4 | taskArgs.txEnd !== undefined 5 | ? (await ethers.provider.getTransaction(taskArgs.txEnd)).blockNumber 6 | : await ethers.provider.getBlockNumber() 7 | 8 | if (taskArgs.blockStart) { 9 | blockStart = taskArgs.blockStart 10 | } 11 | console.log(`blockStart: ${blockStart} -> blockEnd: ${blockEnd}`) 12 | const contract = await ethers.getContractAt("NonblockingLzApp", taskArgs.dstUa) 13 | const step = taskArgs.step 14 | for (let from = blockStart; from <= blockEnd; from += step + 1) { 15 | const to = Math.min(from + step, blockEnd) 16 | const deposits = await contract.queryFilter(contract.filters.MessageFailed(), from, to) 17 | for (const e of deposits) { 18 | // event MessageFailed(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes _payload, bytes _reason); 19 | let messageFailed = { 20 | block: `${from}`, 21 | srcChainId: `${e?.args[0].toString()}`, 22 | srcAddress: `${e?.args[1].toString()}`, 23 | nonce: `${e?.args[2].toString()}`, 24 | payload: `${e?.args[3].toString()}`, 25 | reason: `${e?.args[4].toString()}`, 26 | } 27 | console.log(messageFailed) 28 | if (taskArgs.nonce !== undefined && messageFailed.nonce === taskArgs.nonce) { 29 | console.log(`Attempting to clear nonce: ${e.args[3].toString()}`) 30 | let tx = await ( 31 | await contract.retryMessage(messageFailed.srcChainId, messageFailed.srcAddress, messageFailed.nonce, messageFailed.payload) 32 | ).wait() 33 | console.log("txHash:" + tx.transactionHash) 34 | } 35 | } 36 | } 37 | } 38 | 39 | // npx hardhat --network goerli getMessageFailedEvent --tx-start TX_HASH_SRC --tx-end TX_HASH_DST --dstUA DST_ADDRESS --nonce NONCE_TO_CLEAR 40 | -------------------------------------------------------------------------------- /tasks/setTrustedRemote.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | const { getDeploymentAddresses } = require("../utils/readStatic") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | let localContract, remoteContract 6 | 7 | if (taskArgs.contract) { 8 | localContract = taskArgs.contract 9 | remoteContract = taskArgs.contract 10 | } else { 11 | localContract = taskArgs.localContract 12 | remoteContract = taskArgs.remoteContract 13 | } 14 | 15 | if (!localContract || !remoteContract) { 16 | console.log("Must pass in contract name OR pass in both localContract name and remoteContract name") 17 | return 18 | } 19 | 20 | // get local contract 21 | const localContractInstance = await ethers.getContract(localContract) 22 | 23 | // get deployed remote contract address 24 | const remoteAddress = getDeploymentAddresses(taskArgs.targetNetwork)[remoteContract] 25 | 26 | // get remote chain id 27 | const remoteChainId = CHAIN_ID[taskArgs.targetNetwork] 28 | 29 | // concat remote and local address 30 | let remoteAndLocal = hre.ethers.utils.solidityPack(["address", "address"], [remoteAddress, localContractInstance.address]) 31 | 32 | // check if pathway is already set 33 | const isTrustedRemoteSet = await localContractInstance.isTrustedRemote(remoteChainId, remoteAndLocal) 34 | 35 | if (!isTrustedRemoteSet) { 36 | try { 37 | let tx = await (await localContractInstance.setTrustedRemote(remoteChainId, remoteAndLocal)).wait() 38 | console.log(`✅ [${hre.network.name}] setTrustedRemote(${remoteChainId}, ${remoteAndLocal})`) 39 | console.log(` tx: ${tx.transactionHash}`) 40 | } catch (e) { 41 | if (e.error.message.includes("The chainId + address is already trusted")) { 42 | console.log("*source already set*") 43 | } else { 44 | console.log(`❌ [${hre.network.name}] setTrustedRemote(${remoteChainId}, ${remoteAndLocal})`) 45 | } 46 | } 47 | } else { 48 | console.log("*source already set*") 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/token/onft721/ONFT721A.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 7 | import "erc721a/contracts/ERC721A.sol"; 8 | import "erc721a/contracts/IERC721A.sol"; 9 | import "./interfaces/IONFT721.sol"; 10 | import "./ONFT721Core.sol"; 11 | 12 | // DISCLAIMER: 13 | // This contract can only be deployed on one chain and must be the first minter of each token id! 14 | // This is because ERC721A does not have the ability to mint a specific token id. 15 | // Other chains must have ONFT721 deployed. 16 | 17 | // NOTE: this ONFT contract has no public minting logic. 18 | // must implement your own minting logic in child contract 19 | contract ONFT721A is ONFT721Core, ERC721A, ERC721A__IERC721Receiver { 20 | constructor( 21 | string memory _name, 22 | string memory _symbol, 23 | uint _minGasToTransferAndStore, 24 | address _lzEndpoint 25 | ) ERC721A(_name, _symbol) ONFT721Core(_minGasToTransferAndStore, _lzEndpoint) {} 26 | 27 | function supportsInterface(bytes4 interfaceId) public view virtual override(ONFT721Core, ERC721A) returns (bool) { 28 | return interfaceId == type(IONFT721Core).interfaceId || super.supportsInterface(interfaceId); 29 | } 30 | 31 | function _debitFrom( 32 | address _from, 33 | uint16, 34 | bytes memory, 35 | uint _tokenId 36 | ) internal virtual override(ONFT721Core) { 37 | safeTransferFrom(_from, address(this), _tokenId); 38 | } 39 | 40 | function _creditTo( 41 | uint16, 42 | address _toAddress, 43 | uint _tokenId 44 | ) internal virtual override(ONFT721Core) { 45 | require(_exists(_tokenId) && ERC721A.ownerOf(_tokenId) == address(this)); 46 | safeTransferFrom(address(this), _toAddress, _tokenId); 47 | } 48 | 49 | function onERC721Received( 50 | address, 51 | address, 52 | uint, 53 | bytes memory 54 | ) public virtual override returns (bytes4) { 55 | return ERC721A__IERC721Receiver.onERC721Received.selector; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .DS_Store 107 | package-lock.json 108 | .idea 109 | cache/ 110 | artifacts/ 111 | deployments/ 112 | .openzeppelin/ 113 | 114 | -------------------------------------------------------------------------------- /tasks/oftSend.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | let signers = await ethers.getSigners() 5 | let owner = signers[0] 6 | let toAddress = owner.address 7 | let qty = ethers.utils.parseEther(taskArgs.qty) 8 | 9 | let localContract, remoteContract 10 | 11 | if (taskArgs.contract) { 12 | localContract = taskArgs.contract 13 | remoteContract = taskArgs.contract 14 | } else { 15 | localContract = taskArgs.localContract 16 | remoteContract = taskArgs.remoteContract 17 | } 18 | 19 | if (!localContract || !remoteContract) { 20 | console.log("Must pass in contract name OR pass in both localContract name and remoteContract name") 21 | return 22 | } 23 | 24 | // get remote chain id 25 | const remoteChainId = CHAIN_ID[taskArgs.targetNetwork] 26 | 27 | // get local contract 28 | const localContractInstance = await ethers.getContract(localContract) 29 | 30 | // quote fee with default adapterParams 31 | let adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 200000]) // default adapterParams example 32 | 33 | let fees = await localContractInstance.estimateSendFee(remoteChainId, toAddress, qty, false, adapterParams) 34 | console.log(`fees[0] (wei): ${fees[0]} / (eth): ${ethers.utils.formatEther(fees[0])}`) 35 | 36 | let tx = await ( 37 | await localContractInstance.sendFrom( 38 | owner.address, // 'from' address to send tokens 39 | remoteChainId, // remote LayerZero chainId 40 | toAddress, // 'to' address to send tokens 41 | qty, // amount of tokens to send (in wei) 42 | owner.address, // refund address (if too much message fee is sent, it gets refunded) 43 | ethers.constants.AddressZero, // address(0x0) if not paying in ZRO (LayerZero Token) 44 | "0x", // flexible bytes array to indicate messaging adapter services 45 | { value: fees[0] } 46 | ) 47 | ).wait() 48 | console.log(`✅ Message Sent [${hre.network.name}] sendTokens() to OFT @ LZ chainId[${remoteChainId}] token:[${toAddress}]`) 49 | console.log(` tx: ${tx.transactionHash}`) 50 | console.log(`* check your address [${owner.address}] on the destination chain, in the ERC20 transaction tab !"`) 51 | } 52 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/OFTWithFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./BaseOFTWithFee.sol"; 7 | 8 | contract OFTWithFee is BaseOFTWithFee, ERC20 { 9 | 10 | uint internal immutable ld2sdRate; 11 | 12 | constructor(string memory _name, string memory _symbol, uint8 _sharedDecimals, address _lzEndpoint) ERC20(_name, _symbol) BaseOFTWithFee(_sharedDecimals, _lzEndpoint) { 13 | uint8 decimals = decimals(); 14 | require(_sharedDecimals <= decimals, "OFTWithFee: sharedDecimals must be <= decimals"); 15 | ld2sdRate = 10 ** (decimals - _sharedDecimals); 16 | } 17 | 18 | /************************************************************************ 19 | * public functions 20 | ************************************************************************/ 21 | function circulatingSupply() public view virtual override returns (uint) { 22 | return totalSupply(); 23 | } 24 | 25 | function token() public view virtual override returns (address) { 26 | return address(this); 27 | } 28 | 29 | /************************************************************************ 30 | * internal functions 31 | ************************************************************************/ 32 | function _debitFrom(address _from, uint16, bytes32, uint _amount) internal virtual override returns (uint) { 33 | address spender = _msgSender(); 34 | if (_from != spender) _spendAllowance(_from, spender, _amount); 35 | _burn(_from, _amount); 36 | return _amount; 37 | } 38 | 39 | function _creditTo(uint16, address _toAddress, uint _amount) internal virtual override returns (uint) { 40 | _mint(_toAddress, _amount); 41 | return _amount; 42 | } 43 | 44 | function _transferFrom(address _from, address _to, uint _amount) internal virtual override returns (uint) { 45 | address spender = _msgSender(); 46 | // if transfer from this contract, no need to check allowance 47 | if (_from != address(this) && _from != spender) _spendAllowance(_from, spender, _amount); 48 | _transfer(_from, _to, _amount); 49 | return _amount; 50 | } 51 | 52 | function _ld2sdRate() internal view virtual override returns (uint) { 53 | return ld2sdRate; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tasks/oftv2Send.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = require("../constants/chainIds.json") 2 | 3 | module.exports = async function (taskArgs, hre) { 4 | let signers = await ethers.getSigners() 5 | let owner = signers[0] 6 | let toAddress = owner.address 7 | let qty = ethers.utils.parseEther(taskArgs.qty) 8 | 9 | let localContract, remoteContract 10 | 11 | if (taskArgs.contract) { 12 | localContract = taskArgs.contract 13 | remoteContract = taskArgs.contract 14 | } else { 15 | localContract = taskArgs.localContract 16 | remoteContract = taskArgs.remoteContract 17 | } 18 | 19 | if (!localContract || !remoteContract) { 20 | console.log("Must pass in contract name OR pass in both localContract name and remoteContract name") 21 | return 22 | } 23 | 24 | let toAddressBytes = ethers.utils.defaultAbiCoder.encode(["address"], [toAddress]) 25 | 26 | // get remote chain id 27 | const remoteChainId = CHAIN_ID[taskArgs.targetNetwork] 28 | 29 | // get local contract 30 | const localContractInstance = await ethers.getContract(localContract) 31 | 32 | // quote fee with default adapterParams 33 | let adapterParams = ethers.utils.solidityPack(["uint16", "uint256"], [1, 200000]) // default adapterParams example 34 | 35 | let fees = await localContractInstance.estimateSendFee(remoteChainId, toAddressBytes, qty, false, adapterParams) 36 | console.log(`fees[0] (wei): ${fees[0]} / (eth): ${ethers.utils.formatEther(fees[0])}`) 37 | 38 | let tx = await ( 39 | await localContractInstance.sendFrom( 40 | owner.address, // 'from' address to send tokens 41 | remoteChainId, // remote LayerZero chainId 42 | toAddressBytes, // 'to' address to send tokens 43 | qty, // amount of tokens to send (in wei) 44 | { 45 | refundAddress: owner.address, 46 | zroPaymentAddress: ethers.constants.AddressZero, 47 | adapterParams, 48 | }, 49 | { value: fees[0] } 50 | ) 51 | ).wait() 52 | console.log(`✅ Message Sent [${hre.network.name}] sendTokens() to OFT @ LZ chainId[${remoteChainId}] token:[${toAddress}]`) 53 | console.log(` tx: ${tx.transactionHash}`) 54 | console.log(`* check your address [${owner.address}] on the destination chain, in the ERC20 transaction tab !"`) 55 | } 56 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/Fee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | abstract contract Fee is Ownable { 8 | uint public constant BP_DENOMINATOR = 10000; 9 | 10 | mapping(uint16 => FeeConfig) public chainIdToFeeBps; 11 | uint16 public defaultFeeBp; 12 | address public feeOwner; // defaults to owner 13 | 14 | struct FeeConfig { 15 | uint16 feeBP; 16 | bool enabled; 17 | } 18 | 19 | event SetFeeBp(uint16 dstchainId, bool enabled, uint16 feeBp); 20 | event SetDefaultFeeBp(uint16 feeBp); 21 | event SetFeeOwner(address feeOwner); 22 | 23 | constructor(){ 24 | feeOwner = owner(); 25 | } 26 | 27 | function setDefaultFeeBp(uint16 _feeBp) public virtual onlyOwner { 28 | require(_feeBp <= BP_DENOMINATOR, "Fee: fee bp must be <= BP_DENOMINATOR"); 29 | defaultFeeBp = _feeBp; 30 | emit SetDefaultFeeBp(defaultFeeBp); 31 | } 32 | 33 | function setFeeBp(uint16 _dstChainId, bool _enabled, uint16 _feeBp) public virtual onlyOwner { 34 | require(_feeBp <= BP_DENOMINATOR, "Fee: fee bp must be <= BP_DENOMINATOR"); 35 | chainIdToFeeBps[_dstChainId] = FeeConfig(_feeBp, _enabled); 36 | emit SetFeeBp(_dstChainId, _enabled, _feeBp); 37 | } 38 | 39 | function setFeeOwner(address _feeOwner) public virtual onlyOwner { 40 | require(_feeOwner != address(0x0), "Fee: feeOwner cannot be 0x"); 41 | feeOwner = _feeOwner; 42 | emit SetFeeOwner(_feeOwner); 43 | } 44 | 45 | function quoteOFTFee(uint16 _dstChainId, uint _amount) public virtual view returns (uint fee) { 46 | FeeConfig memory config = chainIdToFeeBps[_dstChainId]; 47 | if (config.enabled) { 48 | fee = _amount * config.feeBP / BP_DENOMINATOR; 49 | } else if (defaultFeeBp > 0) { 50 | fee = _amount * defaultFeeBp / BP_DENOMINATOR; 51 | } else { 52 | fee = 0; 53 | } 54 | } 55 | 56 | function _payOFTFee(address _from, uint16 _dstChainId, uint _amount) internal virtual returns (uint amount, uint fee) { 57 | fee = quoteOFTFee(_dstChainId, _amount); 58 | amount = _amount - fee; 59 | if (fee > 0) { 60 | _transferFrom(_from, feeOwner, fee); 61 | } 62 | } 63 | 64 | function _transferFrom(address _from, address _to, uint _amount) internal virtual returns (uint); 65 | } 66 | -------------------------------------------------------------------------------- /contracts/token/onft1155/ProxyONFT1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ONFT1155Core.sol"; 6 | import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; 7 | import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; 8 | import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; 9 | 10 | contract ProxyONFT1155 is ONFT1155Core, IERC1155Receiver { 11 | using ERC165Checker for address; 12 | 13 | IERC1155 public immutable token; 14 | 15 | constructor(address _lzEndpoint, address _proxyToken) ONFT1155Core(_lzEndpoint) { 16 | require(_proxyToken.supportsInterface(type(IERC1155).interfaceId), "ProxyONFT1155: invalid ERC1155 token"); 17 | token = IERC1155(_proxyToken); 18 | } 19 | 20 | function supportsInterface(bytes4 interfaceId) public view virtual override(ONFT1155Core, IERC165) returns (bool) { 21 | return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId); 22 | } 23 | 24 | function _debitFrom( 25 | address _from, 26 | uint16, 27 | bytes memory, 28 | uint[] memory _tokenIds, 29 | uint[] memory _amounts 30 | ) internal virtual override { 31 | require(_from == _msgSender(), "ProxyONFT1155: owner is not send caller"); 32 | token.safeBatchTransferFrom(_from, address(this), _tokenIds, _amounts, ""); 33 | } 34 | 35 | function _creditTo( 36 | uint16, 37 | address _toAddress, 38 | uint[] memory _tokenIds, 39 | uint[] memory _amounts 40 | ) internal virtual override { 41 | token.safeBatchTransferFrom(address(this), _toAddress, _tokenIds, _amounts, ""); 42 | } 43 | 44 | function onERC1155Received( 45 | address _operator, 46 | address, 47 | uint, 48 | uint, 49 | bytes memory 50 | ) public virtual override returns (bytes4) { 51 | // only allow `this` to tranfser token from others 52 | if (_operator != address(this)) return bytes4(0); 53 | return this.onERC1155Received.selector; 54 | } 55 | 56 | function onERC1155BatchReceived( 57 | address _operator, 58 | address, 59 | uint[] memory, 60 | uint[] memory, 61 | bytes memory 62 | ) public virtual override returns (bytes4) { 63 | // only allow `this` to tranfser token from others 64 | if (_operator != address(this)) return bytes4(0); 65 | return this.onERC1155BatchReceived.selector; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/examples/OmniCounter.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai") 2 | const { ethers } = require("hardhat") 3 | 4 | describe("OmniCounter", function () { 5 | beforeEach(async function () { 6 | // use this chainId 7 | this.chainId = 123 8 | 9 | // create a LayerZero Endpoint mock for testing 10 | const LayerZeroEndpointMock = await ethers.getContractFactory("LZEndpointMock") 11 | this.lzEndpointMock = await LayerZeroEndpointMock.deploy(this.chainId) 12 | 13 | // create two OmniCounter instances 14 | const OmniCounter = await ethers.getContractFactory("OmniCounter") 15 | this.omniCounterA = await OmniCounter.deploy(this.lzEndpointMock.address) 16 | this.omniCounterB = await OmniCounter.deploy(this.lzEndpointMock.address) 17 | 18 | this.lzEndpointMock.setDestLzEndpoint(this.omniCounterA.address, this.lzEndpointMock.address) 19 | this.lzEndpointMock.setDestLzEndpoint(this.omniCounterB.address, this.lzEndpointMock.address) 20 | 21 | // set each contracts source address so it can send to each other 22 | this.omniCounterA.setTrustedRemote( 23 | this.chainId, 24 | ethers.utils.solidityPack(["address", "address"], [this.omniCounterB.address, this.omniCounterA.address]) 25 | ) 26 | this.omniCounterB.setTrustedRemote( 27 | this.chainId, 28 | ethers.utils.solidityPack(["address", "address"], [this.omniCounterA.address, this.omniCounterB.address]) 29 | ) 30 | }) 31 | 32 | it("increment the counter of the destination OmniCounter", async function () { 33 | // ensure theyre both starting from 0 34 | expect(await this.omniCounterA.counter()).to.be.equal(0) // initial value 35 | expect(await this.omniCounterB.counter()).to.be.equal(0) // initial value 36 | 37 | // instruct each OmniCounter to increment the other OmniCounter 38 | // counter A increments counter B 39 | await this.omniCounterA.incrementCounter(this.chainId, { value: ethers.utils.parseEther("0.5") }) 40 | expect(await this.omniCounterA.counter()).to.be.equal(0) // still 0 41 | expect(await this.omniCounterB.counter()).to.be.equal(1) // now its 1 42 | 43 | // counter B increments counter A 44 | await this.omniCounterB.incrementCounter(this.chainId, { value: ethers.utils.parseEther("0.5") }) 45 | expect(await this.omniCounterA.counter()).to.be.equal(1) // now its 1 46 | expect(await this.omniCounterB.counter()).to.be.equal(1) // still 1 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/interfaces/IOFTCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | /** 8 | * @dev Interface of the IOFT core standard 9 | */ 10 | interface IOFTCore is IERC165 { 11 | /** 12 | * @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`) 13 | * _dstChainId - L0 defined chain id to send tokens too 14 | * _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 15 | * _amount - amount of the tokens to transfer 16 | * _useZro - indicates to use zro to pay L0 fees 17 | * _adapterParam - flexible bytes array to indicate messaging adapter services in L0 18 | */ 19 | function estimateSendFee(uint16 _dstChainId, bytes calldata _toAddress, uint _amount, bool _useZro, bytes calldata _adapterParams) external view returns (uint nativeFee, uint zroFee); 20 | 21 | /** 22 | * @dev send `_amount` amount of token to (`_dstChainId`, `_toAddress`) from `_from` 23 | * `_from` the owner of token 24 | * `_dstChainId` the destination chain identifier 25 | * `_toAddress` can be any size depending on the `dstChainId`. 26 | * `_amount` the quantity of tokens in wei 27 | * `_refundAddress` the address LayerZero refunds if too much message fee is sent 28 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 29 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 30 | */ 31 | function sendFrom(address _from, uint16 _dstChainId, bytes calldata _toAddress, uint _amount, address payable _refundAddress, address _zroPaymentAddress, bytes calldata _adapterParams) external payable; 32 | 33 | /** 34 | * @dev returns the circulating amount of tokens on current chain 35 | */ 36 | function circulatingSupply() external view returns (uint); 37 | 38 | /** 39 | * @dev returns the address of the ERC20 token 40 | */ 41 | function token() external view returns (address); 42 | 43 | /** 44 | * @dev Emitted when `_amount` tokens are moved from the `_sender` to (`_dstChainId`, `_toAddress`) 45 | * `_nonce` is the outbound nonce 46 | */ 47 | event SendToChain(uint16 indexed _dstChainId, address indexed _from, bytes _toAddress, uint _amount); 48 | 49 | /** 50 | * @dev Emitted when `_amount` tokens are received from `_srcChainId` into the `_toAddress` on the local chain. 51 | * `_nonce` is the inbound nonce. 52 | */ 53 | event ReceiveFromChain(uint16 indexed _srcChainId, address indexed _to, uint _amount); 54 | 55 | event SetUseCustomAdapterParams(bool _useCustomAdapterParams); 56 | } 57 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/OFTV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./BaseOFTV2.sol"; 7 | 8 | /// @title OFT V1.2 Contract 9 | /// @notice This contract is version 1.2 of the OFT Standard, enabling cross-chain token transfers between EVM and non-EVM contracts. 10 | /// @dev This contract is only compatible with Endpoint V1. 11 | contract OFTV2 is BaseOFTV2, ERC20 { 12 | uint internal immutable ld2sdRate; 13 | 14 | constructor( 15 | string memory _name, 16 | string memory _symbol, 17 | uint8 _sharedDecimals, 18 | address _lzEndpoint 19 | ) ERC20(_name, _symbol) BaseOFTV2(_sharedDecimals, _lzEndpoint) { 20 | uint8 decimals = decimals(); 21 | require(_sharedDecimals <= decimals, "OFT: sharedDecimals must be <= decimals"); 22 | ld2sdRate = 10**(decimals - _sharedDecimals); 23 | } 24 | 25 | /************************************************************************ 26 | * public functions 27 | ************************************************************************/ 28 | function circulatingSupply() public view virtual override returns (uint) { 29 | return totalSupply(); 30 | } 31 | 32 | function token() public view virtual override returns (address) { 33 | return address(this); 34 | } 35 | 36 | /************************************************************************ 37 | * internal functions 38 | ************************************************************************/ 39 | function _debitFrom( 40 | address _from, 41 | uint16, 42 | bytes32, 43 | uint _amount 44 | ) internal virtual override returns (uint) { 45 | address spender = _msgSender(); 46 | if (_from != spender) _spendAllowance(_from, spender, _amount); 47 | _burn(_from, _amount); 48 | return _amount; 49 | } 50 | 51 | function _creditTo( 52 | uint16, 53 | address _toAddress, 54 | uint _amount 55 | ) internal virtual override returns (uint) { 56 | _mint(_toAddress, _amount); 57 | return _amount; 58 | } 59 | 60 | function _transferFrom( 61 | address _from, 62 | address _to, 63 | uint _amount 64 | ) internal virtual override returns (uint) { 65 | address spender = _msgSender(); 66 | // if transfer from this contract, no need to check allowance 67 | if (_from != address(this) && _from != spender) _spendAllowance(_from, spender, _amount); 68 | _transfer(_from, _to, _amount); 69 | return _amount; 70 | } 71 | 72 | function _ld2sdRate() internal view virtual override returns (uint) { 73 | return ld2sdRate; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tasks/getStoredPayloadEvent.js: -------------------------------------------------------------------------------- 1 | const LZ_ENDPOINTS = require("../constants/layerzeroEndpoints.json") 2 | const ABI = require("../constants/endpoint_abi.json") 3 | 4 | module.exports = async function (taskArgs, hre) { 5 | let blockStart = (await ethers.provider.getTransaction(taskArgs.txStart)).blockNumber 6 | let blockEnd = 7 | taskArgs.txEnd !== undefined 8 | ? (await ethers.provider.getTransaction(taskArgs.txEnd)).blockNumber 9 | : await ethers.provider.getBlockNumber() 10 | 11 | console.log(`blockStart: ${blockStart} -> blockEnd: ${blockEnd}`) 12 | console.log(hre.network.name) 13 | console.log(LZ_ENDPOINTS[hre.network.name]) 14 | 15 | const lzEndpointAddress = LZ_ENDPOINTS[hre.network.name] 16 | const endpoint = await hre.ethers.getContractAt(ABI, lzEndpointAddress) 17 | 18 | // concat remote and local address 19 | let remoteAndLocal = hre.ethers.utils.solidityPack(["address", "address"], [taskArgs.srcAddress, taskArgs.desAddress]) 20 | 21 | const step = taskArgs.step 22 | for (let from = blockStart; from <= blockEnd; from += step + 1) { 23 | const to = Math.min(from + step, blockEnd) 24 | const deposits = await endpoint.queryFilter(endpoint.filters.PayloadStored(), from, to) 25 | for (const e of deposits) { 26 | // event PayloadStored(uint16 srcChainId, bytes srcAddress, address dstAddress, uint64 nonce, bytes payload, bytes reason); 27 | let storedPayload = { 28 | block: `${from}`, 29 | srcChainId: `${e?.args[0].toString()}`, 30 | srcAddress: `${e?.args[1].toString()}`, 31 | dstAddress: `${e?.args[2].toString()}`, 32 | nonce: `${e?.args[3].toString()}`, 33 | payload: `${e?.args[4].toString()}`, 34 | reason: `${e?.args[5].toString()}`, 35 | } 36 | 37 | if (e.args[1] === remoteAndLocal) console.log(storedPayload) 38 | if (e.args[1] === remoteAndLocal && taskArgs.nonce !== undefined && storedPayload.nonce === taskArgs.nonce) { 39 | console.log(`Attempting to clear nonce: ${e.args[3].toString()}`) 40 | let tx = await (await endpoint.retryPayload(e.args[0], e.args[1], e?.args[4], { gasLimit: 200000 })).wait() 41 | console.log("txHash:" + tx.transactionHash) 42 | } 43 | } 44 | } 45 | } 46 | 47 | // npx hardhat --network bsc-testnet getStoredPayloadEvent --tx-start TX_HASH_SRC --src-address TBD --des-address TBD 48 | // npx hardhat --network bsc-testnet getStoredPayloadEvent --tx-start 0xf74b8a299ff58651d8f4e2411f5459b7f703b2582404a34a657e247a8463cb84 --src-address 0xff7e5f0faf0cba105cdb875833b801355fa58aa0 --des-address 0x2ef82e5c7afb10f70a704efebc15036d0e5864b1 49 | 50 | // to clear nonce 51 | // npx hardhat --network bsc-testnet getStoredPayloadEvent --tx-start 0xf74b8a299ff58651d8f4e2411f5459b7f703b2582404a34a657e247a8463cb84 --src-address 0xff7e5f0faf0cba105cdb875833b801355fa58aa0 --des-address 0x2ef82e5c7afb10f70a704efebc15036d0e5864b1 --nonce 8 52 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/BaseOFTV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./OFTCoreV2.sol"; 6 | import "./interfaces/IOFTV2.sol"; 7 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 8 | 9 | abstract contract BaseOFTV2 is OFTCoreV2, ERC165, IOFTV2 { 10 | constructor(uint8 _sharedDecimals, address _lzEndpoint) OFTCoreV2(_sharedDecimals, _lzEndpoint) {} 11 | 12 | /************************************************************************ 13 | * public functions 14 | ************************************************************************/ 15 | function sendFrom( 16 | address _from, 17 | uint16 _dstChainId, 18 | bytes32 _toAddress, 19 | uint _amount, 20 | LzCallParams calldata _callParams 21 | ) public payable virtual override { 22 | _send(_from, _dstChainId, _toAddress, _amount, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 23 | } 24 | 25 | function sendAndCall( 26 | address _from, 27 | uint16 _dstChainId, 28 | bytes32 _toAddress, 29 | uint _amount, 30 | bytes calldata _payload, 31 | uint64 _dstGasForCall, 32 | LzCallParams calldata _callParams 33 | ) public payable virtual override { 34 | _sendAndCall( 35 | _from, 36 | _dstChainId, 37 | _toAddress, 38 | _amount, 39 | _payload, 40 | _dstGasForCall, 41 | _callParams.refundAddress, 42 | _callParams.zroPaymentAddress, 43 | _callParams.adapterParams 44 | ); 45 | } 46 | 47 | /************************************************************************ 48 | * public view functions 49 | ************************************************************************/ 50 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 51 | return interfaceId == type(IOFTV2).interfaceId || super.supportsInterface(interfaceId); 52 | } 53 | 54 | function estimateSendFee( 55 | uint16 _dstChainId, 56 | bytes32 _toAddress, 57 | uint _amount, 58 | bool _useZro, 59 | bytes calldata _adapterParams 60 | ) public view virtual override returns (uint nativeFee, uint zroFee) { 61 | return _estimateSendFee(_dstChainId, _toAddress, _amount, _useZro, _adapterParams); 62 | } 63 | 64 | function estimateSendAndCallFee( 65 | uint16 _dstChainId, 66 | bytes32 _toAddress, 67 | uint _amount, 68 | bytes calldata _payload, 69 | uint64 _dstGasForCall, 70 | bool _useZro, 71 | bytes calldata _adapterParams 72 | ) public view virtual override returns (uint nativeFee, uint zroFee) { 73 | return _estimateSendAndCallFee(_dstChainId, _toAddress, _amount, _payload, _dstGasForCall, _useZro, _adapterParams); 74 | } 75 | 76 | function circulatingSupply() public view virtual override returns (uint); 77 | 78 | function token() public view virtual override returns (address); 79 | } 80 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/BaseOFTWithFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../OFTCoreV2.sol"; 6 | import "./IOFTWithFee.sol"; 7 | import "./Fee.sol"; 8 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 9 | 10 | abstract contract BaseOFTWithFee is OFTCoreV2, Fee, ERC165, IOFTWithFee { 11 | 12 | constructor(uint8 _sharedDecimals, address _lzEndpoint) OFTCoreV2(_sharedDecimals, _lzEndpoint) { 13 | } 14 | 15 | /************************************************************************ 16 | * public functions 17 | ************************************************************************/ 18 | function sendFrom(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, LzCallParams calldata _callParams) public payable virtual override { 19 | (_amount,) = _payOFTFee(_from, _dstChainId, _amount); 20 | _amount = _send(_from, _dstChainId, _toAddress, _amount, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 21 | require(_amount >= _minAmount, "BaseOFTWithFee: amount is less than minAmount"); 22 | } 23 | 24 | function sendAndCall(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, bytes calldata _payload, uint64 _dstGasForCall, LzCallParams calldata _callParams) public payable virtual override { 25 | (_amount,) = _payOFTFee(_from, _dstChainId, _amount); 26 | _amount = _sendAndCall(_from, _dstChainId, _toAddress, _amount, _payload, _dstGasForCall, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 27 | require(_amount >= _minAmount, "BaseOFTWithFee: amount is less than minAmount"); 28 | } 29 | 30 | /************************************************************************ 31 | * public view functions 32 | ************************************************************************/ 33 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 34 | return interfaceId == type(IOFTWithFee).interfaceId || super.supportsInterface(interfaceId); 35 | } 36 | 37 | function estimateSendFee(uint16 _dstChainId, bytes32 _toAddress, uint _amount, bool _useZro, bytes calldata _adapterParams) public view virtual override returns (uint nativeFee, uint zroFee) { 38 | return _estimateSendFee(_dstChainId, _toAddress, _amount, _useZro, _adapterParams); 39 | } 40 | 41 | function estimateSendAndCallFee(uint16 _dstChainId, bytes32 _toAddress, uint _amount, bytes calldata _payload, uint64 _dstGasForCall, bool _useZro, bytes calldata _adapterParams) public view virtual override returns (uint nativeFee, uint zroFee) { 42 | return _estimateSendAndCallFee(_dstChainId, _toAddress, _amount, _payload, _dstGasForCall, _useZro, _adapterParams); 43 | } 44 | 45 | function circulatingSupply() public view virtual override returns (uint); 46 | 47 | function token() public view virtual override returns (address); 48 | 49 | function _transferFrom(address _from, address _to, uint _amount) internal virtual override (Fee, OFTCoreV2) returns (uint); 50 | } 51 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/ProxyOFTWithFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./BaseOFTWithFee.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract ProxyOFTWithFee is BaseOFTWithFee { 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 internal immutable innerToken; 12 | uint internal immutable ld2sdRate; 13 | 14 | // total amount is transferred from this chain to other chains, ensuring the total is less than uint64.max in sd 15 | uint public outboundAmount; 16 | 17 | constructor(address _token, uint8 _sharedDecimals, address _lzEndpoint) BaseOFTWithFee(_sharedDecimals, _lzEndpoint) { 18 | innerToken = IERC20(_token); 19 | 20 | (bool success, bytes memory data) = _token.staticcall( 21 | abi.encodeWithSignature("decimals()") 22 | ); 23 | require(success, "ProxyOFTWithFee: failed to get token decimals"); 24 | uint8 decimals = abi.decode(data, (uint8)); 25 | 26 | require(_sharedDecimals <= decimals, "ProxyOFTWithFee: sharedDecimals must be <= decimals"); 27 | ld2sdRate = 10 ** (decimals - _sharedDecimals); 28 | } 29 | 30 | /************************************************************************ 31 | * public functions 32 | ************************************************************************/ 33 | function circulatingSupply() public view virtual override returns (uint) { 34 | return innerToken.totalSupply() - outboundAmount; 35 | } 36 | 37 | function token() public view virtual override returns (address) { 38 | return address(innerToken); 39 | } 40 | 41 | /************************************************************************ 42 | * internal functions 43 | ************************************************************************/ 44 | function _debitFrom(address _from, uint16, bytes32, uint _amount) internal virtual override returns (uint) { 45 | require(_from == _msgSender(), "ProxyOFTWithFee: owner is not send caller"); 46 | 47 | _amount = _transferFrom(_from, address(this), _amount); 48 | 49 | // _amount still may have dust if the token has transfer fee, then give the dust back to the sender 50 | (uint amount, uint dust) = _removeDust(_amount); 51 | if (dust > 0) innerToken.safeTransfer(_from, dust); 52 | 53 | // check total outbound amount 54 | outboundAmount += amount; 55 | uint cap = _sd2ld(type(uint64).max); 56 | require(cap >= outboundAmount, "ProxyOFTWithFee: outboundAmount overflow"); 57 | 58 | return amount; 59 | } 60 | 61 | function _creditTo(uint16, address _toAddress, uint _amount) internal virtual override returns (uint) { 62 | outboundAmount -= _amount; 63 | 64 | // tokens are already in this contract, so no need to transfer 65 | if (_toAddress == address(this)) { 66 | return _amount; 67 | } 68 | 69 | return _transferFrom(address(this), _toAddress, _amount); 70 | } 71 | 72 | function _transferFrom(address _from, address _to, uint _amount) internal virtual override returns (uint) { 73 | uint before = innerToken.balanceOf(_to); 74 | if (_from == address(this)) { 75 | innerToken.safeTransfer(_to, _amount); 76 | } else { 77 | innerToken.safeTransferFrom(_from, _to, _amount); 78 | } 79 | return innerToken.balanceOf(_to) - before; 80 | } 81 | 82 | function _ld2sdRate() internal view virtual override returns (uint) { 83 | return ld2sdRate; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/lzApp/libs/LzLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity >=0.6.0; 4 | pragma experimental ABIEncoderV2; 5 | 6 | library LzLib { 7 | // LayerZero communication 8 | struct CallParams { 9 | address payable refundAddress; 10 | address zroPaymentAddress; 11 | } 12 | 13 | //--------------------------------------------------------------------------- 14 | // Address type handling 15 | 16 | struct AirdropParams { 17 | uint airdropAmount; 18 | bytes32 airdropAddress; 19 | } 20 | 21 | function buildAdapterParams(LzLib.AirdropParams memory _airdropParams, uint _uaGasLimit) internal pure returns (bytes memory adapterParams) { 22 | if (_airdropParams.airdropAmount == 0 && _airdropParams.airdropAddress == bytes32(0x0)) { 23 | adapterParams = buildDefaultAdapterParams(_uaGasLimit); 24 | } else { 25 | adapterParams = buildAirdropAdapterParams(_uaGasLimit, _airdropParams); 26 | } 27 | } 28 | 29 | // Build Adapter Params 30 | function buildDefaultAdapterParams(uint _uaGas) internal pure returns (bytes memory) { 31 | // txType 1 32 | // bytes [2 32 ] 33 | // fields [txType extraGas] 34 | return abi.encodePacked(uint16(1), _uaGas); 35 | } 36 | 37 | function buildAirdropAdapterParams(uint _uaGas, AirdropParams memory _params) internal pure returns (bytes memory) { 38 | require(_params.airdropAmount > 0, "Airdrop amount must be greater than 0"); 39 | require(_params.airdropAddress != bytes32(0x0), "Airdrop address must be set"); 40 | 41 | // txType 2 42 | // bytes [2 32 32 bytes[] ] 43 | // fields [txType extraGas dstNativeAmt dstNativeAddress] 44 | return abi.encodePacked(uint16(2), _uaGas, _params.airdropAmount, _params.airdropAddress); 45 | } 46 | 47 | function getGasLimit(bytes memory _adapterParams) internal pure returns (uint gasLimit) { 48 | require(_adapterParams.length == 34 || _adapterParams.length > 66, "Invalid adapterParams"); 49 | assembly { 50 | gasLimit := mload(add(_adapterParams, 34)) 51 | } 52 | } 53 | 54 | // Decode Adapter Params 55 | function decodeAdapterParams(bytes memory _adapterParams) 56 | internal 57 | pure 58 | returns ( 59 | uint16 txType, 60 | uint uaGas, 61 | uint airdropAmount, 62 | address payable airdropAddress 63 | ) 64 | { 65 | require(_adapterParams.length == 34 || _adapterParams.length > 66, "Invalid adapterParams"); 66 | assembly { 67 | txType := mload(add(_adapterParams, 2)) 68 | uaGas := mload(add(_adapterParams, 34)) 69 | } 70 | require(txType == 1 || txType == 2, "Unsupported txType"); 71 | require(uaGas > 0, "Gas too low"); 72 | 73 | if (txType == 2) { 74 | assembly { 75 | airdropAmount := mload(add(_adapterParams, 66)) 76 | airdropAddress := mload(add(_adapterParams, 86)) 77 | } 78 | } 79 | } 80 | 81 | //--------------------------------------------------------------------------- 82 | // Address type handling 83 | function bytes32ToAddress(bytes32 _bytes32Address) internal pure returns (address _address) { 84 | return address(uint160(uint(_bytes32Address))); 85 | } 86 | 87 | function addressToBytes32(address _address) internal pure returns (bytes32 _bytes32Address) { 88 | return bytes32(uint(uint160(_address))); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tasks/deployWireCheck.js: -------------------------------------------------------------------------------- 1 | const shell = require("shelljs") 2 | const environments = require("../constants/environments.json") 3 | 4 | module.exports = async function (taskArgs) { 5 | const networks = environments[taskArgs.e] 6 | if (!taskArgs.e || networks.length === 0) { 7 | console.log(`Invalid environment argument: ${taskArgs.e}`) 8 | } 9 | 10 | //deploy proxy oft 11 | if (taskArgs.proxyContract !== undefined) { 12 | console.log(`deploying ${taskArgs.proxyContract} to chain ${taskArgs.proxyChain}`) 13 | const deployProxyCommand = `npx hardhat --network ${taskArgs.proxyChain} deploy --tags ${taskArgs.proxyContract}` 14 | console.log("deployProxyCommand: " + deployProxyCommand) 15 | shell.exec(deployProxyCommand) 16 | } 17 | 18 | //deploy oft's 19 | networks.map(async (network) => { 20 | if (network !== taskArgs.proxyChain) { 21 | console.log(`deploying ${taskArgs.contract} to chain ${network}`) 22 | const deployCommand = `npx hardhat --network ${network} deploy --tags ${taskArgs.contract}` 23 | console.log("deployCommand: " + deployCommand) 24 | shell.exec(deployCommand) 25 | } 26 | }) 27 | 28 | //wire 29 | console.log({ networks }) 30 | networks.map(async (source) => { 31 | let srcContract, dstContract 32 | networks.map(async (destination) => { 33 | if (taskArgs.proxyChain) { 34 | if (source === taskArgs.proxyChain && destination === taskArgs.proxyChain) { 35 | srcContract = taskArgs.proxyContract 36 | dstContract = taskArgs.proxyContract 37 | } else if (source === taskArgs.proxyChain) { 38 | srcContract = taskArgs.proxyContract 39 | dstContract = taskArgs.contract 40 | } else if (destination === taskArgs.proxyChain) { 41 | srcContract = taskArgs.contract 42 | dstContract = taskArgs.proxyContract 43 | } else { 44 | srcContract = taskArgs.contract 45 | dstContract = taskArgs.contract 46 | } 47 | } else { 48 | srcContract = taskArgs.contract 49 | dstContract = taskArgs.contract 50 | } 51 | 52 | let wireUpCommand = `npx hardhat --network ${source} setTrustedRemote --target-network ${destination} --local-contract ${srcContract} --remote-contract ${dstContract}` 53 | console.log("wireUpCommand: " + wireUpCommand) 54 | shell.exec(wireUpCommand) 55 | }) 56 | }) 57 | 58 | //check 59 | let checkWireUpCommand 60 | if (taskArgs.proxyChain === undefined) { 61 | checkWireUpCommand = `npx hardhat checkWireUpAll --e ${taskArgs.e} --contract ${taskArgs.contract}` 62 | } else { 63 | checkWireUpCommand = `npx hardhat checkWireUpAll --e ${taskArgs.e} --contract ${taskArgs.contract} --proxy-chain ${taskArgs.proxyChain} --proxy-contract ${taskArgs.proxyContract}` 64 | } 65 | console.log("checkWireUpCommand: " + checkWireUpCommand) 66 | shell.exec(checkWireUpCommand) 67 | 68 | //print addresses 69 | let getAddressesCommand 70 | if (taskArgs.proxyChain !== undefined) { 71 | getAddressesCommand = `node utils/getAddresses ${taskArgs.e} ${taskArgs.proxyContract},${taskArgs.contract}` 72 | } else { 73 | getAddressesCommand = `node utils/getAddresses ${taskArgs.e} ${taskArgs.contract}` 74 | } 75 | console.log("getAddressesCommand: " + getAddressesCommand) 76 | shell.exec(getAddressesCommand) 77 | } 78 | -------------------------------------------------------------------------------- /contracts/lzApp/NonblockingLzApp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./LzApp.sol"; 6 | import "../libraries/ExcessivelySafeCall.sol"; 7 | 8 | /* 9 | * the default LayerZero messaging behaviour is blocking, i.e. any failed message will block the channel 10 | * this abstract class try-catch all fail messages and store locally for future retry. hence, non-blocking 11 | * NOTE: if the srcAddress is not configured properly, it will still block the message pathway from (srcChainId, srcAddress) 12 | */ 13 | abstract contract NonblockingLzApp is LzApp { 14 | using ExcessivelySafeCall for address; 15 | 16 | constructor(address _endpoint) LzApp(_endpoint) {} 17 | 18 | mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) public failedMessages; 19 | 20 | event MessageFailed(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes _payload, bytes _reason); 21 | event RetryMessageSuccess(uint16 _srcChainId, bytes _srcAddress, uint64 _nonce, bytes32 _payloadHash); 22 | 23 | // overriding the virtual function in LzReceiver 24 | function _blockingLzReceive( 25 | uint16 _srcChainId, 26 | bytes memory _srcAddress, 27 | uint64 _nonce, 28 | bytes memory _payload 29 | ) internal virtual override { 30 | (bool success, bytes memory reason) = address(this).excessivelySafeCall( 31 | gasleft(), 32 | 150, 33 | abi.encodeWithSelector(this.nonblockingLzReceive.selector, _srcChainId, _srcAddress, _nonce, _payload) 34 | ); 35 | if (!success) { 36 | _storeFailedMessage(_srcChainId, _srcAddress, _nonce, _payload, reason); 37 | } 38 | } 39 | 40 | function _storeFailedMessage( 41 | uint16 _srcChainId, 42 | bytes memory _srcAddress, 43 | uint64 _nonce, 44 | bytes memory _payload, 45 | bytes memory _reason 46 | ) internal virtual { 47 | failedMessages[_srcChainId][_srcAddress][_nonce] = keccak256(_payload); 48 | emit MessageFailed(_srcChainId, _srcAddress, _nonce, _payload, _reason); 49 | } 50 | 51 | function nonblockingLzReceive( 52 | uint16 _srcChainId, 53 | bytes calldata _srcAddress, 54 | uint64 _nonce, 55 | bytes calldata _payload 56 | ) public virtual { 57 | // only internal transaction 58 | require(_msgSender() == address(this), "NonblockingLzApp: caller must be LzApp"); 59 | _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); 60 | } 61 | 62 | //@notice override this function 63 | function _nonblockingLzReceive( 64 | uint16 _srcChainId, 65 | bytes memory _srcAddress, 66 | uint64 _nonce, 67 | bytes memory _payload 68 | ) internal virtual; 69 | 70 | function retryMessage( 71 | uint16 _srcChainId, 72 | bytes calldata _srcAddress, 73 | uint64 _nonce, 74 | bytes calldata _payload 75 | ) public payable virtual { 76 | // assert there is message to retry 77 | bytes32 payloadHash = failedMessages[_srcChainId][_srcAddress][_nonce]; 78 | require(payloadHash != bytes32(0), "NonblockingLzApp: no stored message"); 79 | require(keccak256(_payload) == payloadHash, "NonblockingLzApp: invalid payload"); 80 | // clear the stored message 81 | failedMessages[_srcChainId][_srcAddress][_nonce] = bytes32(0); 82 | // execute the message. revert if it fails again 83 | _nonblockingLzReceive(_srcChainId, _srcAddress, _nonce, _payload); 84 | emit RetryMessageSuccess(_srcChainId, _srcAddress, _nonce, payloadHash); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/examples/PingPong.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // 4 | // Note: You will need to fund each deployed contract with gas. 5 | // 6 | // PingPong sends a LayerZero message back and forth between chains 7 | // a predetermined number of times (or until it runs out of gas). 8 | // 9 | // Demonstrates: 10 | // 1. a recursive feature of calling send() from inside lzReceive() 11 | // 2. how to `estimateFees` for a send()'ing a LayerZero message 12 | // 3. the contract pays the message fee 13 | 14 | pragma solidity ^0.8.0; 15 | pragma abicoder v2; 16 | 17 | import "../lzApp/NonblockingLzApp.sol"; 18 | 19 | /// @title PingPong 20 | /// @notice Sends a LayerZero message back and forth between chains a predetermined number of times. 21 | contract PingPong is NonblockingLzApp { 22 | 23 | /// @dev event emitted every ping() to keep track of consecutive pings count 24 | event Ping(uint256 pingCount); 25 | 26 | /// @param _endpoint The LayerZero endpoint address. 27 | constructor(address _endpoint) NonblockingLzApp(_endpoint) {} 28 | 29 | /// @notice Pings the destination chain, along with the current number of pings sent. 30 | /// @param _dstChainId The destination chain ID. 31 | /// @param _totalPings The total number of pings to send. 32 | function ping( 33 | uint16 _dstChainId, 34 | uint256 _totalPings 35 | ) public { 36 | _ping(_dstChainId, 0, _totalPings); 37 | } 38 | 39 | /// @dev Internal function to ping the destination chain, along with the current number of pings sent. 40 | /// @param _dstChainId The destination chain ID. 41 | /// @param _pings The current ping count. 42 | /// @param _totalPings The total number of pings to send. 43 | function _ping( 44 | uint16 _dstChainId, 45 | uint256 _pings, 46 | uint256 _totalPings 47 | ) internal { 48 | require(address(this).balance > 0, "This contract ran out of money."); 49 | 50 | // encode the payload with the number of pings 51 | bytes memory payload = abi.encode(_pings, _totalPings); 52 | 53 | // encode the adapter parameters 54 | uint16 version = 1; 55 | uint256 gasForDestinationLzReceive = 350000; 56 | bytes memory adapterParams = abi.encodePacked(version, gasForDestinationLzReceive); 57 | 58 | // send LayerZero message 59 | _lzSend( // {value: messageFee} will be paid out of this contract! 60 | _dstChainId, // destination chainId 61 | payload, // abi.encode()'ed bytes 62 | payable(this), // (msg.sender will be this contract) refund address (LayerZero will refund any extra gas back to caller of send()) 63 | address(0x0), // future param, unused for this example 64 | adapterParams, // v1 adapterParams, specify custom destination gas qty 65 | address(this).balance 66 | ); 67 | } 68 | 69 | /// @dev Internal function to handle incoming Ping messages. 70 | /// @param _srcChainId The source chain ID from which the message originated. 71 | /// @param _payload The payload of the incoming message. 72 | function _nonblockingLzReceive( 73 | uint16 _srcChainId, 74 | bytes memory, /*_srcAddress*/ 75 | uint64, /*_nonce*/ 76 | bytes memory _payload 77 | ) internal override { 78 | // decode the number of pings sent thus far 79 | (uint256 pingCount, uint256 totalPings) = abi.decode(_payload, (uint256, uint256)); 80 | ++pingCount; 81 | emit Ping(pingCount); 82 | 83 | // *pong* back to the other side 84 | if (pingCount < totalPings) { 85 | _ping(_srcChainId, pingCount, totalPings); 86 | } 87 | } 88 | 89 | // allow this contract to receive ether 90 | receive() external payable {} 91 | } 92 | -------------------------------------------------------------------------------- /test/examples/PingPong.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai") 2 | const { ethers } = require("hardhat") 3 | 4 | // fund "to" address by "value" from "signer" 5 | const fund = async (signer, to, value) => { 6 | ;( 7 | await signer.sendTransaction({ 8 | to, 9 | value, 10 | }) 11 | ).wait() 12 | } 13 | 14 | describe("PingPong", async function () { 15 | const chainIdA = 1 16 | const chainIdB = 2 17 | // amount to fund each PingPong instance 18 | const pingPongBalance = ethers.utils.parseEther(".1") 19 | const gasForDstLzReceive = 350000 20 | 21 | let LZEndpointMock, layerZeroEndpointMockA, layerZeroEndpointMockB 22 | let PingPong, pingPongA, pingPongB 23 | let owner 24 | 25 | before(async function () { 26 | LZEndpointMock = await ethers.getContractFactory("LZEndpointMock") 27 | PingPong = await ethers.getContractFactory("PingPong") 28 | owner = (await ethers.getSigners())[0] 29 | }) 30 | 31 | beforeEach(async function () { 32 | layerZeroEndpointMockA = await LZEndpointMock.deploy(chainIdA) 33 | layerZeroEndpointMockB = await LZEndpointMock.deploy(chainIdB) 34 | 35 | // create two PingPong contract instances and provide native token balance 36 | pingPongA = await PingPong.deploy(layerZeroEndpointMockA.address) 37 | await fund(owner, pingPongA.address, pingPongBalance) 38 | pingPongB = await PingPong.deploy(layerZeroEndpointMockB.address) 39 | await fund(owner, pingPongB.address, pingPongBalance) 40 | 41 | await layerZeroEndpointMockA.setDestLzEndpoint(pingPongB.address, layerZeroEndpointMockB.address) 42 | await layerZeroEndpointMockB.setDestLzEndpoint(pingPongA.address, layerZeroEndpointMockA.address) 43 | 44 | // enable bidirectional communication between pingPongA and pingPongB 45 | await pingPongA.setTrustedRemote(chainIdB, ethers.utils.solidityPack(["address", "address"], [pingPongB.address, pingPongA.address])) // for A, set B 46 | await pingPongB.setTrustedRemote(chainIdA, ethers.utils.solidityPack(["address", "address"], [pingPongA.address, pingPongB.address])) // for B, set A 47 | }) 48 | 49 | it("ping back and forth once between PingPong contract instances", async function () { 50 | const startBalanceA = await ethers.provider.getBalance(pingPongA.address) 51 | const startBalanceB = await ethers.provider.getBalance(pingPongB.address) 52 | 53 | // Send one ping from A->B, then one pong back from B->A. Validate B emits a ping with count=1. 54 | await expect(pingPongA.ping(chainIdB, 2)).to.emit(pingPongB, "Ping").withArgs(1) 55 | 56 | // Ensure pingPongA has emitted exactly one Ping with count=2 and no MessageFailed events. 57 | const aPings = await pingPongA.queryFilter(pingPongA.filters.Ping(), 0, "latest") 58 | expect(aPings.length).to.equal(1) 59 | // waffle 3 is incapable of expect'ing multiple emits. 60 | expect(pingPongA.interface.decodeEventLog("Ping", aPings[0].data).pingCount).to.equal(2) 61 | expect((await pingPongA.queryFilter(pingPongA.filters.MessageFailed(), 0, "latest")).length).to.equal(0) 62 | 63 | // Ensure pingPongB has emitted one Ping and no MessageFailed events. 64 | expect((await pingPongB.queryFilter(pingPongB.filters.Ping(), 0, "latest")).length).to.equal(1) 65 | expect((await pingPongB.queryFilter(pingPongB.filters.MessageFailed(), 0, "latest")).length).to.equal(0) 66 | 67 | // Ensure PingPong contract balances have decreased. 68 | expect(await ethers.provider.getBalance(pingPongA.address)).to.be.lt(startBalanceA.sub(gasForDstLzReceive)) 69 | expect(await ethers.provider.getBalance(pingPongB.address)).to.be.lt(startBalanceB.sub(gasForDstLzReceive)) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/ProxyOFTV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./BaseOFTV2.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | 8 | contract ProxyOFTV2 is BaseOFTV2 { 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 internal immutable innerToken; 12 | uint internal immutable ld2sdRate; 13 | 14 | // total amount is transferred from this chain to other chains, ensuring the total is less than uint64.max in sd 15 | uint public outboundAmount; 16 | 17 | constructor( 18 | address _token, 19 | uint8 _sharedDecimals, 20 | address _lzEndpoint 21 | ) BaseOFTV2(_sharedDecimals, _lzEndpoint) { 22 | innerToken = IERC20(_token); 23 | 24 | (bool success, bytes memory data) = _token.staticcall(abi.encodeWithSignature("decimals()")); 25 | require(success, "ProxyOFT: failed to get token decimals"); 26 | uint8 decimals = abi.decode(data, (uint8)); 27 | 28 | require(_sharedDecimals <= decimals, "ProxyOFT: sharedDecimals must be <= decimals"); 29 | ld2sdRate = 10**(decimals - _sharedDecimals); 30 | } 31 | 32 | /************************************************************************ 33 | * public functions 34 | ************************************************************************/ 35 | function circulatingSupply() public view virtual override returns (uint) { 36 | return innerToken.totalSupply() - outboundAmount; 37 | } 38 | 39 | function token() public view virtual override returns (address) { 40 | return address(innerToken); 41 | } 42 | 43 | /************************************************************************ 44 | * internal functions 45 | ************************************************************************/ 46 | function _debitFrom( 47 | address _from, 48 | uint16, 49 | bytes32, 50 | uint _amount 51 | ) internal virtual override returns (uint) { 52 | require(_from == _msgSender(), "ProxyOFT: owner is not send caller"); 53 | 54 | _amount = _transferFrom(_from, address(this), _amount); 55 | 56 | // _amount still may have dust if the token has transfer fee, then give the dust back to the sender 57 | (uint amount, uint dust) = _removeDust(_amount); 58 | if (dust > 0) innerToken.safeTransfer(_from, dust); 59 | 60 | // check total outbound amount 61 | outboundAmount += amount; 62 | uint cap = _sd2ld(type(uint64).max); 63 | require(cap >= outboundAmount, "ProxyOFT: outboundAmount overflow"); 64 | 65 | return amount; 66 | } 67 | 68 | function _creditTo( 69 | uint16, 70 | address _toAddress, 71 | uint _amount 72 | ) internal virtual override returns (uint) { 73 | outboundAmount -= _amount; 74 | 75 | // tokens are already in this contract, so no need to transfer 76 | if (_toAddress == address(this)) { 77 | return _amount; 78 | } 79 | 80 | return _transferFrom(address(this), _toAddress, _amount); 81 | } 82 | 83 | function _transferFrom( 84 | address _from, 85 | address _to, 86 | uint _amount 87 | ) internal virtual override returns (uint) { 88 | uint before = innerToken.balanceOf(_to); 89 | if (_from == address(this)) { 90 | innerToken.safeTransfer(_to, _amount); 91 | } else { 92 | innerToken.safeTransferFrom(_from, _to, _amount); 93 | } 94 | return innerToken.balanceOf(_to) - before; 95 | } 96 | 97 | function _ld2sdRate() internal view virtual override returns (uint) { 98 | return ld2sdRate; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/token/onft721/interfaces/IONFT721Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | /** 8 | * @dev Interface of the ONFT Core standard 9 | */ 10 | interface IONFT721Core is IERC165 { 11 | /** 12 | * @dev Emitted when `_tokenIds[]` are moved from the `_sender` to (`_dstChainId`, `_toAddress`) 13 | * `_nonce` is the outbound nonce from 14 | */ 15 | event SendToChain(uint16 indexed _dstChainId, address indexed _from, bytes indexed _toAddress, uint[] _tokenIds); 16 | event ReceiveFromChain(uint16 indexed _srcChainId, bytes indexed _srcAddress, address indexed _toAddress, uint[] _tokenIds); 17 | event SetMinGasToTransferAndStore(uint _minGasToTransferAndStore); 18 | event SetDstChainIdToTransferGas(uint16 _dstChainId, uint _dstChainIdToTransferGas); 19 | event SetDstChainIdToBatchLimit(uint16 _dstChainId, uint _dstChainIdToBatchLimit); 20 | 21 | /** 22 | * @dev Emitted when `_payload` was received from lz, but not enough gas to deliver all tokenIds 23 | */ 24 | event CreditStored(bytes32 _hashedPayload, bytes _payload); 25 | /** 26 | * @dev Emitted when `_hashedPayload` has been completely delivered 27 | */ 28 | event CreditCleared(bytes32 _hashedPayload); 29 | 30 | /** 31 | * @dev send token `_tokenId` to (`_dstChainId`, `_toAddress`) from `_from` 32 | * `_toAddress` can be any size depending on the `dstChainId`. 33 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 34 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 35 | */ 36 | function sendFrom( 37 | address _from, 38 | uint16 _dstChainId, 39 | bytes calldata _toAddress, 40 | uint _tokenId, 41 | address payable _refundAddress, 42 | address _zroPaymentAddress, 43 | bytes calldata _adapterParams 44 | ) external payable; 45 | 46 | /** 47 | * @dev send tokens `_tokenIds[]` to (`_dstChainId`, `_toAddress`) from `_from` 48 | * `_toAddress` can be any size depending on the `dstChainId`. 49 | * `_zroPaymentAddress` set to address(0x0) if not paying in ZRO (LayerZero Token) 50 | * `_adapterParams` is a flexible bytes array to indicate messaging adapter services 51 | */ 52 | function sendBatchFrom( 53 | address _from, 54 | uint16 _dstChainId, 55 | bytes calldata _toAddress, 56 | uint[] calldata _tokenIds, 57 | address payable _refundAddress, 58 | address _zroPaymentAddress, 59 | bytes calldata _adapterParams 60 | ) external payable; 61 | 62 | /** 63 | * @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`) 64 | * _dstChainId - L0 defined chain id to send tokens too 65 | * _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 66 | * _tokenId - token Id to transfer 67 | * _useZro - indicates to use zro to pay L0 fees 68 | * _adapterParams - flexible bytes array to indicate messaging adapter services in L0 69 | */ 70 | function estimateSendFee( 71 | uint16 _dstChainId, 72 | bytes calldata _toAddress, 73 | uint _tokenId, 74 | bool _useZro, 75 | bytes calldata _adapterParams 76 | ) external view returns (uint nativeFee, uint zroFee); 77 | 78 | /** 79 | * @dev estimate send token `_tokenId` to (`_dstChainId`, `_toAddress`) 80 | * _dstChainId - L0 defined chain id to send tokens too 81 | * _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 82 | * _tokenIds[] - token Ids to transfer 83 | * _useZro - indicates to use zro to pay L0 fees 84 | * _adapterParams - flexible bytes array to indicate messaging adapter services in L0 85 | */ 86 | function estimateSendBatchFee( 87 | uint16 _dstChainId, 88 | bytes calldata _toAddress, 89 | uint[] calldata _tokenIds, 90 | bool _useZro, 91 | bytes calldata _adapterParams 92 | ) external view returns (uint nativeFee, uint zroFee); 93 | } 94 | -------------------------------------------------------------------------------- /contracts/token/onft1155/interfaces/IONFT1155Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | /** 8 | * @dev Interface of the ONFT Core standard 9 | */ 10 | interface IONFT1155Core is IERC165 { 11 | event SendToChain(uint16 indexed _dstChainId, address indexed _from, bytes indexed _toAddress, uint _tokenId, uint _amount); 12 | event SendBatchToChain(uint16 indexed _dstChainId, address indexed _from, bytes indexed _toAddress, uint[] _tokenIds, uint[] _amounts); 13 | event ReceiveFromChain(uint16 indexed _srcChainId, bytes indexed _srcAddress, address indexed _toAddress, uint _tokenId, uint _amount); 14 | event ReceiveBatchFromChain( 15 | uint16 indexed _srcChainId, 16 | bytes indexed _srcAddress, 17 | address indexed _toAddress, 18 | uint[] _tokenIds, 19 | uint[] _amounts 20 | ); 21 | 22 | // _from - address where tokens should be deducted from on behalf of 23 | // _dstChainId - L0 defined chain id to send tokens too 24 | // _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 25 | // _tokenId - token Id to transfer 26 | // _amount - amount of the tokens to transfer 27 | // _refundAddress - address on src that will receive refund for any overpayment of L0 fees 28 | // _zroPaymentAddress - if paying in zro, pass the address to use. using 0x0 indicates not paying fees in zro 29 | // _adapterParams - flexible bytes array to indicate messaging adapter services in L0 30 | function sendFrom( 31 | address _from, 32 | uint16 _dstChainId, 33 | bytes calldata _toAddress, 34 | uint _tokenId, 35 | uint _amount, 36 | address payable _refundAddress, 37 | address _zroPaymentAddress, 38 | bytes calldata _adapterParams 39 | ) external payable; 40 | 41 | // _from - address where tokens should be deducted from on behalf of 42 | // _dstChainId - L0 defined chain id to send tokens too 43 | // _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 44 | // _tokenIds - token Ids to transfer 45 | // _amounts - amounts of the tokens to transfer 46 | // _refundAddress - address on src that will receive refund for any overpayment of L0 fees 47 | // _zroPaymentAddress - if paying in zro, pass the address to use. using 0x0 indicates not paying fees in zro 48 | // _adapterParams - flexible bytes array to indicate messaging adapter services in L0 49 | function sendBatchFrom( 50 | address _from, 51 | uint16 _dstChainId, 52 | bytes calldata _toAddress, 53 | uint[] calldata _tokenIds, 54 | uint[] calldata _amounts, 55 | address payable _refundAddress, 56 | address _zroPaymentAddress, 57 | bytes calldata _adapterParams 58 | ) external payable; 59 | 60 | // _dstChainId - L0 defined chain id to send tokens too 61 | // _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 62 | // _tokenId - token Id to transfer 63 | // _amount - amount of the tokens to transfer 64 | // _useZro - indicates to use zro to pay L0 fees 65 | // _adapterParams - flexible bytes array to indicate messaging adapter services in L0 66 | function estimateSendFee( 67 | uint16 _dstChainId, 68 | bytes calldata _toAddress, 69 | uint _tokenId, 70 | uint _amount, 71 | bool _useZro, 72 | bytes calldata _adapterParams 73 | ) external view returns (uint nativeFee, uint zroFee); 74 | 75 | // _dstChainId - L0 defined chain id to send tokens too 76 | // _toAddress - dynamic bytes array which contains the address to whom you are sending tokens to on the dstChain 77 | // _tokenIds - tokens Id to transfer 78 | // _amounts - amounts of the tokens to transfer 79 | // _useZro - indicates to use zro to pay L0 fees 80 | // _adapterParams - flexible bytes array to indicate messaging adapter services in L0 81 | function estimateSendBatchFee( 82 | uint16 _dstChainId, 83 | bytes calldata _toAddress, 84 | uint[] calldata _tokenIds, 85 | uint[] calldata _amounts, 86 | bool _useZro, 87 | bytes calldata _adapterParams 88 | ) external view returns (uint nativeFee, uint zroFee); 89 | } 90 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/OFTCore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../../lzApp/NonblockingLzApp.sol"; 6 | import "./interfaces/IOFTCore.sol"; 7 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 8 | 9 | abstract contract OFTCore is NonblockingLzApp, ERC165, IOFTCore { 10 | using BytesLib for bytes; 11 | 12 | uint public constant NO_EXTRA_GAS = 0; 13 | 14 | // packet type 15 | uint16 public constant PT_SEND = 0; 16 | 17 | bool public useCustomAdapterParams; 18 | 19 | constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {} 20 | 21 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 22 | return interfaceId == type(IOFTCore).interfaceId || super.supportsInterface(interfaceId); 23 | } 24 | 25 | function estimateSendFee( 26 | uint16 _dstChainId, 27 | bytes calldata _toAddress, 28 | uint _amount, 29 | bool _useZro, 30 | bytes calldata _adapterParams 31 | ) public view virtual override returns (uint nativeFee, uint zroFee) { 32 | // mock the payload for sendFrom() 33 | bytes memory payload = abi.encode(PT_SEND, _toAddress, _amount); 34 | return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams); 35 | } 36 | 37 | function sendFrom( 38 | address _from, 39 | uint16 _dstChainId, 40 | bytes calldata _toAddress, 41 | uint _amount, 42 | address payable _refundAddress, 43 | address _zroPaymentAddress, 44 | bytes calldata _adapterParams 45 | ) public payable virtual override { 46 | _send(_from, _dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams); 47 | } 48 | 49 | function setUseCustomAdapterParams(bool _useCustomAdapterParams) public virtual onlyOwner { 50 | useCustomAdapterParams = _useCustomAdapterParams; 51 | emit SetUseCustomAdapterParams(_useCustomAdapterParams); 52 | } 53 | 54 | function _nonblockingLzReceive( 55 | uint16 _srcChainId, 56 | bytes memory _srcAddress, 57 | uint64 _nonce, 58 | bytes memory _payload 59 | ) internal virtual override { 60 | uint16 packetType; 61 | assembly { 62 | packetType := mload(add(_payload, 32)) 63 | } 64 | 65 | if (packetType == PT_SEND) { 66 | _sendAck(_srcChainId, _srcAddress, _nonce, _payload); 67 | } else { 68 | revert("OFTCore: unknown packet type"); 69 | } 70 | } 71 | 72 | function _send( 73 | address _from, 74 | uint16 _dstChainId, 75 | bytes memory _toAddress, 76 | uint _amount, 77 | address payable _refundAddress, 78 | address _zroPaymentAddress, 79 | bytes memory _adapterParams 80 | ) internal virtual { 81 | _checkAdapterParams(_dstChainId, PT_SEND, _adapterParams, NO_EXTRA_GAS); 82 | 83 | uint amount = _debitFrom(_from, _dstChainId, _toAddress, _amount); 84 | 85 | bytes memory lzPayload = abi.encode(PT_SEND, _toAddress, amount); 86 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, msg.value); 87 | 88 | emit SendToChain(_dstChainId, _from, _toAddress, amount); 89 | } 90 | 91 | function _sendAck( 92 | uint16 _srcChainId, 93 | bytes memory, 94 | uint64, 95 | bytes memory _payload 96 | ) internal virtual { 97 | (, bytes memory toAddressBytes, uint amount) = abi.decode(_payload, (uint16, bytes, uint)); 98 | 99 | address to = toAddressBytes.toAddress(0); 100 | 101 | amount = _creditTo(_srcChainId, to, amount); 102 | emit ReceiveFromChain(_srcChainId, to, amount); 103 | } 104 | 105 | function _checkAdapterParams( 106 | uint16 _dstChainId, 107 | uint16 _pkType, 108 | bytes memory _adapterParams, 109 | uint _extraGas 110 | ) internal virtual { 111 | if (useCustomAdapterParams) { 112 | _checkGasLimit(_dstChainId, _pkType, _adapterParams, _extraGas); 113 | } else { 114 | require(_adapterParams.length == 0, "OFTCore: _adapterParams must be empty."); 115 | } 116 | } 117 | 118 | function _debitFrom( 119 | address _from, 120 | uint16 _dstChainId, 121 | bytes memory _toAddress, 122 | uint _amount 123 | ) internal virtual returns (uint); 124 | 125 | function _creditTo( 126 | uint16 _srcChainId, 127 | address _toAddress, 128 | uint _amount 129 | ) internal virtual returns (uint); 130 | } 131 | -------------------------------------------------------------------------------- /contracts/examples/GasDrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../lzApp/NonblockingLzApp.sol"; 6 | 7 | /// @title GasDrop 8 | /// @notice A contract for sending and receiving gas across chains using LayerZero's NonblockingLzApp. 9 | contract GasDrop is NonblockingLzApp { 10 | 11 | /// @notice The version of the adapterParams. 12 | uint16 public constant VERSION = 2; 13 | 14 | /// @notice The default amount of gas to be used on the destination chain. 15 | uint public dstGas = 25000; 16 | 17 | /// @dev Emitted when the destination gas is updated. 18 | event SetDstGas(uint dstGas); 19 | 20 | /// @dev Emitted when a gas drop is sent. 21 | event SendGasDrop(uint16 indexed _dstChainId, address indexed _from, bytes indexed _toAddress, uint _amount); 22 | 23 | /// @dev Emitted when a gas drop is received on this chain. 24 | event ReceiveGasDrop(uint16 indexed _srcChainId, address indexed _from, bytes indexed _toAddress, uint _amount); 25 | 26 | /// @param _endpoint The LayerZero endpoint address. 27 | constructor(address _endpoint) NonblockingLzApp(_endpoint) {} 28 | 29 | /// @dev Internal function to handle incoming LayerZero messages and emit a ReceiveGasDrop event. 30 | /// @param _srcChainId The source chain ID from where the message originated. 31 | /// @param _payload The payload of the incoming message. 32 | function _nonblockingLzReceive(uint16 _srcChainId, bytes memory, uint64, bytes memory _payload) internal virtual override { 33 | (uint amount, address fromAddress, bytes memory toAddress) = abi.decode(_payload, (uint, address, bytes)); 34 | emit ReceiveGasDrop(_srcChainId, fromAddress, toAddress, amount); 35 | } 36 | 37 | /// @notice Estimate the fee for sending a gas drop to other chains. 38 | /// @param _dstChainId Array of destination chain IDs. 39 | /// @param _toAddress Array of destination addresses. 40 | /// @param _amount Array of amounts to send. 41 | /// @param _useZro Whether to use ZRO for payment or not. 42 | /// @return nativeFee The total native fee for all destinations. 43 | /// @return zroFee The total ZRO fee for all destinations. 44 | function estimateSendFee(uint16[] calldata _dstChainId, bytes[] calldata _toAddress, uint[] calldata _amount, bool _useZro) external view virtual returns (uint nativeFee, uint zroFee) { 45 | require(_dstChainId.length == _toAddress.length, "_dstChainId and _toAddress must be same size"); 46 | require(_toAddress.length == _amount.length, "_toAddress and _amount must be same size"); 47 | for(uint i = 0; i < _dstChainId.length; i++) { 48 | bytes memory adapterParams = abi.encodePacked(VERSION, dstGas, _amount[i], _toAddress[i]); 49 | bytes memory payload = abi.encode(_amount[i], msg.sender, _toAddress[i]); 50 | (uint native, uint zro) = lzEndpoint.estimateFees(_dstChainId[i], address(this), payload, _useZro, adapterParams); 51 | nativeFee += native; 52 | zroFee += zro; 53 | } 54 | } 55 | 56 | /// @notice Send gas drops to other chains. 57 | /// @param _dstChainId Array of destination chain IDs. 58 | /// @param _toAddress Array of destination addresses. 59 | /// @param _amount Array of amounts to send. 60 | /// @param _refundAddress Address for refunds. 61 | /// @param _zroPaymentAddress Address for ZRO payments. 62 | function gasDrop(uint16[] calldata _dstChainId, bytes[] calldata _toAddress, uint[] calldata _amount, address payable _refundAddress, address _zroPaymentAddress) external payable virtual { 63 | require(_dstChainId.length == _toAddress.length, "_dstChainId and _toAddress must be same size"); 64 | require(_toAddress.length == _amount.length, "_toAddress and _amount must be same size"); 65 | uint _dstGas = dstGas; 66 | for(uint i = 0; i < _dstChainId.length; i++) { 67 | bytes memory adapterParams = abi.encodePacked(VERSION, _dstGas, _amount[i], _toAddress[i]); 68 | bytes memory payload = abi.encode(_amount[i], msg.sender, _toAddress[i]); 69 | address payable refundAddress = (i == _dstChainId.length - 1) ? _refundAddress : payable(address(this)); 70 | _lzSend(_dstChainId[i], payload, refundAddress, _zroPaymentAddress, adapterParams, address(this).balance); 71 | emit SendGasDrop(_dstChainId[i], msg.sender, _toAddress[i], _amount[i]); 72 | } 73 | } 74 | 75 | /// @notice Update the destination gas amount. 76 | /// @param _dstGas The new destination gas amount. 77 | function setDstGas(uint _dstGas) external onlyOwner { 78 | dstGas = _dstGas; 79 | emit SetDstGas(dstGas); 80 | } 81 | 82 | /// @dev Fallback function to receive Ether. 83 | receive() external payable {} 84 | } 85 | -------------------------------------------------------------------------------- /contracts/token/oft/v1/NativeOFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "./OFT.sol"; 7 | 8 | contract NativeOFT is OFT, ReentrancyGuard { 9 | event Deposit(address indexed _dst, uint _amount); 10 | event Withdrawal(address indexed _src, uint _amount); 11 | 12 | constructor( 13 | string memory _name, 14 | string memory _symbol, 15 | address _lzEndpoint 16 | ) OFT(_name, _symbol, _lzEndpoint) {} 17 | 18 | function sendFrom( 19 | address _from, 20 | uint16 _dstChainId, 21 | bytes calldata _toAddress, 22 | uint _amount, 23 | address payable _refundAddress, 24 | address _zroPaymentAddress, 25 | bytes calldata _adapterParams 26 | ) public payable virtual override(OFTCore, IOFTCore) { 27 | _send(_from, _dstChainId, _toAddress, _amount, _refundAddress, _zroPaymentAddress, _adapterParams); 28 | } 29 | 30 | function _send( 31 | address _from, 32 | uint16 _dstChainId, 33 | bytes memory _toAddress, 34 | uint _amount, 35 | address payable _refundAddress, 36 | address _zroPaymentAddress, 37 | bytes memory _adapterParams 38 | ) internal virtual override(OFTCore) { 39 | uint messageFee = _debitFromNative(_from, _dstChainId, _toAddress, _amount); 40 | bytes memory lzPayload = abi.encode(PT_SEND, _toAddress, _amount); 41 | 42 | if (useCustomAdapterParams) { 43 | _checkGasLimit(_dstChainId, PT_SEND, _adapterParams, NO_EXTRA_GAS); 44 | } else { 45 | require(_adapterParams.length == 0, "NativeOFT: _adapterParams must be empty."); 46 | } 47 | 48 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, messageFee); 49 | } 50 | 51 | function deposit() public payable { 52 | _mint(msg.sender, msg.value); 53 | emit Deposit(msg.sender, msg.value); 54 | } 55 | 56 | function withdraw(uint _amount) public nonReentrant { 57 | require(balanceOf(msg.sender) >= _amount, "NativeOFT: Insufficient balance."); 58 | _burn(msg.sender, _amount); 59 | (bool success, ) = msg.sender.call{value: _amount}(""); 60 | require(success, "NativeOFT: failed to unwrap"); 61 | emit Withdrawal(msg.sender, _amount); 62 | } 63 | 64 | function _debitFromNative( 65 | address _from, 66 | uint16, 67 | bytes memory, 68 | uint _amount 69 | ) internal returns (uint messageFee) { 70 | messageFee = msg.sender == _from ? _debitMsgSender(_amount) : _debitMsgFrom(_from, _amount); 71 | } 72 | 73 | function _debitMsgSender(uint _amount) internal returns (uint messageFee) { 74 | uint msgSenderBalance = balanceOf(msg.sender); 75 | 76 | if (msgSenderBalance < _amount) { 77 | require(msgSenderBalance + msg.value >= _amount, "NativeOFT: Insufficient msg.value"); 78 | 79 | // user can cover difference with additional msg.value ie. wrapping 80 | uint mintAmount = _amount - msgSenderBalance; 81 | _mint(address(msg.sender), mintAmount); 82 | 83 | // update the messageFee to take out mintAmount 84 | messageFee = msg.value - mintAmount; 85 | } else { 86 | messageFee = msg.value; 87 | } 88 | 89 | _transfer(msg.sender, address(this), _amount); 90 | return messageFee; 91 | } 92 | 93 | function _debitMsgFrom(address _from, uint _amount) internal returns (uint messageFee) { 94 | uint msgFromBalance = balanceOf(_from); 95 | 96 | if (msgFromBalance < _amount) { 97 | require(msgFromBalance + msg.value >= _amount, "NativeOFT: Insufficient msg.value"); 98 | 99 | // user can cover difference with additional msg.value ie. wrapping 100 | uint mintAmount = _amount - msgFromBalance; 101 | _mint(address(msg.sender), mintAmount); 102 | 103 | // transfer the differential amount to the contract 104 | _transfer(msg.sender, address(this), mintAmount); 105 | 106 | // overwrite the _amount to take the rest of the balance from the _from address 107 | _amount = msgFromBalance; 108 | 109 | // update the messageFee to take out mintAmount 110 | messageFee = msg.value - mintAmount; 111 | } else { 112 | messageFee = msg.value; 113 | } 114 | 115 | _spendAllowance(_from, msg.sender, _amount); 116 | _transfer(_from, address(this), _amount); 117 | return messageFee; 118 | } 119 | 120 | function _creditTo( 121 | uint16, 122 | address _toAddress, 123 | uint _amount 124 | ) internal override(OFT) returns (uint) { 125 | _burn(address(this), _amount); 126 | (bool success, ) = _toAddress.call{value: _amount}(""); 127 | require(success, "NativeOFT: failed to _creditTo"); 128 | return _amount; 129 | } 130 | 131 | receive() external payable { 132 | deposit(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/mocks/OFTStakingMockV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import "../interfaces/IOFTV2.sol"; 8 | import "../interfaces/IOFTReceiverV2.sol"; 9 | import "../../../../libraries/BytesLib.sol"; 10 | 11 | // OFTStakingMock is an example to integrate with OFT. It shows how to send OFT cross chain with a custom payload and 12 | // call a receiver contract on the destination chain when oft is received. 13 | contract OFTStakingMockV2 is IOFTReceiverV2 { 14 | using SafeERC20 for IERC20; 15 | using BytesLib for bytes; 16 | 17 | uint64 public constant DST_GAS_FOR_CALL = 300000; // estimate gas usage of onOFTReceived() 18 | 19 | // packet type 20 | uint8 public constant PT_DEPOSIT_TO_REMOTE_CHAIN = 1; 21 | // ... other types 22 | 23 | // variables 24 | IOFTV2 public oft; 25 | mapping(uint16 => bytes32) public remoteStakingContracts; 26 | mapping(address => uint) public balances; 27 | bool public paused; // for testing try/catch 28 | 29 | event Deposit(address from, uint amount); 30 | event Withdrawal(address to, uint amount); 31 | event DepositToDstChain(address from, uint16 dstChainId, bytes to, uint amountOut); 32 | 33 | // _oft can be any composable OFT contract, e.g. ComposableOFT, ComposableBasedOFT and ComposableProxyOFT. 34 | constructor(address _oft) { 35 | oft = IOFTV2(_oft); 36 | IERC20(oft.token()).safeApprove(_oft, type(uint).max); 37 | } 38 | 39 | function setRemoteStakingContract(uint16 _chainId, bytes32 _stakingContract) external { 40 | remoteStakingContracts[_chainId] = _stakingContract; 41 | } 42 | 43 | function deposit(uint _amount) external payable { 44 | IERC20(oft.token()).safeTransferFrom(msg.sender, address(this), _amount); 45 | balances[msg.sender] += _amount; 46 | emit Deposit(msg.sender, _amount); 47 | } 48 | 49 | function withdraw(uint _amount) external { 50 | withdrawTo(_amount, msg.sender); 51 | } 52 | 53 | function withdrawTo(uint _amount, address _to) public { 54 | require(balances[msg.sender] >= _amount); 55 | balances[msg.sender] -= _amount; 56 | IERC20(oft.token()).safeTransfer(_to, _amount); 57 | emit Withdrawal(msg.sender, _amount); 58 | } 59 | 60 | function depositToDstChain( 61 | uint16 _dstChainId, 62 | bytes calldata _to, // address of the owner of token on the destination chain 63 | uint _amount, // amount of token to deposit 64 | bytes calldata _adapterParams 65 | ) external payable { 66 | bytes32 dstStakingContract = remoteStakingContracts[_dstChainId]; 67 | require(dstStakingContract != bytes32(0), "invalid _dstChainId"); 68 | 69 | // transfer token from sender to this contract 70 | // if the oft is not the proxy oft, dont need to transfer token to this contract 71 | // and call sendAndCall() with the msg.sender (_from) instead of address(this) 72 | // here we use a common pattern to be compatible with all kinds of composable OFT 73 | IERC20(oft.token()).safeTransferFrom(msg.sender, address(this), _amount); 74 | 75 | bytes memory payload = abi.encode(PT_DEPOSIT_TO_REMOTE_CHAIN, _to); 76 | ICommonOFT.LzCallParams memory callParams = ICommonOFT.LzCallParams(payable(msg.sender), address(0), _adapterParams); 77 | oft.sendAndCall{value: msg.value}(address(this), _dstChainId, dstStakingContract, _amount, payload, DST_GAS_FOR_CALL, callParams); 78 | 79 | emit DepositToDstChain(msg.sender, _dstChainId, _to, _amount); 80 | } 81 | 82 | function quoteForDeposit( 83 | uint16 _dstChainId, 84 | bytes calldata _to, // address of the owner of token on the destination chain 85 | uint _amount, // amount of token to deposit 86 | bytes calldata _adapterParams 87 | ) public view returns (uint nativeFee, uint zroFee) { 88 | bytes32 dstStakingContract = remoteStakingContracts[_dstChainId]; 89 | require(dstStakingContract != bytes32(0), "invalid _dstChainId"); 90 | 91 | bytes memory payload = abi.encode(PT_DEPOSIT_TO_REMOTE_CHAIN, _to); 92 | return oft.estimateSendAndCallFee(_dstChainId, dstStakingContract, _amount, payload, DST_GAS_FOR_CALL, false, _adapterParams); 93 | } 94 | 95 | //----------------------------------------------------------------------------------------------------------------------- 96 | function onOFTReceived(uint16 _srcChainId, bytes calldata, uint64, bytes32 _from, uint _amount, bytes memory _payload) external override { 97 | require(!paused, "paused"); // for testing safe call 98 | require(msg.sender == address(oft), "only oft can call onOFTReceived()"); 99 | require(_from == remoteStakingContracts[_srcChainId], "invalid from"); 100 | 101 | uint8 pkType; 102 | assembly { 103 | pkType := mload(add(_payload, 32)) 104 | } 105 | 106 | if (pkType == PT_DEPOSIT_TO_REMOTE_CHAIN) { 107 | (, bytes memory toAddrBytes) = abi.decode(_payload, (uint8, bytes)); 108 | 109 | address to = toAddrBytes.toAddress(0); 110 | balances[to] += _amount; 111 | } else { 112 | revert("invalid deposit type"); 113 | } 114 | } 115 | 116 | function setPaused(bool _paused) external { 117 | paused = _paused; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config() 2 | 3 | // uncomment to include contract sizing in test output 4 | // require("hardhat-contract-sizer") 5 | require("@nomiclabs/hardhat-waffle") 6 | require(`@nomiclabs/hardhat-etherscan`) 7 | require("solidity-coverage") 8 | // uncomment to include gas reporting in test output 9 | //require('hardhat-gas-reporter') 10 | require("hardhat-deploy") 11 | require("hardhat-deploy-ethers") 12 | require("@openzeppelin/hardhat-upgrades") 13 | require("./tasks") 14 | 15 | // This is a sample Hardhat task. To learn how to create your own go to 16 | // https://hardhat.org/guides/create-task.html 17 | task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { 18 | const accounts = await hre.ethers.getSigners() 19 | 20 | for (const account of accounts) { 21 | console.log(account.address) 22 | } 23 | }) 24 | 25 | function getMnemonic(networkName) { 26 | if (networkName) { 27 | const mnemonic = process.env["MNEMONIC_" + networkName.toUpperCase()] 28 | if (mnemonic && mnemonic !== "") { 29 | return mnemonic 30 | } 31 | } 32 | 33 | const mnemonic = process.env.MNEMONIC 34 | if (!mnemonic || mnemonic === "") { 35 | return "test test test test test test test test test test test junk" 36 | } 37 | 38 | return mnemonic 39 | } 40 | 41 | function accounts(chainKey) { 42 | return { mnemonic: getMnemonic(chainKey) } 43 | } 44 | 45 | // You need to export an object to set up your config 46 | // Go to https://hardhat.org/config/ to learn more 47 | 48 | /** 49 | * @type import('hardhat/config').HardhatUserConfig 50 | */ 51 | module.exports = { 52 | solidity: { 53 | compilers: [ 54 | { 55 | version: "0.8.4", 56 | settings: { 57 | optimizer: { 58 | enabled: true, 59 | runs: 200, 60 | }, 61 | }, 62 | }, 63 | { 64 | version: "0.7.6", 65 | settings: { 66 | optimizer: { 67 | enabled: true, 68 | runs: 200, 69 | }, 70 | }, 71 | }, 72 | { 73 | version: "0.8.12", 74 | settings: { 75 | optimizer: { 76 | enabled: true, 77 | runs: 200, 78 | }, 79 | }, 80 | }, 81 | ], 82 | }, 83 | 84 | // solidity: "0.8.4", 85 | contractSizer: { 86 | alphaSort: false, 87 | runOnCompile: true, 88 | disambiguatePaths: false, 89 | }, 90 | 91 | namedAccounts: { 92 | deployer: { 93 | default: 0, // wallet address 0, of the mnemonic in .env 94 | }, 95 | proxyOwner: { 96 | default: 1, 97 | }, 98 | }, 99 | 100 | mocha: { 101 | timeout: 100000000, 102 | }, 103 | 104 | networks: { 105 | ethereum: { 106 | url: "https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", // public infura endpoint 107 | chainId: 1, 108 | accounts: accounts(), 109 | }, 110 | bsc: { 111 | url: "https://bsc-dataseed1.binance.org", 112 | chainId: 56, 113 | accounts: accounts(), 114 | }, 115 | avalanche: { 116 | url: "https://api.avax.network/ext/bc/C/rpc", 117 | chainId: 43114, 118 | accounts: accounts(), 119 | }, 120 | polygon: { 121 | url: "https://rpc-mainnet.maticvigil.com", 122 | chainId: 137, 123 | accounts: accounts(), 124 | }, 125 | arbitrum: { 126 | url: `https://arb1.arbitrum.io/rpc`, 127 | chainId: 42161, 128 | accounts: accounts(), 129 | }, 130 | optimism: { 131 | url: `https://mainnet.optimism.io`, 132 | chainId: 10, 133 | accounts: accounts(), 134 | }, 135 | fantom: { 136 | url: `https://rpcapi.fantom.network`, 137 | chainId: 250, 138 | accounts: accounts(), 139 | }, 140 | metis: { 141 | url: `https://andromeda.metis.io/?owner=1088`, 142 | chainId: 1088, 143 | accounts: accounts(), 144 | }, 145 | 146 | goerli: { 147 | url: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", // public infura endpoint 148 | chainId: 5, 149 | accounts: accounts(), 150 | }, 151 | "bsc-testnet": { 152 | url: "https://data-seed-prebsc-1-s1.binance.org:8545/", 153 | chainId: 97, 154 | accounts: accounts(), 155 | }, 156 | fuji: { 157 | url: `https://api.avax-test.network/ext/bc/C/rpc`, 158 | chainId: 43113, 159 | accounts: accounts(), 160 | }, 161 | mumbai: { 162 | url: "https://rpc-mumbai.maticvigil.com/", 163 | chainId: 80001, 164 | accounts: accounts(), 165 | }, 166 | "arbitrum-goerli": { 167 | url: `https://goerli-rollup.arbitrum.io/rpc/`, 168 | chainId: 421613, 169 | accounts: accounts(), 170 | }, 171 | "optimism-goerli": { 172 | url: `https://goerli.optimism.io/`, 173 | chainId: 420, 174 | accounts: accounts(), 175 | }, 176 | "fantom-testnet": { 177 | url: `https://rpc.ankr.com/fantom_testnet`, 178 | chainId: 4002, 179 | accounts: accounts(), 180 | }, 181 | }, 182 | } 183 | -------------------------------------------------------------------------------- /contracts/libraries/ExcessivelySafeCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity >=0.7.6; 3 | 4 | library ExcessivelySafeCall { 5 | uint constant LOW_28_MASK = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 6 | 7 | /// @notice Use when you _really_ really _really_ don't trust the called 8 | /// contract. This prevents the called contract from causing reversion of 9 | /// the caller in as many ways as we can. 10 | /// @dev The main difference between this and a solidity low-level call is 11 | /// that we limit the number of bytes that the callee can cause to be 12 | /// copied to caller memory. This prevents stupid things like malicious 13 | /// contracts returning 10,000,000 bytes causing a local OOG when copying 14 | /// to memory. 15 | /// @param _target The address to call 16 | /// @param _gas The amount of gas to forward to the remote contract 17 | /// @param _maxCopy The maximum number of bytes of returndata to copy 18 | /// to memory. 19 | /// @param _calldata The data to send to the remote contract 20 | /// @return success and returndata, as `.call()`. Returndata is capped to 21 | /// `_maxCopy` bytes. 22 | function excessivelySafeCall( 23 | address _target, 24 | uint _gas, 25 | uint16 _maxCopy, 26 | bytes memory _calldata 27 | ) internal returns (bool, bytes memory) { 28 | // set up for assembly call 29 | uint _toCopy; 30 | bool _success; 31 | bytes memory _returnData = new bytes(_maxCopy); 32 | // dispatch message to recipient 33 | // by assembly calling "handle" function 34 | // we call via assembly to avoid memcopying a very large returndata 35 | // returned by a malicious contract 36 | assembly { 37 | _success := call( 38 | _gas, // gas 39 | _target, // recipient 40 | 0, // ether value 41 | add(_calldata, 0x20), // inloc 42 | mload(_calldata), // inlen 43 | 0, // outloc 44 | 0 // outlen 45 | ) 46 | // limit our copy to 256 bytes 47 | _toCopy := returndatasize() 48 | if gt(_toCopy, _maxCopy) { 49 | _toCopy := _maxCopy 50 | } 51 | // Store the length of the copied bytes 52 | mstore(_returnData, _toCopy) 53 | // copy the bytes from returndata[0:_toCopy] 54 | returndatacopy(add(_returnData, 0x20), 0, _toCopy) 55 | } 56 | return (_success, _returnData); 57 | } 58 | 59 | /// @notice Use when you _really_ really _really_ don't trust the called 60 | /// contract. This prevents the called contract from causing reversion of 61 | /// the caller in as many ways as we can. 62 | /// @dev The main difference between this and a solidity low-level call is 63 | /// that we limit the number of bytes that the callee can cause to be 64 | /// copied to caller memory. This prevents stupid things like malicious 65 | /// contracts returning 10,000,000 bytes causing a local OOG when copying 66 | /// to memory. 67 | /// @param _target The address to call 68 | /// @param _gas The amount of gas to forward to the remote contract 69 | /// @param _maxCopy The maximum number of bytes of returndata to copy 70 | /// to memory. 71 | /// @param _calldata The data to send to the remote contract 72 | /// @return success and returndata, as `.call()`. Returndata is capped to 73 | /// `_maxCopy` bytes. 74 | function excessivelySafeStaticCall( 75 | address _target, 76 | uint _gas, 77 | uint16 _maxCopy, 78 | bytes memory _calldata 79 | ) internal view returns (bool, bytes memory) { 80 | // set up for assembly call 81 | uint _toCopy; 82 | bool _success; 83 | bytes memory _returnData = new bytes(_maxCopy); 84 | // dispatch message to recipient 85 | // by assembly calling "handle" function 86 | // we call via assembly to avoid memcopying a very large returndata 87 | // returned by a malicious contract 88 | assembly { 89 | _success := staticcall( 90 | _gas, // gas 91 | _target, // recipient 92 | add(_calldata, 0x20), // inloc 93 | mload(_calldata), // inlen 94 | 0, // outloc 95 | 0 // outlen 96 | ) 97 | // limit our copy to 256 bytes 98 | _toCopy := returndatasize() 99 | if gt(_toCopy, _maxCopy) { 100 | _toCopy := _maxCopy 101 | } 102 | // Store the length of the copied bytes 103 | mstore(_returnData, _toCopy) 104 | // copy the bytes from returndata[0:_toCopy] 105 | returndatacopy(add(_returnData, 0x20), 0, _toCopy) 106 | } 107 | return (_success, _returnData); 108 | } 109 | 110 | /** 111 | * @notice Swaps function selectors in encoded contract calls 112 | * @dev Allows reuse of encoded calldata for functions with identical 113 | * argument types but different names. It simply swaps out the first 4 bytes 114 | * for the new selector. This function modifies memory in place, and should 115 | * only be used with caution. 116 | * @param _newSelector The new 4-byte selector 117 | * @param _buf The encoded contract args 118 | */ 119 | function swapSelector(bytes4 _newSelector, bytes memory _buf) internal pure { 120 | require(_buf.length >= 4); 121 | uint _mask = LOW_28_MASK; 122 | assembly { 123 | // load the first word of 124 | let _word := mload(add(_buf, 0x20)) 125 | // mask out the top 4 bytes 126 | // /x 127 | _word := and(_word, _mask) 128 | _word := or(_newSelector, _word) 129 | mstore(add(_buf, 0x20), _word) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /tasks/cachedSwapSavedParse.js: -------------------------------------------------------------------------------- 1 | const CHAIN_ID = { 2 | "ethereum": 101, 3 | "bsc": 102, 4 | "avalanche": 106, 5 | "polygon": 109, 6 | "arbitrum": 110, 7 | "optimism": 111, 8 | "fantom": 112, 9 | "metis": 151, 10 | "base": 184, 11 | "linea": 183, 12 | "kava": 177 13 | } 14 | 15 | const STG_FACTORIES = { 16 | "ethereum": "0x06D538690AF257Da524f25D0CD52fD85b1c2173E", 17 | "bsc": "0xe7Ec689f432f29383f217e36e680B5C855051f25", 18 | "avalanche": "0x808d7c71ad2ba3FA531b068a2417C63106BC0949", 19 | "polygon": "0x808d7c71ad2ba3FA531b068a2417C63106BC0949", 20 | "arbitrum": "0x55bDb4164D28FBaF0898e0eF14a589ac09Ac9970", 21 | "optimism": "0xE3B53AF74a4BF62Ae5511055290838050bf764Df", 22 | "fantom": "0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944", 23 | "metis": "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398", 24 | "kava": "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398", 25 | "linea": "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398", 26 | "base": "0xAf5191B0De278C7286d6C7CC6ab6BB8A73bA2Cd6" 27 | } 28 | 29 | const STG_BRIDGE = { 30 | "ethereum": "0x296F55F8Fb28E498B858d0BcDA06D955B2Cb3f97", 31 | "bsc": "0x6694340fc020c5E6B96567843da2df01b2CE1eb6", 32 | "avalanche": "0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944", 33 | "polygon": "0x9d1B1669c73b033DFe47ae5a0164Ab96df25B944", 34 | "arbitrum": "0x352d8275AAE3e0c2404d9f68f6cEE084B5bEB3DD", 35 | "optimism": "0x701a95707A0290AC8B90b3719e8EE5b210360883", 36 | "fantom": "0x45A01E4e04F14f7A4a6702c74187c5F6222033cd", 37 | "metis": "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B", 38 | "kava": "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B", 39 | "linea": "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B", 40 | "base": "0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398" 41 | } 42 | 43 | const SWAP_REMOTE_UA_PAYLOAD_ENCODING = [ 44 | 'uint8', // TYPE_SWAP_REMOTE 45 | 'uint256', // _srcPoolId 46 | 'uint256', // _dstPoolId 47 | 'uint256', // _lzTxParams.dstGasForCall 48 | 'creditObj(uint256,uint256)', // _c 49 | 'swapObj(uint256,uint256,uint256,uint256,uint256,uint256)', // _s 50 | 'bytes', // _to 51 | 'bytes', // _payload 52 | ] 53 | 54 | let FACTORY_ABI = [ 55 | "function getPool(uint256) view returns(address)" 56 | ]; 57 | 58 | let POOL_ABI = [ 59 | "function token() view returns(address)", 60 | "function convertRate() view returns(uint256)" 61 | ]; 62 | 63 | const STARGATE_RECEIVER_INTERFACE_ABI = [ 64 | "function sgReceive(uint16 _chainId, bytes memory _srcAddress, uint256 _nonce, address _token, uint256 amountLD, bytes memory payload)" 65 | ]; 66 | 67 | const STARGATE_COMPOSER_ABI = [ 68 | "function clearCachedSwap(uint16 _srcChainId,bytes calldata _srcAddress,uint64 _nonce,address _receiver,bytes calldata _sgReceiveCallData)", 69 | "function payloadHashes(uint16,bytes,uint256) view returns(bytes32)" 70 | ]; 71 | 72 | const STARGATE_COMPOSER_ADDRESS = "0xeCc19E177d24551aA7ed6Bc6FE566eCa726CC8a9" 73 | const DST_POOL_ID_INDEX = 2; 74 | const SWAP_OBJ_INDEX = 5; 75 | const AMOUNT_INDEX = 0 76 | const EQ_REWARD_INDEX = 2; 77 | const PAYLOAD_INDEX = 7; 78 | 79 | module.exports = async function (taskArgs, hre) { 80 | const srcNetwork = taskArgs.srcNetwork; 81 | const nonce = taskArgs.nonce; 82 | const uaPayload = taskArgs.uaPayload 83 | 84 | const srcChainId = CHAIN_ID[srcNetwork]; 85 | const srcAddress = hre.ethers.utils.solidityPack( 86 | ['address','address'], 87 | [STG_BRIDGE[srcNetwork], STG_BRIDGE[hre.network.name]] 88 | ) 89 | 90 | const decodedPayload = ethers.utils.defaultAbiCoder.decode( 91 | SWAP_REMOTE_UA_PAYLOAD_ENCODING, 92 | uaPayload, 93 | ) 94 | 95 | const factoryAddress = STG_FACTORIES[hre.network.name] 96 | const factory = await ethers.getContractAt(FACTORY_ABI, factoryAddress); 97 | 98 | const dstPoolId = decodedPayload[DST_POOL_ID_INDEX]; 99 | const poolAddress = await factory.getPool(dstPoolId.valueOf()) 100 | const pool = await ethers.getContractAt(POOL_ABI, poolAddress); 101 | 102 | const token = await pool.token(); 103 | const convertRate = (await pool.convertRate()).valueOf(); 104 | 105 | const amount = decodedPayload[SWAP_OBJ_INDEX][AMOUNT_INDEX].valueOf() 106 | const eqReward = decodedPayload[SWAP_OBJ_INDEX][EQ_REWARD_INDEX].valueOf() 107 | const amountLD = (amount + eqReward) * convertRate; 108 | 109 | const receiver = ethers.utils.hexDataSlice(decodedPayload[PAYLOAD_INDEX], 0, 20) 110 | const callDataSrcAddress = ethers.utils.hexDataSlice(decodedPayload[PAYLOAD_INDEX], 20, 40) 111 | const payload = ethers.utils.hexDataSlice(decodedPayload[PAYLOAD_INDEX], 40) 112 | 113 | const interfaceStargateReceiver = new ethers.utils.Interface(STARGATE_RECEIVER_INTERFACE_ABI); 114 | const sgReceiveCallData = interfaceStargateReceiver.encodeFunctionData("sgReceive", [ srcChainId, callDataSrcAddress, nonce, token, amountLD, payload]) 115 | 116 | console.log({srcChainId,srcAddress,nonce,receiver,sgReceiveCallData}) 117 | 118 | if(taskArgs.clear) { 119 | const stargateComposer = await ethers.getContractAt(STARGATE_COMPOSER_ABI, STARGATE_COMPOSER_ADDRESS); 120 | const currentPayloadHash = await stargateComposer.payloadHashes(srcChainId,srcAddress,nonce); 121 | const encodedPacked = ethers.utils.solidityPack(["address", "bytes"], [receiver, sgReceiveCallData]) 122 | const hash = ethers.utils.keccak256(encodedPacked); 123 | 124 | if(currentPayloadHash === ethers.constants.HashZero) { 125 | console.log("Nothing to clear. Cache Swap empty."); 126 | return 127 | } else if(currentPayloadHash !== hash) { 128 | console.log("Cached Payload Hash doesnt match."); 129 | return 130 | } 131 | 132 | try { 133 | const tx = await (await stargateComposer.clearCachedSwap(srcChainId,srcAddress,nonce,receiver,sgReceiveCallData)).wait(); 134 | console.log(`tx: ${tx.transactionHash}`) 135 | } catch (e) { 136 | if(e?.error?.message) { 137 | console.log(e.error.message) 138 | } else { 139 | console.log(e) 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /contracts/lzApp/interfaces/ILayerZeroEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | import "./ILayerZeroUserApplicationConfig.sol"; 6 | 7 | interface ILayerZeroEndpoint is ILayerZeroUserApplicationConfig { 8 | // @notice send a LayerZero message to the specified address at a LayerZero endpoint. 9 | // @param _dstChainId - the destination chain identifier 10 | // @param _destination - the address on destination chain (in bytes). address length/format may vary by chains 11 | // @param _payload - a custom bytes payload to send to the destination contract 12 | // @param _refundAddress - if the source transaction is cheaper than the amount of value passed, refund the additional amount to this address 13 | // @param _zroPaymentAddress - the address of the ZRO token holder who would pay for the transaction 14 | // @param _adapterParams - parameters for custom functionality. e.g. receive airdropped native gas from the relayer on destination 15 | function send( 16 | uint16 _dstChainId, 17 | bytes calldata _destination, 18 | bytes calldata _payload, 19 | address payable _refundAddress, 20 | address _zroPaymentAddress, 21 | bytes calldata _adapterParams 22 | ) external payable; 23 | 24 | // @notice used by the messaging library to publish verified payload 25 | // @param _srcChainId - the source chain identifier 26 | // @param _srcAddress - the source contract (as bytes) at the source chain 27 | // @param _dstAddress - the address on destination chain 28 | // @param _nonce - the unbound message ordering nonce 29 | // @param _gasLimit - the gas limit for external contract execution 30 | // @param _payload - verified payload to send to the destination contract 31 | function receivePayload( 32 | uint16 _srcChainId, 33 | bytes calldata _srcAddress, 34 | address _dstAddress, 35 | uint64 _nonce, 36 | uint _gasLimit, 37 | bytes calldata _payload 38 | ) external; 39 | 40 | // @notice get the inboundNonce of a lzApp from a source chain which could be EVM or non-EVM chain 41 | // @param _srcChainId - the source chain identifier 42 | // @param _srcAddress - the source chain contract address 43 | function getInboundNonce(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (uint64); 44 | 45 | // @notice get the outboundNonce from this source chain which, consequently, is always an EVM 46 | // @param _srcAddress - the source chain contract address 47 | function getOutboundNonce(uint16 _dstChainId, address _srcAddress) external view returns (uint64); 48 | 49 | // @notice gets a quote in source native gas, for the amount that send() requires to pay for message delivery 50 | // @param _dstChainId - the destination chain identifier 51 | // @param _userApplication - the user app address on this EVM chain 52 | // @param _payload - the custom message to send over LayerZero 53 | // @param _payInZRO - if false, user app pays the protocol fee in native token 54 | // @param _adapterParam - parameters for the adapter service, e.g. send some dust native token to dstChain 55 | function estimateFees( 56 | uint16 _dstChainId, 57 | address _userApplication, 58 | bytes calldata _payload, 59 | bool _payInZRO, 60 | bytes calldata _adapterParam 61 | ) external view returns (uint nativeFee, uint zroFee); 62 | 63 | // @notice get this Endpoint's immutable source identifier 64 | function getChainId() external view returns (uint16); 65 | 66 | // @notice the interface to retry failed message on this Endpoint destination 67 | // @param _srcChainId - the source chain identifier 68 | // @param _srcAddress - the source chain contract address 69 | // @param _payload - the payload to be retried 70 | function retryPayload( 71 | uint16 _srcChainId, 72 | bytes calldata _srcAddress, 73 | bytes calldata _payload 74 | ) external; 75 | 76 | // @notice query if any STORED payload (message blocking) at the endpoint. 77 | // @param _srcChainId - the source chain identifier 78 | // @param _srcAddress - the source chain contract address 79 | function hasStoredPayload(uint16 _srcChainId, bytes calldata _srcAddress) external view returns (bool); 80 | 81 | // @notice query if the _libraryAddress is valid for sending msgs. 82 | // @param _userApplication - the user app address on this EVM chain 83 | function getSendLibraryAddress(address _userApplication) external view returns (address); 84 | 85 | // @notice query if the _libraryAddress is valid for receiving msgs. 86 | // @param _userApplication - the user app address on this EVM chain 87 | function getReceiveLibraryAddress(address _userApplication) external view returns (address); 88 | 89 | // @notice query if the non-reentrancy guard for send() is on 90 | // @return true if the guard is on. false otherwise 91 | function isSendingPayload() external view returns (bool); 92 | 93 | // @notice query if the non-reentrancy guard for receive() is on 94 | // @return true if the guard is on. false otherwise 95 | function isReceivingPayload() external view returns (bool); 96 | 97 | // @notice get the configuration of the LayerZero messaging library of the specified version 98 | // @param _version - messaging library version 99 | // @param _chainId - the chainId for the pending config change 100 | // @param _userApplication - the contract address of the user application 101 | // @param _configType - type of configuration. every messaging library has its own convention. 102 | function getConfig( 103 | uint16 _version, 104 | uint16 _chainId, 105 | address _userApplication, 106 | uint _configType 107 | ) external view returns (bytes memory); 108 | 109 | // @notice get the send() LayerZero messaging library version 110 | // @param _userApplication - the contract address of the user application 111 | function getSendVersion(address _userApplication) external view returns (uint16); 112 | 113 | // @notice get the lzReceive() LayerZero messaging library version 114 | // @param _userApplication - the contract address of the user application 115 | function getReceiveVersion(address _userApplication) external view returns (uint16); 116 | } 117 | -------------------------------------------------------------------------------- /contracts/token/onft1155/ONFT1155Core.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./interfaces/IONFT1155Core.sol"; 6 | import "../../lzApp/NonblockingLzApp.sol"; 7 | import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 8 | 9 | abstract contract ONFT1155Core is NonblockingLzApp, ERC165, IONFT1155Core { 10 | uint public constant NO_EXTRA_GAS = 0; 11 | uint16 public constant FUNCTION_TYPE_SEND = 1; 12 | uint16 public constant FUNCTION_TYPE_SEND_BATCH = 2; 13 | bool public useCustomAdapterParams; 14 | 15 | event SetUseCustomAdapterParams(bool _useCustomAdapterParams); 16 | 17 | constructor(address _lzEndpoint) NonblockingLzApp(_lzEndpoint) {} 18 | 19 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 20 | return interfaceId == type(IONFT1155Core).interfaceId || super.supportsInterface(interfaceId); 21 | } 22 | 23 | function estimateSendFee( 24 | uint16 _dstChainId, 25 | bytes memory _toAddress, 26 | uint _tokenId, 27 | uint _amount, 28 | bool _useZro, 29 | bytes memory _adapterParams 30 | ) public view virtual override returns (uint nativeFee, uint zroFee) { 31 | return estimateSendBatchFee(_dstChainId, _toAddress, _toSingletonArray(_tokenId), _toSingletonArray(_amount), _useZro, _adapterParams); 32 | } 33 | 34 | function estimateSendBatchFee( 35 | uint16 _dstChainId, 36 | bytes memory _toAddress, 37 | uint[] memory _tokenIds, 38 | uint[] memory _amounts, 39 | bool _useZro, 40 | bytes memory _adapterParams 41 | ) public view virtual override returns (uint nativeFee, uint zroFee) { 42 | bytes memory payload = abi.encode(_toAddress, _tokenIds, _amounts); 43 | return lzEndpoint.estimateFees(_dstChainId, address(this), payload, _useZro, _adapterParams); 44 | } 45 | 46 | function sendFrom( 47 | address _from, 48 | uint16 _dstChainId, 49 | bytes memory _toAddress, 50 | uint _tokenId, 51 | uint _amount, 52 | address payable _refundAddress, 53 | address _zroPaymentAddress, 54 | bytes memory _adapterParams 55 | ) public payable virtual override { 56 | _sendBatch( 57 | _from, 58 | _dstChainId, 59 | _toAddress, 60 | _toSingletonArray(_tokenId), 61 | _toSingletonArray(_amount), 62 | _refundAddress, 63 | _zroPaymentAddress, 64 | _adapterParams 65 | ); 66 | } 67 | 68 | function sendBatchFrom( 69 | address _from, 70 | uint16 _dstChainId, 71 | bytes memory _toAddress, 72 | uint[] memory _tokenIds, 73 | uint[] memory _amounts, 74 | address payable _refundAddress, 75 | address _zroPaymentAddress, 76 | bytes memory _adapterParams 77 | ) public payable virtual override { 78 | _sendBatch(_from, _dstChainId, _toAddress, _tokenIds, _amounts, _refundAddress, _zroPaymentAddress, _adapterParams); 79 | } 80 | 81 | function _sendBatch( 82 | address _from, 83 | uint16 _dstChainId, 84 | bytes memory _toAddress, 85 | uint[] memory _tokenIds, 86 | uint[] memory _amounts, 87 | address payable _refundAddress, 88 | address _zroPaymentAddress, 89 | bytes memory _adapterParams 90 | ) internal virtual { 91 | _debitFrom(_from, _dstChainId, _toAddress, _tokenIds, _amounts); 92 | bytes memory payload = abi.encode(_toAddress, _tokenIds, _amounts); 93 | if (_tokenIds.length == 1) { 94 | if (useCustomAdapterParams) { 95 | _checkGasLimit(_dstChainId, FUNCTION_TYPE_SEND, _adapterParams, NO_EXTRA_GAS); 96 | } else { 97 | require(_adapterParams.length == 0, "LzApp: _adapterParams must be empty."); 98 | } 99 | _lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams, msg.value); 100 | emit SendToChain(_dstChainId, _from, _toAddress, _tokenIds[0], _amounts[0]); 101 | } else if (_tokenIds.length > 1) { 102 | if (useCustomAdapterParams) { 103 | _checkGasLimit(_dstChainId, FUNCTION_TYPE_SEND_BATCH, _adapterParams, NO_EXTRA_GAS); 104 | } else { 105 | require(_adapterParams.length == 0, "LzApp: _adapterParams must be empty."); 106 | } 107 | _lzSend(_dstChainId, payload, _refundAddress, _zroPaymentAddress, _adapterParams, msg.value); 108 | emit SendBatchToChain(_dstChainId, _from, _toAddress, _tokenIds, _amounts); 109 | } 110 | } 111 | 112 | function _nonblockingLzReceive( 113 | uint16 _srcChainId, 114 | bytes memory _srcAddress, 115 | uint64, /*_nonce*/ 116 | bytes memory _payload 117 | ) internal virtual override { 118 | // decode and load the toAddress 119 | (bytes memory toAddressBytes, uint[] memory tokenIds, uint[] memory amounts) = abi.decode(_payload, (bytes, uint[], uint[])); 120 | address toAddress; 121 | assembly { 122 | toAddress := mload(add(toAddressBytes, 20)) 123 | } 124 | 125 | _creditTo(_srcChainId, toAddress, tokenIds, amounts); 126 | 127 | if (tokenIds.length == 1) { 128 | emit ReceiveFromChain(_srcChainId, _srcAddress, toAddress, tokenIds[0], amounts[0]); 129 | } else if (tokenIds.length > 1) { 130 | emit ReceiveBatchFromChain(_srcChainId, _srcAddress, toAddress, tokenIds, amounts); 131 | } 132 | } 133 | 134 | function setUseCustomAdapterParams(bool _useCustomAdapterParams) external onlyOwner { 135 | useCustomAdapterParams = _useCustomAdapterParams; 136 | emit SetUseCustomAdapterParams(_useCustomAdapterParams); 137 | } 138 | 139 | function _debitFrom( 140 | address _from, 141 | uint16 _dstChainId, 142 | bytes memory _toAddress, 143 | uint[] memory _tokenIds, 144 | uint[] memory _amounts 145 | ) internal virtual; 146 | 147 | function _creditTo( 148 | uint16 _srcChainId, 149 | address _toAddress, 150 | uint[] memory _tokenIds, 151 | uint[] memory _amounts 152 | ) internal virtual; 153 | 154 | function _toSingletonArray(uint element) internal pure returns (uint[] memory) { 155 | uint[] memory array = new uint[](1); 156 | array[0] = element; 157 | return array; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test/oft/v2/ComposableProxyOFTV2.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai") 2 | const { ethers } = require("hardhat") 3 | 4 | describe("Composable ProxyOFT v2: ", function () { 5 | const srcChainId = 1 6 | const dstChainId = 2 7 | 8 | let srcEndpoint, dstEndpoint, proxyOFT, dstOFT, srcStaking, dstStaking, dstPath, srcPath, token 9 | let owner, alice, bob, carol 10 | let dstStakingAddressBytes32, srcStakingAddressBytes32 11 | 12 | before(async function () { 13 | const LZEndpointMock = await ethers.getContractFactory("LZEndpointMock") 14 | const ProxyOFT = await ethers.getContractFactory("ProxyOFTV2") 15 | const ERC20Mock = await ethers.getContractFactory("ERC20Mock") 16 | const OFT = await ethers.getContractFactory("OFTV2") 17 | const OFTStakingMock = await ethers.getContractFactory("OFTStakingMockV2") 18 | 19 | srcEndpoint = await LZEndpointMock.deploy(srcChainId) 20 | dstEndpoint = await LZEndpointMock.deploy(dstChainId) 21 | token = await ERC20Mock.deploy("Mock", "MOCK") 22 | 23 | proxyOFT = await ProxyOFT.deploy(token.address, 6, srcEndpoint.address) 24 | dstOFT = await OFT.deploy("OFT", "OFT", 6, dstEndpoint.address) 25 | 26 | srcStaking = await OFTStakingMock.deploy(proxyOFT.address) 27 | dstStaking = await OFTStakingMock.deploy(dstOFT.address) 28 | 29 | // internal bookkeeping for endpoints (not part of a real deploy, just for this test) 30 | srcEndpoint.setDestLzEndpoint(dstOFT.address, dstEndpoint.address) 31 | dstEndpoint.setDestLzEndpoint(proxyOFT.address, srcEndpoint.address) 32 | 33 | // set each contracts source address so it can send to each other 34 | dstPath = ethers.utils.solidityPack(["address", "address"], [dstOFT.address, proxyOFT.address]) 35 | srcPath = ethers.utils.solidityPack(["address", "address"], [proxyOFT.address, dstOFT.address]) 36 | await proxyOFT.setTrustedRemote(dstChainId, dstPath) // for A, set B 37 | await dstOFT.setTrustedRemote(srcChainId, srcPath) // for B, set A 38 | 39 | // set each contracts source address so it can send to each other 40 | dstStakingAddressBytes32 = ethers.utils.defaultAbiCoder.encode(["address"], [dstStaking.address]) 41 | srcStakingAddressBytes32 = ethers.utils.defaultAbiCoder.encode(["address"], [srcStaking.address]) 42 | await srcStaking.setRemoteStakingContract(dstChainId, dstStakingAddressBytes32) 43 | await dstStaking.setRemoteStakingContract(srcChainId, srcStakingAddressBytes32) 44 | 45 | // set destination min gas 46 | await proxyOFT.setMinDstGas(dstChainId, parseInt(await proxyOFT.PT_SEND()), 225000) 47 | await proxyOFT.setMinDstGas(dstChainId, parseInt(await proxyOFT.PT_SEND_AND_CALL()), 225000) 48 | 49 | owner = (await ethers.getSigners())[0] 50 | alice = (await ethers.getSigners())[1] 51 | bob = (await ethers.getSigners())[2] 52 | carol = (await ethers.getSigners())[3] 53 | 54 | // mint initial tokens 55 | await token.mint(owner.address, ethers.utils.parseEther("1000000")) 56 | }) 57 | 58 | it("deposit on dst chain", async function () { 59 | // owner transfer 100 ether token to alice 60 | const amount = ethers.utils.parseEther("100") 61 | await token.transfer(alice.address, amount) 62 | expect(await token.balanceOf(alice.address)).to.equal(amount) 63 | 64 | // alice deposit 100 ether token to dst chain and transfer to bob 65 | await token.connect(alice).approve(srcStaking.address, amount) 66 | 67 | const adapterParam = ethers.utils.solidityPack(["uint16", "uint256"], [1, 225000 + 300000]) // min gas of OFT + gas for call 68 | // deposit on dst chain 69 | const fee = await srcStaking.quoteForDeposit(dstChainId, bob.address, amount, adapterParam) 70 | 71 | await srcStaking.connect(alice).depositToDstChain(dstChainId, bob.address, amount, adapterParam, { value: fee[0] }) 72 | 73 | // check balance 74 | expect(await token.balanceOf(alice.address)).to.equal(0) 75 | expect(await dstOFT.balanceOf(dstStaking.address)).to.equal(amount) 76 | expect(await dstStaking.balances(bob.address)).to.equal(amount) 77 | 78 | // withdraw 79 | await dstStaking.connect(bob).withdraw(amount) 80 | expect(await dstOFT.balanceOf(dstStaking.address)).to.equal(0) 81 | expect(await dstOFT.balanceOf(bob.address)).to.equal(amount) 82 | }) 83 | 84 | it("failed to call on oft received for paused", async function () { 85 | // owner transfer 50 ether token to alice 86 | const amount = ethers.utils.parseEther("50") 87 | 88 | await token.transfer(alice.address, amount) 89 | expect(await token.balanceOf(alice.address)).to.equal(amount) 90 | 91 | // carol 100 ether token to dst chain and transfer to bob 92 | await token.connect(alice).approve(srcStaking.address, amount) 93 | 94 | await dstStaking.setPaused(true) // paused on dst chain 95 | 96 | const adapterParam = ethers.utils.solidityPack(["uint16", "uint256"], [1, 225000 + 300000]) // min gas of OFT + gas for call 97 | 98 | // deposit on dst chain 99 | const fee = await srcStaking.quoteForDeposit(dstChainId, carol.address, amount, adapterParam) 100 | await srcStaking.connect(alice).depositToDstChain(dstChainId, carol.address, amount, adapterParam, { value: fee[0] }) 101 | 102 | // check balance 103 | expect(await token.balanceOf(alice.address)).to.equal(0) 104 | expect(await dstOFT.balanceOf(dstOFT.address)).to.equal(amount) 105 | expect(await dstStaking.balances(carol.address)).to.equal(0) // failed to call onOFTReceived() for paused 106 | 107 | // should be 0 for failure to call onOFTReceived() 108 | expect(await dstOFT.balanceOf(dstStaking.address)).to.equal(0) 109 | }) 110 | 111 | it("retry to call onOFTReceived() by calling retryMessage()", async function () { 112 | await dstStaking.setPaused(false) // unpaused on dst chain 113 | 114 | const amount = ethers.utils.parseEther("50") 115 | const amountSD = amount.div(Math.pow(10, 12)) 116 | const payloadForCall = ethers.utils.defaultAbiCoder.encode(["uint8", "bytes"], [1, carol.address]) 117 | 118 | // retry to call onOFTReceived() 119 | const payload = ethers.utils.solidityPack( 120 | ["uint8", "bytes32", "uint64", "bytes32", "uint64", "bytes"], 121 | [1, dstStakingAddressBytes32, amountSD, srcStakingAddressBytes32, 300000, payloadForCall] 122 | ) 123 | 124 | // console.log("_from", alice.address) 125 | // console.log("_to", dstOFT.address) 126 | // console.log("_amount", amount) 127 | // console.log("payload", payload) 128 | await dstOFT.retryMessage(srcChainId, srcPath, 2, payload) 129 | expect(await dstStaking.balances(carol.address)).to.equal(amount) 130 | expect(await dstOFT.balanceOf(dstStaking.address)).to.equal(amount) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/NativeOFTV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "./OFTV2.sol"; 7 | 8 | contract NativeOFTV2 is OFTV2, ReentrancyGuard { 9 | uint public outboundAmount; 10 | 11 | event Deposit(address indexed _dst, uint _amount); 12 | event Withdrawal(address indexed _src, uint _amount); 13 | 14 | constructor( 15 | string memory _name, 16 | string memory _symbol, 17 | uint8 _sharedDecimals, 18 | address _lzEndpoint 19 | ) OFTV2(_name, _symbol, _sharedDecimals, _lzEndpoint) {} 20 | 21 | /************************************************************************ 22 | * public functions 23 | ************************************************************************/ 24 | function sendFrom( 25 | address _from, 26 | uint16 _dstChainId, 27 | bytes32 _toAddress, 28 | uint _amount, 29 | LzCallParams calldata _callParams 30 | ) public payable virtual override { 31 | _send(_from, _dstChainId, _toAddress, _amount, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 32 | } 33 | 34 | function sendAndCall( 35 | address _from, 36 | uint16 _dstChainId, 37 | bytes32 _toAddress, 38 | uint _amount, 39 | bytes calldata _payload, 40 | uint64 _dstGasForCall, 41 | LzCallParams calldata _callParams 42 | ) public payable virtual override { 43 | _sendAndCall( 44 | _from, 45 | _dstChainId, 46 | _toAddress, 47 | _amount, 48 | _payload, 49 | _dstGasForCall, 50 | _callParams.refundAddress, 51 | _callParams.zroPaymentAddress, 52 | _callParams.adapterParams 53 | ); 54 | } 55 | 56 | function deposit() public payable { 57 | _mint(msg.sender, msg.value); 58 | emit Deposit(msg.sender, msg.value); 59 | } 60 | 61 | function withdraw(uint _amount) external nonReentrant { 62 | require(balanceOf(msg.sender) >= _amount, "NativeOFTV2: Insufficient balance."); 63 | _burn(msg.sender, _amount); 64 | (bool success, ) = msg.sender.call{value: _amount}(""); 65 | require(success, "NativeOFTV2: failed to unwrap"); 66 | emit Withdrawal(msg.sender, _amount); 67 | } 68 | 69 | function _send( 70 | address _from, 71 | uint16 _dstChainId, 72 | bytes32 _toAddress, 73 | uint _amount, 74 | address payable _refundAddress, 75 | address _zroPaymentAddress, 76 | bytes memory _adapterParams 77 | ) internal virtual override returns (uint amount) { 78 | _checkGasLimit(_dstChainId, PT_SEND, _adapterParams, NO_EXTRA_GAS); 79 | 80 | (amount, ) = _removeDust(_amount); 81 | require(amount > 0, "NativeOFTV2: amount too small"); 82 | uint messageFee = _debitFromNative(_from, amount); 83 | 84 | bytes memory lzPayload = _encodeSendPayload(_toAddress, _ld2sd(amount)); 85 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, messageFee); 86 | 87 | emit SendToChain(_dstChainId, _from, _toAddress, amount); 88 | } 89 | 90 | function _sendAndCall( 91 | address _from, 92 | uint16 _dstChainId, 93 | bytes32 _toAddress, 94 | uint _amount, 95 | bytes memory _payload, 96 | uint64 _dstGasForCall, 97 | address payable _refundAddress, 98 | address _zroPaymentAddress, 99 | bytes memory _adapterParams 100 | ) internal virtual override returns (uint amount) { 101 | _checkGasLimit(_dstChainId, PT_SEND_AND_CALL, _adapterParams, _dstGasForCall); 102 | 103 | (amount, ) = _removeDust(_amount); 104 | require(amount > 0, "NativeOFTV2: amount too small"); 105 | uint messageFee = _debitFromNative(_from, amount); 106 | 107 | // encode the msg.sender into the payload instead of _from 108 | bytes memory lzPayload = _encodeSendAndCallPayload(msg.sender, _toAddress, _ld2sd(amount), _payload, _dstGasForCall); 109 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, messageFee); 110 | 111 | emit SendToChain(_dstChainId, _from, _toAddress, amount); 112 | } 113 | 114 | function _debitFromNative(address _from, uint _amount) internal returns (uint messageFee) { 115 | outboundAmount += _amount; 116 | messageFee = msg.sender == _from ? _debitMsgSender(_amount) : _debitMsgFrom(_from, _amount); 117 | } 118 | 119 | function _debitMsgSender(uint _amount) internal returns (uint messageFee) { 120 | uint msgSenderBalance = balanceOf(msg.sender); 121 | 122 | if (msgSenderBalance < _amount) { 123 | require(msgSenderBalance + msg.value >= _amount, "NativeOFTV2: Insufficient msg.value"); 124 | 125 | // user can cover difference with additional msg.value ie. wrapping 126 | uint mintAmount = _amount - msgSenderBalance; 127 | _mint(address(msg.sender), mintAmount); 128 | 129 | // update the messageFee to take out mintAmount 130 | messageFee = msg.value - mintAmount; 131 | } else { 132 | messageFee = msg.value; 133 | } 134 | 135 | _transfer(msg.sender, address(this), _amount); 136 | return messageFee; 137 | } 138 | 139 | function _debitMsgFrom(address _from, uint _amount) internal returns (uint messageFee) { 140 | uint msgFromBalance = balanceOf(_from); 141 | 142 | if (msgFromBalance < _amount) { 143 | require(msgFromBalance + msg.value >= _amount, "NativeOFTV2: Insufficient msg.value"); 144 | 145 | // user can cover difference with additional msg.value ie. wrapping 146 | uint mintAmount = _amount - msgFromBalance; 147 | _mint(address(msg.sender), mintAmount); 148 | 149 | // transfer the differential amount to the contract 150 | _transfer(msg.sender, address(this), mintAmount); 151 | 152 | // overwrite the _amount to take the rest of the balance from the _from address 153 | _amount = msgFromBalance; 154 | 155 | // update the messageFee to take out mintAmount 156 | messageFee = msg.value - mintAmount; 157 | } else { 158 | messageFee = msg.value; 159 | } 160 | 161 | _spendAllowance(_from, msg.sender, _amount); 162 | _transfer(_from, address(this), _amount); 163 | return messageFee; 164 | } 165 | 166 | function _creditTo( 167 | uint16, 168 | address _toAddress, 169 | uint _amount 170 | ) internal override returns (uint) { 171 | outboundAmount -= _amount; 172 | _burn(address(this), _amount); 173 | (bool success, ) = _toAddress.call{value: _amount}(""); 174 | require(success, "NativeOFTV2: failed to _creditTo"); 175 | return _amount; 176 | } 177 | 178 | receive() external payable { 179 | deposit(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /tasks/checkWireUpAll.js: -------------------------------------------------------------------------------- 1 | const shell = require("shelljs") 2 | const environments = require("../constants/environments.json") 3 | const { getDeploymentAddresses } = require("../utils/readStatic") 4 | 5 | let trustedRemoteTable = {} 6 | let trustedRemoteChecks = {} 7 | const MAX_TRYS = 10 8 | 9 | function TrustedRemoteTestnet() { 10 | this.goerli 11 | this.bscTestnet 12 | this.fuji 13 | this.mumbai 14 | this.arbitrumGoerli 15 | this.optimismGoerli 16 | this.fantomTestnet 17 | } 18 | 19 | function TrustedRemote() { 20 | this.ethereum 21 | this.bsc 22 | this.avalanche 23 | this.polygon 24 | this.arbitrum 25 | this.optimism 26 | this.fantom 27 | } 28 | 29 | function isJsonString(str) { 30 | try { 31 | JSON.parse(str) 32 | } catch (e) { 33 | return false 34 | } 35 | return true 36 | } 37 | 38 | module.exports = async function (taskArgs) { 39 | const networks = environments[taskArgs.e] 40 | if (!taskArgs.e || networks.length === 0) { 41 | console.log(`Invalid environment argument: ${taskArgs.e}`) 42 | } 43 | // loop through all networks and fill up trustedRemoteTable 44 | await Promise.all( 45 | networks.map(async (network) => { 46 | let result 47 | let resultParsed 48 | let trys = 0 49 | while (true) { 50 | let checkWireUpCommand 51 | if (network === taskArgs.proxyChain) { 52 | checkWireUpCommand = `npx hardhat --network ${network} checkWireUp --e ${taskArgs.e} --contract ${taskArgs.proxyContract}` 53 | } else { 54 | checkWireUpCommand = `npx hardhat --network ${network} checkWireUp --e ${taskArgs.e} --contract ${taskArgs.contract}` 55 | } 56 | 57 | console.log("checkWireUp: " + checkWireUpCommand) 58 | // remove spaces and new lines from stdout 59 | result = shell.exec(checkWireUpCommand).stdout.replace(/(\r\n|\n|\r|\s)/gm, "") 60 | // remove extra words before JSON object, so it can be parsed correctly 61 | result = result.substring(result.indexOf("{")) 62 | // make sure it is JSON otherwise the network does not have this contract deployed 63 | if (!isJsonString(result)) { 64 | trustedRemoteTable[network] = new TrustedRemote() 65 | break 66 | } 67 | // parse result into JSON object 68 | resultParsed = JSON.parse(result) 69 | // make sure all chain ids are set if so we break 70 | if (Object.keys(resultParsed).length === networks.length) { 71 | break 72 | } 73 | // we will retry a max of 10 times otherwise we throw an error to stop infinite while loop 74 | else if (trys === MAX_TRYS) { 75 | throw new Error(`Retired the max amount of times for ${network}`) 76 | } 77 | // sometimes the returned JSON is missing chains so retry until they are all set properly 78 | else { 79 | ++trys 80 | console.log(`On retry:${trys} for ${network}`) 81 | } 82 | } 83 | trustedRemoteTable[network] = taskArgs.e === "mainnet" ? new TrustedRemote() : new TrustedRemoteTestnet() 84 | // assign new passed object to the trustedRemoteTable[network] 85 | Object.assign(trustedRemoteTable[network], resultParsed) 86 | // if trustedRemoteTable[network] is not empty then set trustedRemoteChecks[network] 87 | if (Object.keys(trustedRemoteTable[network]).length > 0) { 88 | trustedRemoteChecks[network] = taskArgs.e === "mainnet" ? new TrustedRemote() : new TrustedRemoteTestnet() 89 | } 90 | }) 91 | ) 92 | 93 | // use filled trustedRemoteTable to make trustedRemoteChecks 94 | const environmentArray = environments[taskArgs.e] 95 | for (let i = 0; i < environmentArray.length; i++) { 96 | if (trustedRemoteTable[environmentArray[i]] === undefined) continue 97 | const envToCamelCase = environmentArray[i].replace(/-./g, (m) => m[1].toUpperCase()) 98 | let actualRemoteAddress = getDeployedAddress(environmentArray[i], taskArgs.proxyChain, taskArgs.contract, taskArgs.proxyContract) 99 | if (actualRemoteAddress === undefined) continue 100 | for (let j = 0; j < environmentArray.length; j++) { 101 | if (trustedRemoteTable[environmentArray[j]] === undefined) continue 102 | let actualLocalAddress = getDeployedAddress(environmentArray[j], taskArgs.proxyChain, taskArgs.contract, taskArgs.proxyContract) 103 | if (actualLocalAddress !== undefined) { 104 | const currentlySetTrustedRemote = trustedRemoteTable[environmentArray[j]][envToCamelCase] 105 | let actualSetTrustedRemote = actualRemoteAddress + actualLocalAddress.substring(2) 106 | console.log( 107 | `${environmentArray[j]}'s currentSetRemoteAddress for ${environmentArray[i]}: ${currentlySetTrustedRemote} ${ 108 | JSON.stringify(actualSetTrustedRemote) === JSON.stringify(currentlySetTrustedRemote) ? "✅ " : "❌ " 109 | }` 110 | ) 111 | if (JSON.stringify(actualSetTrustedRemote) === JSON.stringify(currentlySetTrustedRemote)) { 112 | if (environmentArray[i] === environmentArray[j]) { 113 | trustedRemoteChecks[environmentArray[j]][environmentArray[i]] = "" 114 | } else { 115 | trustedRemoteChecks[environmentArray[j]][environmentArray[i]] = "🟩" 116 | } 117 | } else if (JSON.stringify(actualSetTrustedRemote) !== JSON.stringify(currentlySetTrustedRemote)) { 118 | trustedRemoteChecks[environmentArray[j]][environmentArray[i]] = "🟥" 119 | } 120 | } 121 | } 122 | } 123 | console.log("Legend") 124 | console.log("Set: 🟩") 125 | console.log("Not Set: 🟥") 126 | console.table(trustedRemoteChecks) 127 | 128 | //print addresses 129 | let getAddressesCommand 130 | if (taskArgs.proxyChain !== undefined) { 131 | getAddressesCommand = `node utils/getAddresses ${taskArgs.e} ${taskArgs.proxyContract},${taskArgs.contract}` 132 | } else { 133 | getAddressesCommand = `node utils/getAddresses ${taskArgs.e} ${taskArgs.contract}` 134 | } 135 | console.log("getAddressesCommand: " + getAddressesCommand) 136 | shell.exec(getAddressesCommand) 137 | } 138 | 139 | function getDeployedAddress(chain, proxyChain, contract, proxyContract) { 140 | let deployedAddress 141 | try { 142 | if (chain === proxyChain) { 143 | deployedAddress = getDeploymentAddresses(chain)[proxyContract].toLowerCase() 144 | } else { 145 | deployedAddress = getDeploymentAddresses(chain)[contract].toLowerCase() 146 | } 147 | } catch { 148 | deployedAddress = undefined 149 | } 150 | return deployedAddress 151 | } 152 | -------------------------------------------------------------------------------- /contracts/token/oft/v2/fee/NativeOFTWithFee.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import "./OFTWithFee.sol"; 7 | 8 | contract NativeOFTWithFee is OFTWithFee, ReentrancyGuard { 9 | uint public outboundAmount; 10 | 11 | event Deposit(address indexed _dst, uint _amount); 12 | event Withdrawal(address indexed _src, uint _amount); 13 | 14 | constructor(string memory _name, string memory _symbol, uint8 _sharedDecimals, address _lzEndpoint) OFTWithFee(_name, _symbol, _sharedDecimals, _lzEndpoint) {} 15 | 16 | function deposit() public payable { 17 | _mint(msg.sender, msg.value); 18 | emit Deposit(msg.sender, msg.value); 19 | } 20 | 21 | function withdraw(uint _amount) external nonReentrant { 22 | require(balanceOf(msg.sender) >= _amount, "NativeOFTWithFee: Insufficient balance."); 23 | _burn(msg.sender, _amount); 24 | (bool success, ) = msg.sender.call{value: _amount}(""); 25 | require(success, "NativeOFTWithFee: failed to unwrap"); 26 | emit Withdrawal(msg.sender, _amount); 27 | } 28 | 29 | /************************************************************************ 30 | * public functions 31 | ************************************************************************/ 32 | function sendFrom(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, LzCallParams calldata _callParams) public payable virtual override { 33 | _amount = _send(_from, _dstChainId, _toAddress, _amount, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 34 | require(_amount >= _minAmount, "NativeOFTWithFee: amount is less than minAmount"); 35 | } 36 | 37 | function sendAndCall(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, uint _minAmount, bytes calldata _payload, uint64 _dstGasForCall, LzCallParams calldata _callParams) public payable virtual override { 38 | _amount = _sendAndCall(_from, _dstChainId, _toAddress, _amount, _payload, _dstGasForCall, _callParams.refundAddress, _callParams.zroPaymentAddress, _callParams.adapterParams); 39 | require(_amount >= _minAmount, "NativeOFTWithFee: amount is less than minAmount"); 40 | } 41 | 42 | function _send(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) internal virtual override returns (uint amount) { 43 | _checkGasLimit(_dstChainId, PT_SEND, _adapterParams, NO_EXTRA_GAS); 44 | 45 | uint messageFee; 46 | (messageFee, amount) = _debitFromNative(_from, _amount, _dstChainId); 47 | 48 | bytes memory lzPayload = _encodeSendPayload(_toAddress, _ld2sd(amount)); 49 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, messageFee); 50 | 51 | emit SendToChain(_dstChainId, _from, _toAddress, amount); 52 | } 53 | 54 | function _sendAndCall(address _from, uint16 _dstChainId, bytes32 _toAddress, uint _amount, bytes memory _payload, uint64 _dstGasForCall, address payable _refundAddress, address _zroPaymentAddress, bytes memory _adapterParams) internal virtual override returns (uint amount) { 55 | _checkGasLimit(_dstChainId, PT_SEND_AND_CALL, _adapterParams, _dstGasForCall); 56 | 57 | uint messageFee; 58 | (messageFee, amount) = _debitFromNative(_from, _amount, _dstChainId); 59 | 60 | // encode the msg.sender into the payload instead of _from 61 | bytes memory lzPayload = _encodeSendAndCallPayload(msg.sender, _toAddress, _ld2sd(amount), _payload, _dstGasForCall); 62 | _lzSend(_dstChainId, lzPayload, _refundAddress, _zroPaymentAddress, _adapterParams, messageFee); 63 | 64 | emit SendToChain(_dstChainId, _from, _toAddress, amount); 65 | } 66 | 67 | function _debitFromNative(address _from, uint _amount, uint16 _dstChainId) internal returns (uint messageFee, uint amount) { 68 | uint fee = quoteOFTFee(_dstChainId, _amount); 69 | uint newMsgValue = msg.value; 70 | 71 | if(fee > 0) { 72 | // subtract fee from _amount 73 | _amount -= fee; 74 | 75 | // pay fee and update newMsgValue 76 | if(balanceOf(_from) >= fee) { 77 | _transferFrom(_from, feeOwner, fee); 78 | } else { 79 | _mint(feeOwner, fee); 80 | newMsgValue -= fee; 81 | } 82 | } 83 | 84 | (amount,) = _removeDust(_amount); 85 | require(amount > 0, "NativeOFTWithFee: amount too small"); 86 | outboundAmount += amount; 87 | messageFee = msg.sender == _from ? _debitMsgSender(amount, newMsgValue) : _debitMsgFrom(_from, amount, newMsgValue); 88 | } 89 | 90 | function _debitMsgSender(uint _amount, uint currentMsgValue) internal returns (uint messageFee) { 91 | uint msgSenderBalance = balanceOf(msg.sender); 92 | 93 | if (msgSenderBalance < _amount) { 94 | require(msgSenderBalance + currentMsgValue >= _amount, "NativeOFTWithFee: Insufficient msg.value"); 95 | 96 | // user can cover difference with additional msg.value ie. wrapping 97 | uint mintAmount = _amount - msgSenderBalance; 98 | 99 | _mint(address(msg.sender), mintAmount); 100 | 101 | // update the messageFee to take out mintAmount 102 | messageFee = currentMsgValue - mintAmount; 103 | } else { 104 | messageFee = currentMsgValue; 105 | } 106 | 107 | _transfer(msg.sender, address(this), _amount); 108 | return messageFee; 109 | } 110 | 111 | function _debitMsgFrom(address _from, uint _amount, uint currentMsgValue) internal returns (uint messageFee) { 112 | uint msgFromBalance = balanceOf(_from); 113 | 114 | if (msgFromBalance < _amount) { 115 | require(msgFromBalance + currentMsgValue >= _amount, "NativeOFTWithFee: Insufficient msg.value"); 116 | 117 | // user can cover difference with additional msg.value ie. wrapping 118 | uint mintAmount = _amount - msgFromBalance; 119 | _mint(address(msg.sender), mintAmount); 120 | 121 | // transfer the differential amount to the contract 122 | _transfer(msg.sender, address(this), mintAmount); 123 | 124 | // overwrite the _amount to take the rest of the balance from the _from address 125 | _amount = msgFromBalance; 126 | 127 | // update the messageFee to take out mintAmount 128 | messageFee = currentMsgValue - mintAmount; 129 | } else { 130 | messageFee = currentMsgValue; 131 | } 132 | 133 | _spendAllowance(_from, msg.sender, _amount); 134 | _transfer(_from, address(this), _amount); 135 | return messageFee; 136 | } 137 | 138 | function _creditTo(uint16, address _toAddress, uint _amount) internal override returns(uint) { 139 | outboundAmount -= _amount; 140 | _burn(address(this), _amount); 141 | (bool success, ) = _toAddress.call{value: _amount}(""); 142 | require(success, "NativeOFTWithFee: failed to _creditTo"); 143 | return _amount; 144 | } 145 | 146 | receive() external payable { 147 | deposit(); 148 | } 149 | } --------------------------------------------------------------------------------