diff --git a/tools/layer-zero-example/README.md b/tools/layer-zero-example/README.md index 605c78971c..d91f1a1ec3 100644 --- a/tools/layer-zero-example/README.md +++ b/tools/layer-zero-example/README.md @@ -39,8 +39,8 @@ npx hardhat test --grep "OFTTests @bsc @test" --network bsc_testnet ### OFT Adapter -npx hardhat deploy-erc20 --network hedera_testnet -npx hardhat deploy-erc20 --network bsc_testnet +npx hardhat deploy-erc20 --decimals 18 --mint 10000000000000000000 --network hedera_testnet +npx hardhat deploy-erc20 --decimals 18 --mint 10000000000000000000 --network bsc_testnet npx hardhat deploy-oft-adapter --token --network hedera_testnet npx hardhat deploy-oft-adapter --token --network bsc_testnet @@ -125,3 +125,27 @@ wait a couple minutes, the LZ progress can be tracked on https://testnet.layerze npx hardhat test --grep "HTSConnectorTests @hedera @test" --network hedera_testnet npx hardhat test --grep "HTSConnectorTests @bsc @test" --network bsc_testnet + +### HTS Adapter + +npx hardhat create-hts-token --network hedera_testnet +npx hardhat deploy-erc20 --decimals 8 --mint 1000 --network bsc_testnet + +npx hardhat deploy-oft-adapter --token --network hedera_testnet +npx hardhat deploy-oft-adapter --token --network bsc_testnet + +npx hardhat set-peer --source --target --network hedera_testnet +npx hardhat set-peer --source --target --network bsc_testnet + +fill the .env + +npx hardhat test --grep "HTSAdapterTests @hedera @fund-and-approve" --network hedera_testnet +npx hardhat test --grep "HTSAdapterTests @bsc @fund-and-approve" --network bsc_testnet + +npx hardhat test --grep "HTSAdapterTests @hedera @send" --network hedera_testnet +npx hardhat test --grep "HTSAdapterTests @bsc @send" --network bsc_testnet + +wait a couple minutes, the LZ progress can be tracked on https://testnet.layerzeroscan.com/tx/ + +npx hardhat test --grep "HTSAdapterTests @hedera @test" --network hedera_testnet +npx hardhat test --grep "HTSAdapterTests @bsc @test" --network bsc_testnet diff --git a/tools/layer-zero-example/contracts/CreateHTS.sol b/tools/layer-zero-example/contracts/CreateHTS.sol new file mode 100644 index 0000000000..81f37d8950 --- /dev/null +++ b/tools/layer-zero-example/contracts/CreateHTS.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./hts/HederaTokenService.sol"; +import "./hts/IHederaTokenService.sol"; +import "./hts/KeyHelper.sol"; +import "./hts/ExpiryHelper.sol"; + +contract CreateHTS is Ownable, KeyHelper, ExpiryHelper, HederaTokenService { + address public htsTokenAddress; + + constructor(string memory _name, string memory _symbol, address _delegate) payable Ownable(_delegate) { + IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](2); + keys[0] = getSingleKey( + KeyType.ADMIN, + KeyType.PAUSE, + KeyValueType.INHERIT_ACCOUNT_KEY, + bytes("") + ); + keys[1] = getSingleKey( + KeyType.SUPPLY, + KeyValueType.INHERIT_ACCOUNT_KEY, + bytes("") + ); + + IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry(0, address(this), 8000000); + IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken( + _name, _symbol, address(this), "memo", true, 5000, false, keys, expiry + ); + + (int responseCode, address tokenAddress) = HederaTokenService.createFungibleToken( + token, 1000, int32(int256(uint256(8))) + ); + require(responseCode == HederaResponseCodes.SUCCESS, "Failed to create HTS token"); + + int256 transferResponse = HederaTokenService.transferToken(tokenAddress, address(this), msg.sender, 1000); + require(transferResponse == HederaResponseCodes.SUCCESS, "HTS: Transfer failed"); + + htsTokenAddress = tokenAddress; + } +} diff --git a/tools/layer-zero-example/contracts/ERC20Mock.sol b/tools/layer-zero-example/contracts/ERC20Mock.sol index 823e880011..34f21d4f36 100644 --- a/tools/layer-zero-example/contracts/ERC20Mock.sol +++ b/tools/layer-zero-example/contracts/ERC20Mock.sol @@ -4,7 +4,14 @@ pragma solidity ^0.8.22; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ERC20Mock is ERC20 { - constructor() ERC20("ERC20Mock", "E20M") { - _mint(msg.sender, 10000000000000000000); + uint8 decimalsArg = 18; + + constructor(uint256 _initialMint, uint8 _decimals) ERC20("ERC20Mock", "E20M") { + _mint(msg.sender, _initialMint); + decimalsArg = _decimals; + } + + function decimals() public view override returns (uint8) { + return decimalsArg; } } diff --git a/tools/layer-zero-example/hardhat.config.js b/tools/layer-zero-example/hardhat.config.js index c3d0eeab64..968ab832c0 100644 --- a/tools/layer-zero-example/hardhat.config.js +++ b/tools/layer-zero-example/hardhat.config.js @@ -64,9 +64,11 @@ const getEndpointAddress = (network) => { } task('deploy-erc20', "Deploy ERC20 token") + .addParam('mint', 'Initial mint') + .addParam('decimals', 'Decimals') .setAction(async (taskArgs, hre) => { const contractFactory = await ethers.getContractFactory('ERC20Mock'); - const contract = await contractFactory.deploy(); + const contract = await contractFactory.deploy(taskArgs.mint, taskArgs.decimals); await contract.deployTransaction.wait(); console.log(`(${hre.network.name}) ERC20 deployed to: ` + contract.address); @@ -100,7 +102,7 @@ task("deploy-oft", "Deploy OFT contract") .setAction(async (taskArgs, hre) => { const ethers = hre.ethers; const signers = await ethers.getSigners(); - const ENDPOINT_V2 = getEndpointAddress(hre.network.name) + const ENDPOINT_V2 = getEndpointAddress(hre.network.name); const contractFactory = await ethers.getContractFactory('ExampleOFT'); const contract = await contractFactory.deploy('T_NAME', 'T_SYMBOL', ENDPOINT_V2, signers[0].address, taskArgs.mint, taskArgs.decimals); @@ -113,7 +115,7 @@ task("deploy-hts-connector", "Deploy HTS connector contract") .setAction(async (taskArgs, hre) => { const ethers = hre.ethers; const signers = await ethers.getSigners(); - const ENDPOINT_V2 = getEndpointAddress(hre.network.name) + const ENDPOINT_V2 = getEndpointAddress(hre.network.name); const contractFactory = await ethers.getContractFactory('ExampleHTSConnector'); const contract = await contractFactory.deploy('T_NAME', 'T_SYMBOL', ENDPOINT_V2, signers[0].address, { @@ -125,12 +127,27 @@ task("deploy-hts-connector", "Deploy HTS connector contract") console.log(`(${hre.network.name}) ExampleHTSConnector deployed to: ` + contract.address); }); +task("create-hts-token", "Create a HTS token") + .setAction(async (taskArgs, hre) => { + const ethers = hre.ethers; + const signers = await ethers.getSigners(); + + const contractFactory = await ethers.getContractFactory('CreateHTS'); + const contract = await contractFactory.deploy('T_NAME', 'T_SYMBOL', signers[0].address, { + gasLimit: 10_000_000, + value: '30000000000000000000' // 30 hbars + }); + await contract.deployTransaction.wait(); + + console.log(`(${hre.network.name}) Token address: ` + await contract.htsTokenAddress()); + }); + task("deploy-oft-adapter", "Deploy OFT adapter contract") .addParam('token', 'Token address') .setAction(async (taskArgs, hre) => { const ethers = hre.ethers; const signers = await ethers.getSigners(); - const ENDPOINT_V2 = getEndpointAddress(hre.network.name) + const ENDPOINT_V2 = getEndpointAddress(hre.network.name); const contractFactory = await ethers.getContractFactory('ExampleOFTAdapter'); const contract = await contractFactory.deploy(taskArgs.token, ENDPOINT_V2, signers[0].address); @@ -164,7 +181,7 @@ task("deploy-onft-adapter", "Deploy OFT contract") .setAction(async (taskArgs, hre) => { const ethers = hre.ethers; const signers = await ethers.getSigners(); - const ENDPOINT_V2 = getEndpointAddress(hre.network.name) + const ENDPOINT_V2 = getEndpointAddress(hre.network.name); const contractFactory = await ethers.getContractFactory('ExampleONFTAdapter'); const contract = await contractFactory.deploy(taskArgs.token, ENDPOINT_V2, signers[0].address); diff --git a/tools/layer-zero-example/test/htsAdapterTests.js b/tools/layer-zero-example/test/htsAdapterTests.js new file mode 100644 index 0000000000..f2f9d8ac9d --- /dev/null +++ b/tools/layer-zero-example/test/htsAdapterTests.js @@ -0,0 +1,139 @@ +/*- + * + * Hedera JSON RPC Relay - Hardhat Example + * + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +const hre = require('hardhat'); +const { ethers } = hre; +const { Options, addressToBytes32 } = require('@layerzerolabs/lz-v2-utilities'); +const { expect } = require('chai'); + +const HEDERA_EID = 40285; +const BSC_EID = 40102; +const receiverAddress = '0xF51c7a9407217911d74e91642dbC58F18E51Deac'; +const amount = '100'; + +describe('HTSAdapterTests', function() { + it('@hedera @fund-and-approve transfer to adapter', async () => { + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_HTS_HEDERA_CONTRACT); + const transferTx = await contractERC20.transfer(process.env.HTS_ADAPTER_HEDERA_CONTRACT, amount); + const receipt = await transferTx.wait(); + expect(!!receipt.status).to.be.true; + }); + + it('@bsc @fund-and-approve transfer to adapter', async () => { + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_ERC20_BSC_CONTRACT); + const transferTx = await contractERC20.transfer(process.env.HTS_ADAPTER_BSC_CONTRACT, amount); + const receipt = await transferTx.wait(); + expect(!!receipt.status).to.be.true; + }); + + it('@hedera @fund-and-approve adapter approval', async () => { + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_HTS_HEDERA_CONTRACT); + const approveTx = await contractERC20.approve(process.env.HTS_ADAPTER_HEDERA_CONTRACT, amount); + const receipt = await approveTx.wait(); + expect(!!receipt.status).to.be.true; + }); + + it('@bsc @fund-and-approve adapter approval', async () => { + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_ERC20_BSC_CONTRACT); + const approveTx = await contractERC20.approve(process.env.HTS_ADAPTER_BSC_CONTRACT, amount); + const receipt = await approveTx.wait(); + expect(!!receipt.status).to.be.true; + }); + + it('@hedera @send to bsc', async () => { + const signers = await ethers.getSigners(); + + const sendParam = { + dstEid: BSC_EID, + to: addressToBytes32(receiverAddress), + amountLD: amount, + minAmountLD: amount, + extraOptions: Options.newOptions().addExecutorLzReceiveOption(3000000, 0).toBytes(), + composeMsg: ethers.utils.arrayify('0x'), + oftCmd: ethers.utils.arrayify('0x') + }; + + const contract = await ethers.getContractAt('ExampleOFTAdapter', process.env.HTS_ADAPTER_HEDERA_CONTRACT); + const tx = await contract.send(sendParam, { nativeFee: '500000000', lzTokenFee: 0 }, signers[0].address, { + gasLimit: 10_000_000, + value: '5000000000000000000' + }); + + const receipt = await tx.wait(); + if (!receipt.status) { + process.exit(`Execution failed. Tx hash: ${tx.hash}`); + } + + console.log(`(${hre.network.name}) successfully sent to Bsc via tx: ${tx.hash}`); + }); + + it('@bsc @send to hedera', async () => { + const signers = await ethers.getSigners(); + + const sendParam = { + dstEid: HEDERA_EID, + to: addressToBytes32(receiverAddress), + amountLD: amount, + minAmountLD: amount, + extraOptions: Options.newOptions().addExecutorLzReceiveOption(3000000, 0).toBytes(), + composeMsg: ethers.utils.arrayify('0x'), + oftCmd: ethers.utils.arrayify('0x') + }; + + const contract = await ethers.getContractAt('ExampleOFTAdapter', process.env.HTS_ADAPTER_BSC_CONTRACT); + const tx = await contract.send(sendParam, { nativeFee: '1000000000000000', lzTokenFee: 0 }, signers[0].address, { + gasLimit: 1_000_000, + value: '1000000000000000' + }); + + const receipt = await tx.wait(); + if (!receipt.status) { + process.exit(`Execution failed. Tx hash: ${tx.hash}`); + } + + console.log(`(${hre.network.name}) successfully sent to Hedera via tx: ${tx.hash}`); + }); + + it('@hedera @test balance', async () => { + const signers = await ethers.getSigners(); + + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_HTS_HEDERA_CONTRACT); + const receiverBalance = await contractERC20.balanceOf(receiverAddress); + + console.log(`(${hre.network.name}) signer balance: ${await contractERC20.balanceOf(signers[0].address)}`); + console.log(`(${hre.network.name}) adapter balance: ${await contractERC20.balanceOf(process.env.HTS_ADAPTER_HEDERA_CONTRACT)}`); + console.log(`(${hre.network.name}) receiver balance: ${receiverBalance}`); + + expect(receiverBalance).to.equal(amount); + }); + + it('@bsc @test balance', async () => { + const signers = await ethers.getSigners(); + + const contractERC20 = await ethers.getContractAt('ERC20Mock', process.env.HTS_ADAPTER_ERC20_BSC_CONTRACT); + const receiverBalance = await contractERC20.balanceOf(receiverAddress); + + console.log(`(${hre.network.name}) signer balance: ${await contractERC20.balanceOf(signers[0].address)}`); + console.log(`(${hre.network.name}) adapter balance: ${await contractERC20.balanceOf(process.env.HTS_ADAPTER_BSC_CONTRACT)}`); + console.log(`(${hre.network.name}) receiver balance: ${receiverBalance}`); + + expect(receiverBalance).to.equal(amount); + }); +});