diff --git a/examples/onft721/.env.example b/examples/onft721/.env.example new file mode 100644 index 000000000..197ba1d67 --- /dev/null +++ b/examples/onft721/.env.example @@ -0,0 +1,15 @@ +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' +# +# Example environment configuration +# +# .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.-. .-.- +# / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ \ / / \ +# `-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' `-`-' + +# By default, the examples support both mnemonic-based and private key-based authentication +# +# You don't need to set both of these values, just pick the one that you prefer and set that one +MNEMONIC= +PRIVATE_KEY= \ No newline at end of file diff --git a/examples/onft721/.eslintignore b/examples/onft721/.eslintignore new file mode 100644 index 000000000..ee9f768fd --- /dev/null +++ b/examples/onft721/.eslintignore @@ -0,0 +1,10 @@ +artifacts +cache +dist +node_modules +out +*.log +*.sol +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/onft721/.eslintrc.js b/examples/onft721/.eslintrc.js new file mode 100644 index 000000000..f0ea891fd --- /dev/null +++ b/examples/onft721/.eslintrc.js @@ -0,0 +1,10 @@ +require('@rushstack/eslint-patch/modern-module-resolution'); + +module.exports = { + extends: ['@layerzerolabs/eslint-config-next/recommended'], + rules: { + // @layerzerolabs/eslint-config-next defines rules for turborepo-based projects + // that are not relevant for this particular project + 'turbo/no-undeclared-env-vars': 'off', + }, +}; diff --git a/examples/onft721/.gitignore b/examples/onft721/.gitignore new file mode 100644 index 000000000..e2face954 --- /dev/null +++ b/examples/onft721/.gitignore @@ -0,0 +1,24 @@ +node_modules +.env +coverage +coverage.json +typechain +typechain-types + +# Hardhat files +cache +artifacts + + +# LayerZero specific files +.layerzero + +# foundry test compilation files +out + +# pnpm +pnpm-error.log + +# Editor and OS files +.DS_Store +.idea diff --git a/examples/onft721/.nvmrc b/examples/onft721/.nvmrc new file mode 100644 index 000000000..b714151ef --- /dev/null +++ b/examples/onft721/.nvmrc @@ -0,0 +1 @@ +v18.18.0 \ No newline at end of file diff --git a/examples/onft721/.prettierignore b/examples/onft721/.prettierignore new file mode 100644 index 000000000..6e8232f5a --- /dev/null +++ b/examples/onft721/.prettierignore @@ -0,0 +1,10 @@ +artifacts/ +cache/ +dist/ +node_modules/ +out/ +*.log +*ignore +*.yaml +*.lock +package-lock.json \ No newline at end of file diff --git a/examples/onft721/.prettierrc.js b/examples/onft721/.prettierrc.js new file mode 100644 index 000000000..6f55b4019 --- /dev/null +++ b/examples/onft721/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('@layerzerolabs/prettier-config-next'), +}; diff --git a/examples/onft721/README.md b/examples/onft721/README.md new file mode 100644 index 000000000..e0c05c7a2 --- /dev/null +++ b/examples/onft721/README.md @@ -0,0 +1,112 @@ +

+ + LayerZero + +

+ +

+ Homepage | Docs | Developers +

+ +

ONFT721 Example

+ +

+ Quickstart | Configuration | Message Execution Options | Endpoint Addresses +

+ +

Template project for getting started with LayerZero's ONFT721 contract development.

+ +:warning: ** This code is currently under audit and should not yet be used in production. ** + +## 1) Developing Contracts + +#### Installing dependencies + +We recommend using `pnpm` as a package manager (but you can of course use a package manager of your choice): + +```bash +pnpm install +``` + +#### Compiling your contracts + +This project supports both `hardhat` and `forge` compilation. By default, the `compile` command will execute both: + +```bash +pnpm compile +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm compile:forge +pnpm compile:hardhat +``` + +Or adjust the `package.json` to for example remove `forge` build: + +```diff +- "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", +- "compile:forge": "forge build", +- "compile:hardhat": "hardhat compile", ++ "compile": "hardhat compile" +``` + +#### Running tests + +Similarly to the contract compilation, we support both `hardhat` and `forge` tests. By default, the `test` command will execute both: + +```bash +pnpm test +``` + +If you prefer one over the other, you can use the tooling-specific commands: + +```bash +pnpm test:forge +pnpm test:hardhat +``` + +Or adjust the `package.json` to for example remove `hardhat` tests: + +```diff +- "test": "$npm_execpath test:forge && $npm_execpath test:hardhat", +- "test:forge": "forge test", +- "test:hardhat": "$npm_execpath hardhat test" ++ "test": "forge test" +``` + +## 2) Deploying Contracts + +Set up deployer wallet/account: + +- Rename `.env.example` -> `.env` +- Choose your preferred means of setting up your deployer wallet/account: + +``` +MNEMONIC="test test test test test test test test test test test junk" +or... +PRIVATE_KEY="0xabc...def" +``` + +- Fund this address with the corresponding chain's native tokens you want to deploy to. + +To deploy your contracts to your desired blockchains, run the following command in your project's folder: + +```bash +npx hardhat lz:deploy +``` + +More information about available CLI arguments can be found using the `--help` flag: + +```bash +npx hardhat lz:deploy --help +``` + +By following these steps, you can focus more on creating innovative omnichain solutions and less on the complexities of cross-chain communication. + +

+ +

+ Join our community on Discord | Follow us on Twitter +

diff --git a/examples/onft721/contracts/MyONFT721.sol b/examples/onft721/contracts/MyONFT721.sol new file mode 100644 index 000000000..64350c3c6 --- /dev/null +++ b/examples/onft721/contracts/MyONFT721.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { ONFT721 } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721.sol"; + +contract MyONFT721 is ONFT721 { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {} +} diff --git a/examples/onft721/contracts/mocks/MyONFT721Mock.sol b/examples/onft721/contracts/mocks/MyONFT721Mock.sol new file mode 100644 index 000000000..5e7f83f39 --- /dev/null +++ b/examples/onft721/contracts/mocks/MyONFT721Mock.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { MyONFT721 } from "../MyONFT721.sol"; + +// @dev WARNING: This is for testing purposes only +contract MyONFT721Mock is MyONFT721 { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) MyONFT721(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/onft721/deploy/MyONFT721.ts b/examples/onft721/deploy/MyONFT721.ts new file mode 100644 index 000000000..8e80c4942 --- /dev/null +++ b/examples/onft721/deploy/MyONFT721.ts @@ -0,0 +1,53 @@ +import assert from 'assert' + +import { type DeployFunction } from 'hardhat-deploy/types' + +const contractName = 'MyONFT721' + +const deploy: DeployFunction = async (hre) => { + const { getNamedAccounts, deployments } = hre + + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + assert(deployer, 'Missing named deployer account') + + console.log(`Network: ${hre.network.name}`) + console.log(`Deployer: ${deployer}`) + + // This is an external deployment pulled in from @layerzerolabs/lz-evm-sdk-v2 + // + // @layerzerolabs/toolbox-hardhat takes care of plugging in the external deployments + // from @layerzerolabs packages based on the configuration in your hardhat config + // + // For this to work correctly, your network config must define an eid property + // set to `EndpointId` as defined in @layerzerolabs/lz-definitions + // + // For example: + // + // networks: { + // fuji: { + // ... + // eid: EndpointId.AVALANCHE_V2_TESTNET + // } + // } + const endpointV2Deployment = await hre.deployments.get('EndpointV2') + + const { address } = await deploy(contractName, { + from: deployer, + args: [ + 'MyONFT721', // name + 'ONFT', // symbol + endpointV2Deployment.address, // LayerZero's EndpointV2 address + deployer, // owner + ], + log: true, + skipIfAlreadyDeployed: false, + }) + + console.log(`Deployed contract: ${contractName}, network: ${hre.network.name}, address: ${address}`) +} + +deploy.tags = [contractName] + +export default deploy diff --git a/examples/onft721/foundry.toml b/examples/onft721/foundry.toml new file mode 100644 index 000000000..7608c64b3 --- /dev/null +++ b/examples/onft721/foundry.toml @@ -0,0 +1,27 @@ +[profile.default] +solc-version = '0.8.22' +src = 'contracts' +out = 'out' +test = 'test/foundry' +cache_path = 'cache' +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] + +remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std', + '@layerzerolabs/=node_modules/@layerzerolabs/', + '@openzeppelin/=node_modules/@openzeppelin/', +] diff --git a/examples/onft721/hardhat.config.ts b/examples/onft721/hardhat.config.ts new file mode 100644 index 000000000..be20edcdf --- /dev/null +++ b/examples/onft721/hardhat.config.ts @@ -0,0 +1,75 @@ +// Get the environment configuration from .env file +// +// To make use of automatic environment setup: +// - Duplicate .env.example file and name it .env +// - Fill in the environment variables +import 'dotenv/config' + +import 'hardhat-deploy' +import 'hardhat-contract-sizer' +import '@nomiclabs/hardhat-ethers' +import '@layerzerolabs/toolbox-hardhat' +import { HardhatUserConfig, HttpNetworkAccountsUserConfig } from 'hardhat/types' + +import { EndpointId } from '@layerzerolabs/lz-definitions' + +// Set your preferred authentication method +// +// If you prefer using a mnemonic, set a MNEMONIC environment variable +// to a valid mnemonic +const MNEMONIC = process.env.MNEMONIC + +// If you prefer to be authenticated using a private key, set a PRIVATE_KEY environment variable +const PRIVATE_KEY = process.env.PRIVATE_KEY + +const accounts: HttpNetworkAccountsUserConfig | undefined = MNEMONIC + ? { mnemonic: MNEMONIC } + : PRIVATE_KEY + ? [PRIVATE_KEY] + : undefined + +if (accounts == null) { + console.warn( + 'Could not find MNEMONIC or PRIVATE_KEY environment variables. It will not be possible to execute transactions in your example.' + ) +} + +const config: HardhatUserConfig = { + solidity: { + compilers: [ + { + version: '0.8.22', + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + ], + }, + networks: { + sepolia: { + eid: EndpointId.SEPOLIA_V2_TESTNET, + url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/', + accounts, + }, + fuji: { + eid: EndpointId.AVALANCHE_V2_TESTNET, + url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji', + accounts, + }, + amoy: { + eid: EndpointId.AMOY_V2_TESTNET, + url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com', + accounts, + }, + }, + namedAccounts: { + deployer: { + default: 0, // wallet address of index[0], of the mnemonic in .env + }, + }, +} + +export default config diff --git a/examples/onft721/layerzero.config.ts b/examples/onft721/layerzero.config.ts new file mode 100644 index 000000000..0212a343a --- /dev/null +++ b/examples/onft721/layerzero.config.ts @@ -0,0 +1,85 @@ +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' + +import type { OAppEdgeConfig, OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +const sepoliaContract: OmniPointHardhat = { + eid: EndpointId.SEPOLIA_V2_TESTNET, + contractName: 'MyONFT721', +} + +const fujiContract: OmniPointHardhat = { + eid: EndpointId.AVALANCHE_V2_TESTNET, + contractName: 'MyONFT721', +} + +const amoyContract: OmniPointHardhat = { + eid: EndpointId.AMOY_V2_TESTNET, + contractName: 'MyONFT721', +} + +const DEFAULT_EDGE_CONFIG: OAppEdgeConfig = { + enforcedOptions: [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 100_000, + value: 0, + }, + { + msgType: 2, + optionType: ExecutorOptionType.COMPOSE, + index: 0, + gas: 100_000, + value: 0, + }, + ], +} + +const config: OAppOmniGraphHardhat = { + contracts: [ + { + contract: fujiContract, + }, + { + contract: sepoliaContract, + }, + { + contract: amoyContract, + }, + ], + connections: [ + { + from: fujiContract, + to: sepoliaContract, + config: DEFAULT_EDGE_CONFIG, + }, + { + from: fujiContract, + to: amoyContract, + config: DEFAULT_EDGE_CONFIG, + }, + { + from: sepoliaContract, + to: fujiContract, + config: DEFAULT_EDGE_CONFIG, + }, + { + from: sepoliaContract, + to: amoyContract, + config: DEFAULT_EDGE_CONFIG, + }, + { + from: amoyContract, + to: sepoliaContract, + config: DEFAULT_EDGE_CONFIG, + }, + { + from: amoyContract, + to: fujiContract, + config: DEFAULT_EDGE_CONFIG, + }, + ], +} + +export default config diff --git a/examples/onft721/package.json b/examples/onft721/package.json new file mode 100644 index 000000000..30885bbbc --- /dev/null +++ b/examples/onft721/package.json @@ -0,0 +1,75 @@ +{ + "name": "@layerzerolabs/onft721-example", + "version": "0.0.1", + "license": "MIT", + "scripts": { + "clean": "rm -rf artifacts cache out", + "compile": "$npm_execpath run compile:forge && $npm_execpath run compile:hardhat", + "compile:forge": "forge build", + "compile:hardhat": "hardhat compile", + "lint": "$npm_execpath run lint:js && $npm_execpath run lint:sol", + "lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt", + "lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .", + "lint:sol": "solhint 'contracts/**/*.sol'", + "test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat", + "test:forge": "forge test", + "test:hardhat": "hardhat test" + }, + "resolutions": { + "@nomicfoundation/edr": "0.3.5", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + }, + "devDependencies": { + "@babel/core": "^7.23.9", + "@layerzerolabs/eslint-config-next": "~2.3.3", + "@layerzerolabs/lz-definitions": "^2.3.25", + "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.25", + "@layerzerolabs/lz-evm-oapp-v2": "^2.3.25", + "@layerzerolabs/lz-evm-protocol-v2": "^2.3.25", + "@layerzerolabs/lz-evm-v1-0.7": "^2.3.25", + "@layerzerolabs/lz-v2-utilities": "^2.3.25", + "@layerzerolabs/onft-evm": "0.0.1", + "@layerzerolabs/prettier-config-next": "^2.3.25", + "@layerzerolabs/solhint-config": "^2.3.3", + "@layerzerolabs/test-devtools-evm-foundry": "~0.2.7", + "@layerzerolabs/toolbox-foundry": "~0.1.7", + "@layerzerolabs/toolbox-hardhat": "~0.2.35", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts-upgradeable": "^5.0.1", + "@rushstack/eslint-patch": "^1.7.0", + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "@types/node": "~18.18.14", + "chai": "^4.4.1", + "dotenv": "^16.4.1", + "eslint-plugin-jest-extended": "~2.0.0", + "ethers": "^5.7.2", + "hardhat": "^2.22.3", + "hardhat-contract-sizer": "^2.10.0", + "hardhat-deploy": "^0.12.1", + "mocha": "^10.2.0", + "prettier": "^3.2.5", + "solhint": "^4.1.1", + "solidity-bytes-utils": "^0.8.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.16.0" + }, + "pnpm": { + "overrides": { + "@nomicfoundation/edr": "0.3.5", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } + }, + "overrides": { + "@nomicfoundation/edr": "0.3.5", + "ethers": "^5.7.2", + "hardhat-deploy": "^0.12.1" + } +} diff --git a/examples/onft721/solhint.config.js b/examples/onft721/solhint.config.js new file mode 100644 index 000000000..52efe629c --- /dev/null +++ b/examples/onft721/solhint.config.js @@ -0,0 +1 @@ +module.exports = require('@layerzerolabs/solhint-config'); diff --git a/examples/onft721/tsconfig.json b/examples/onft721/tsconfig.json new file mode 100644 index 000000000..027ad0f3f --- /dev/null +++ b/examples/onft721/tsconfig.json @@ -0,0 +1,13 @@ +{ + "exclude": ["node_modules"], + "include": ["deploy", "tasks", "test", "hardhat.config.ts"], + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/examples/onft721/turbo.json b/examples/onft721/turbo.json new file mode 100644 index 000000000..d3c81288a --- /dev/null +++ b/examples/onft721/turbo.json @@ -0,0 +1,9 @@ +{ + "extends": ["//"], + "pipeline": { + "compile": { + "dependsOn": ["@layerzerolabs/oft-example#compile"], + "description": "We include this dependency to make sure that the compilation is executed in series to avoid race conditions with solc compilers in foundry" + } + } +} diff --git a/packages/create-lz-oapp/src/config.ts b/packages/create-lz-oapp/src/config.ts index 3aff23588..dd67e439f 100644 --- a/packages/create-lz-oapp/src/config.ts +++ b/packages/create-lz-oapp/src/config.ts @@ -31,6 +31,13 @@ export const EXAMPLES: Example[] = [ directory: 'examples/oapp', ref, }, + { + id: 'onft721', + label: 'ONFT721', + repository, + directory: 'examples/onft721', + ref, + }, ] export const PACKAGE_MANAGERS: PackageManager[] = [ diff --git a/packages/onft-evm/.gitignore b/packages/onft-evm/.gitignore new file mode 100644 index 000000000..62efafcc3 --- /dev/null +++ b/packages/onft-evm/.gitignore @@ -0,0 +1 @@ +out/**/* diff --git a/packages/onft-evm/README.md b/packages/onft-evm/README.md new file mode 100644 index 000000000..3bcdfbcea --- /dev/null +++ b/packages/onft-evm/README.md @@ -0,0 +1,3 @@ +# ONFT721 + +:warning: ** This code is currently under audit and should not yet be used in production. ** diff --git a/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol b/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol new file mode 100644 index 000000000..924594c2e --- /dev/null +++ b/packages/onft-evm/contracts/libs/ONFTComposeMsgCodec.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +/** + * @title ONFT Composed Message Codec + * @notice Library for encoding and decoding ONFT composed messages. + */ +library ONFTComposeMsgCodec { + // Offset constants for decoding composed messages + uint8 private constant NONCE_OFFSET = 8; + uint8 private constant SRC_EID_OFFSET = 12; + uint8 private constant COMPOSE_FROM_OFFSET = 44; + + /** + * @dev Encodes a ONFT721 composed message. + * @param _nonce The nonce value. + * @param _srcEid The source LayerZero endpoint ID. + * @param _composeMsg The composed message. + * @return The encoded payload, including the composed message. + */ + function encode( + uint64 _nonce, + uint32 _srcEid, + bytes memory _composeMsg // 0x[composeFrom][composeMsg] + ) internal pure returns (bytes memory) { + return abi.encodePacked(_nonce, _srcEid, _composeMsg); + } + + /** + * @dev Retrieves the nonce from the composed message. + * @param _msg The message. + * @return The nonce value. + */ + function nonce(bytes calldata _msg) internal pure returns (uint64) { + return uint64(bytes8(_msg[:NONCE_OFFSET])); + } + + /** + * @dev Retrieves the source LayerZero endpoint ID from the composed message. + * @param _msg The message. + * @return The source LayerZero endpoint ID. + */ + function srcEid(bytes calldata _msg) internal pure returns (uint32) { + return uint32(bytes4(_msg[NONCE_OFFSET:SRC_EID_OFFSET])); + } + + /** + * @dev Retrieves the composeFrom value from the composed message. + * @param _msg The message. + * @return The composeFrom value as bytes32. + */ + function composeFrom(bytes calldata _msg) internal pure returns (bytes32) { + return bytes32(_msg[SRC_EID_OFFSET:COMPOSE_FROM_OFFSET]); + } + + /** + * @dev Retrieves the composed message. + * @param _msg The message. + * @return The composed message. + */ + function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) { + return _msg[COMPOSE_FROM_OFFSET:]; + } + + /** + * @dev Converts an address to bytes32. + * @param _addr The address to convert. + * @return The bytes32 representation of the address. + */ + function addressToBytes32(address _addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } + + /** + * @dev Converts bytes32 to an address. + * @param _b The bytes32 value to convert. + * @return The address representation of bytes32. + */ + function bytes32ToAddress(bytes32 _b) internal pure returns (address) { + return address(uint160(uint256(_b))); + } +} diff --git a/packages/onft-evm/contracts/onft721/ONFT721.sol b/packages/onft-evm/contracts/onft721/ONFT721.sol new file mode 100644 index 000000000..112152023 --- /dev/null +++ b/packages/onft-evm/contracts/onft721/ONFT721.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +import { IONFT721, ONFT721Core } from "./ONFT721Core.sol"; + +/** + * @title ONFT721 Contract + * @dev ONFT721 is an ERC-721 token that extends the functionality of the ONFT721Core contract. + */ +abstract contract ONFT721 is ONFT721Core, ERC721 { + string internal baseTokenURI; + + /** + * @dev Constructor for the ONFT721 contract. + * @param _name The name of the ONFT. + * @param _symbol The symbol of the ONFT. + * @param _lzEndpoint The LayerZero endpoint address. + * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. + */ + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) ERC721(_name, _symbol) ONFT721Core(_lzEndpoint, _delegate) {} + + /** + * @notice Retrieves the address of the underlying ERC721 implementation (ie. this contract). + */ + function token() external view returns (address) { + return address(this); + } + + function setBaseURI(string memory _baseTokenURI) external onlyOwner { + baseTokenURI = _baseTokenURI; + } + + function _baseURI() internal view override returns (string memory) { + return baseTokenURI; + } + + /** + * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send. + * @dev In the case of ONFT where the contract IS the token, approval is NOT required. + * @return requiresApproval Needs approval of the underlying token implementation. + */ + function approvalRequired() external pure virtual returns (bool) { + return false; + } + + function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { + if (_from != ERC721.ownerOf(_tokenId)) revert OnlyNFTOwner(_from, ERC721.ownerOf(_tokenId)); + _burn(_tokenId); + } + + function _credit(address _to, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { + _mint(_to, _tokenId); + } +} diff --git a/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol b/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol new file mode 100644 index 000000000..d0a4fd74c --- /dev/null +++ b/packages/onft-evm/contracts/onft721/ONFT721Adapter.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +import { ONFT721Core } from "./ONFT721Core.sol"; + +/** + * @title ONFT721Adapter Contract + * @dev ONFT721Adapter is a wrapper used to enable cross-chain transferring of an existing ERC721 token. + */ +abstract contract ONFT721Adapter is ONFT721Core { + IERC721 internal immutable innerToken; + + /** + * @dev Constructor for the ONFT721 contract. + * @param _token The underlying ERC721 token address this adapts + * @param _lzEndpoint The LayerZero endpoint address. + * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. + */ + constructor(address _token, address _lzEndpoint, address _delegate) ONFT721Core(_lzEndpoint, _delegate) { + innerToken = IERC721(_token); + } + + /** + * @notice Retrieves the address of the underlying ERC721 implementation (ie. external contract). + */ + function token() external view returns (address) { + return address(innerToken); + } + + /** + * @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send. + * @dev In the case of ONFT where the contract IS the token, approval is NOT required. + * @return requiresApproval Needs approval of the underlying token implementation. + */ + function approvalRequired() external pure virtual returns (bool) { + return true; + } + + function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override { + // @dev Dont need to check onERC721Received() when moving into this contract, ie. no 'safeTransferFrom' required + innerToken.transferFrom(_from, address(this), _tokenId); + } + + function _credit(address _toAddress, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override { + // @dev Do not need to check onERC721Received() when moving out of this contract, ie. no 'safeTransferFrom' required + // @dev The default implementation does not implement IERC721Receiver as 'safeTransferFrom' is not used. + // @dev If IERC721Receiver is required, ensure proper re-entrancy protection is implemented. + innerToken.transferFrom(address(this), _toAddress, _tokenId); + } +} diff --git a/packages/onft-evm/contracts/onft721/ONFT721Core.sol b/packages/onft-evm/contracts/onft721/ONFT721Core.sol new file mode 100644 index 000000000..3dcceb81d --- /dev/null +++ b/packages/onft-evm/contracts/onft721/ONFT721Core.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { OApp, Origin } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol"; +import { OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol"; +import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol"; +import { OAppPreCrimeSimulator } from "@layerzerolabs/lz-evm-oapp-v2/contracts/precrime/OAppPreCrimeSimulator.sol"; + +import { IONFT721, MessagingFee, MessagingReceipt, SendParam } from "./interfaces/IONFT721.sol"; +import { ONFT721MsgCodec } from "./libs/ONFT721MsgCodec.sol"; +import { ONFTComposeMsgCodec } from "../libs/ONFTComposeMsgCodec.sol"; + +/** + * @title ONFT721Core + * @dev Abstract contract for an ONFT721 token. + */ +abstract contract ONFT721Core is IONFT721, OApp, OAppPreCrimeSimulator, OAppOptionsType3 { + using ONFT721MsgCodec for bytes; + using ONFT721MsgCodec for bytes32; + + // @notice Msg types that are used to identify the various OFT operations. + // @dev This can be extended in child contracts for non-default oft operations + // @dev These values are used in things like combineOptions() in OAppOptionsType3.sol. + uint16 public constant SEND = 1; + uint16 public constant SEND_AND_COMPOSE = 2; + + // Address of an optional contract to inspect both 'message' and 'options' + address public msgInspector; + + event MsgInspectorSet(address inspector); + + /** + * @dev Constructor. + * @param _lzEndpoint The address of the LayerZero endpoint. + * @param _delegate The delegate capable of making OApp configurations inside of the endpoint. + */ + constructor(address _lzEndpoint, address _delegate) Ownable(_delegate) OApp(_lzEndpoint, _delegate) {} + + /** + * @notice Retrieves interfaceID and the version of the ONFT. + * @return interfaceId The interface ID (0x23e18da6). + * @return version The version. + * @dev version: Indicates a cross-chain compatible msg encoding with other ONFTs. + * @dev If a new feature is added to the ONFT cross-chain msg encoding, the version will be incremented. + * @dev ie. localONFT version(x,1) CAN send messages to remoteONFT version(x,1) + */ + function onftVersion() external pure virtual returns (bytes4 interfaceId, uint64 version) { + return (type(IONFT721).interfaceId, 1); + } + + /** + * @notice Sets the message inspector address for the OFT. + * @param _msgInspector The address of the message inspector. + * @dev This is an optional contract that can be used to inspect both 'message' and 'options'. + * @dev Set it to address(0) to disable it, or set it to a contract address to enable it. + */ + function setMsgInspector(address _msgInspector) public virtual onlyOwner { + msgInspector = _msgInspector; + emit MsgInspectorSet(_msgInspector); + } + + function quoteSend( + SendParam calldata _sendParam, + bool _payInLzToken + ) external view virtual returns (MessagingFee memory msgFee) { + (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam); + return _quote(_sendParam.dstEid, message, options, _payInLzToken); + } + + function send( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable virtual returns (MessagingReceipt memory msgReceipt) { + _debit(msg.sender, _sendParam.tokenId, _sendParam.dstEid); + + (bytes memory message, bytes memory options) = _buildMsgAndOptions(_sendParam); + + // @dev Sends the message to the LayerZero Endpoint, returning the MessagingReceipt. + msgReceipt = _lzSend(_sendParam.dstEid, message, options, _fee, _refundAddress); + emit ONFTSent(msgReceipt.guid, _sendParam.dstEid, msg.sender, _sendParam.tokenId); + } + + /** + * @dev Internal function to build the message and options. + * @param _sendParam The parameters for the send() operation. + * @return message The encoded message. + * @return options The encoded options. + */ + function _buildMsgAndOptions( + SendParam calldata _sendParam + ) internal view virtual returns (bytes memory message, bytes memory options) { + if (_sendParam.to == bytes32(0)) revert InvalidReceiver(); + bool hasCompose; + (message, hasCompose) = ONFT721MsgCodec.encode(_sendParam.to, _sendParam.tokenId, _sendParam.composeMsg); + uint16 msgType = hasCompose ? SEND_AND_COMPOSE : SEND; + + options = combineOptions(_sendParam.dstEid, msgType, _sendParam.extraOptions); + + // @dev Optionally inspect the message and options depending if the OApp owner has set a msg inspector. + // @dev If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean + if (msgInspector != address(0)) IOAppMsgInspector(msgInspector).inspect(message, options); + } + + /** + * @dev Internal function to handle the receive on the LayerZero endpoint. + * @param _origin The origin information. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address from the src chain. + * - nonce: The nonce of the LayerZero message. + * @param _guid The unique identifier for the received LayerZero message. + * @param _message The encoded message. + * @dev _executor The address of the executor. + * @dev _extraData Additional data. + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address /*_executor*/, // @dev unused in the default implementation. + bytes calldata /*_extraData*/ // @dev unused in the default implementation. + ) internal virtual override { + address toAddress = _message.sendTo().bytes32ToAddress(); + uint256 tokenId = _message.tokenId(); + + _credit(toAddress, tokenId, _origin.srcEid); + + if (_message.isComposed()) { + bytes memory composeMsg = ONFTComposeMsgCodec.encode(_origin.nonce, _origin.srcEid, _message.composeMsg()); + // @dev As batching is not implemented, the compose index is always 0. + // @dev If batching is added, the index will need to be tracked. + endpoint.sendCompose(toAddress, _guid, 0 /* the index of composed message*/, composeMsg); + } + + emit ONFTReceived(_guid, _origin.srcEid, toAddress, tokenId); + } + + /* + * @dev Internal function to handle the OAppPreCrimeSimulator simulated receive. + * @param _origin The origin information. + * - srcEid: The source chain endpoint ID. + * - sender: The sender address from the src chain. + * - nonce: The nonce of the LayerZero message. + * @param _guid The unique identifier for the received LayerZero message. + * @param _message The LayerZero message. + * @param _executor The address of the off-chain executor. + * @param _extraData Arbitrary data passed by the msg executor. + * @dev Enables the preCrime simulator to mock sending lzReceive() messages, + * routes the msg down from the OAppPreCrimeSimulator, and back up to the OAppReceiver. + */ + function _lzReceiveSimulate( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual override { + _lzReceive(_origin, _guid, _message, _executor, _extraData); + } + + /** + * @dev Check if the peer is considered 'trusted' by the OApp. + * @param _eid The endpoint ID to check. + * @param _peer The peer to check. + * @return Whether the peer passed is considered 'trusted' by the OApp. + * @dev Enables OAppPreCrimeSimulator to check whether a potential Inbound Packet is from a trusted source. + */ + function isPeer(uint32 _eid, bytes32 _peer) public view virtual override returns (bool) { + return peers[_eid] == _peer; + } + + function _debit(address /*_from*/, uint256 /*_tokenId*/, uint32 /*_dstEid*/) internal virtual; + + function _credit(address /*_to*/, uint256 /*_tokenId*/, uint32 /*_srcEid*/) internal virtual; +} diff --git a/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol b/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol new file mode 100644 index 000000000..50130e551 --- /dev/null +++ b/packages/onft-evm/contracts/onft721/interfaces/IONFT721.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +import { MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OAppSender.sol"; + +/** + * @dev Struct representing token parameters for the ONFT send() operation. + */ +struct SendParam { + uint32 dstEid; // Destination LayerZero EndpointV2 ID. + bytes32 to; // Recipient address. + uint256 tokenId; + bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. + bytes composeMsg; // The composed message for the send() operation. + bytes onftCmd; // The ONFT command to be executed, unused in default ONFT implementations. +} + +/** + * @title IONFT + * @dev Interface for the ONFT721 token. + * @dev Does not inherit ERC721 to accommodate usage by OFT721Adapter. + */ +interface IONFT721 { + // Custom error messages + error InvalidReceiver(); + error OnlyNFTOwner(address caller, address owner); + + // Events + event ONFTSent( + bytes32 indexed guid, // GUID of the ONFT message. + uint32 dstEid, // Destination Endpoint ID. + address indexed fromAddress, // Address of the sender on the src chain. + uint256 tokenId // ONFT ID sent. + ); + + event ONFTReceived( + bytes32 indexed guid, // GUID of the ONFT message. + uint32 srcEid, // Source Endpoint ID. + address indexed toAddress, // Address of the recipient on the dst chain. + uint256 tokenId // ONFT ID received. + ); + + /** + * @notice Retrieves interfaceID and the version of the ONFT. + * @return interfaceId The interface ID. + * @return version The version. + * @dev interfaceId: This specific interface ID is '0x94642228'. + * @dev version: Indicates a cross-chain compatible msg encoding with other ONFTs. + * @dev If a new feature is added to the ONFT cross-chain msg encoding, the version will be incremented. + * ie. localONFT version(x,1) CAN send messages to remoteONFT version(x,1) + */ + function onftVersion() external view returns (bytes4 interfaceId, uint64 version); + + /** + * @notice Retrieves the address of the token associated with the ONFT. + * @return token The address of the ERC721 token implementation. + */ + function token() external view returns (address); + + /** + * @notice Indicates whether the ONFT contract requires approval of the 'token()' to send. + * @return requiresApproval Needs approval of the underlying token implementation. + * @dev Allows things like wallet implementers to determine integration requirements, + * without understanding the underlying token implementation. + */ + function approvalRequired() external view returns (bool); + + /** + * @notice Provides a quote for the send() operation. + * @param _sendParam The parameters for the send() operation. + * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token. + * @return fee The calculated LayerZero messaging fee from the send() operation. + * @dev MessagingFee: LayerZero msg fee + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + */ + function quoteSend(SendParam calldata _sendParam, bool _payInLzToken) external view returns (MessagingFee memory); + + /** + * @notice Executes the send() operation. + * @param _sendParam The parameters for the send operation. + * @param _fee The fee information supplied by the caller. + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + * @param _refundAddress The address to receive any excess funds from fees etc. on the src. + * @return receipt The LayerZero messaging receipt from the send() operation. + * @dev MessagingReceipt: LayerZero msg receipt + * - guid: The unique identifier for the sent message. + * - nonce: The nonce of the sent message. + * - fee: The LayerZero fee incurred for the message. + */ + function send( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable returns (MessagingReceipt memory); +} diff --git a/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol b/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol new file mode 100644 index 000000000..db69fd4a4 --- /dev/null +++ b/packages/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.22; + +/** + * @title ONFT721MsgCodec + * @notice Library for encoding and decoding ONFT721 LayerZero messages. + */ +library ONFT721MsgCodec { + uint8 private constant SEND_TO_OFFSET = 32; + uint8 private constant TOKEN_ID_OFFSET = 64; + + /** + * @dev Encodes an ONFT721 LayerZero message payload. + * @param _sendTo The recipient address. + * @param _tokenId The ID of the token to transfer. + * @param _composeMsg The composed payload. + * @return payload The encoded message payload. + * @return hasCompose A boolean indicating whether the message payload contains a composed payload. + */ + function encode( + bytes32 _sendTo, + uint256 _tokenId, + bytes memory _composeMsg + ) internal view returns (bytes memory payload, bool hasCompose) { + hasCompose = _composeMsg.length > 0; + payload = hasCompose + ? abi.encodePacked(_sendTo, _tokenId, addressToBytes32(msg.sender), _composeMsg) + : abi.encodePacked(_sendTo, _tokenId); + } + + /** + * @dev Decodes sendTo from the ONFT LayerZero message. + * @param _msg The message. + * @return The recipient address in bytes32 format. + */ + function sendTo(bytes calldata _msg) internal pure returns (bytes32) { + return bytes32(_msg[:SEND_TO_OFFSET]); + } + + /** + * @dev Decodes tokenId from the ONFT LayerZero message. + * @param _msg The message. + * @return The ID of the tokens to transfer. + */ + function tokenId(bytes calldata _msg) internal pure returns (uint256) { + return abi.decode(_msg[SEND_TO_OFFSET:TOKEN_ID_OFFSET], (uint256)); + } + + /** + * @dev Decodes whether there is a composed payload. + * @param _msg The message. + * @return A boolean indicating whether the message has a composed payload. + */ + function isComposed(bytes calldata _msg) internal pure returns (bool) { + return _msg.length > TOKEN_ID_OFFSET; + } + + /** + * @dev Decodes the composed message. + * @param _msg The message. + * @return The composed message. + */ + function composeMsg(bytes calldata _msg) internal pure returns (bytes memory) { + return _msg[TOKEN_ID_OFFSET:]; + } + + /** + * @dev Converts an address to bytes32. + * @param _addr The address to convert. + * @return The bytes32 representation of the address. + */ + function addressToBytes32(address _addr) internal pure returns (bytes32) { + return bytes32(uint256(uint160(_addr))); + } + + /** + * @dev Converts bytes32 to an address. + * @param _b The bytes32 value to convert. + * @return The address representation of bytes32. + */ + function bytes32ToAddress(bytes32 _b) internal pure returns (address) { + return address(uint160(uint256(_b))); + } +} diff --git a/packages/onft-evm/foundry.toml b/packages/onft-evm/foundry.toml new file mode 100644 index 000000000..23238e574 --- /dev/null +++ b/packages/onft-evm/foundry.toml @@ -0,0 +1,27 @@ +[profile.default] +solc-version = '0.8.22' +src = 'contracts' +out = 'out' +test = 'test' +cache_path = 'cache' +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] + +remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/src/ds-test/src/', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/src/forge-std/src/', + '@layerzerolabs/=node_modules/@layerzerolabs/', + '@openzeppelin/=node_modules/@openzeppelin/', +] diff --git a/packages/onft-evm/package.json b/packages/onft-evm/package.json new file mode 100644 index 000000000..74b84c163 --- /dev/null +++ b/packages/onft-evm/package.json @@ -0,0 +1,63 @@ +{ + "name": "@layerzerolabs/onft-evm", + "version": "0.0.1", + "description": "", + "repository": { + "type": "git", + "url": "https://github.com/LayerZero-Labs/devtools.git" + }, + "exports": { + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "files": [ + "artifacts/contracts/**/!(*.dbg).json", + "contracts/**/*" + ], + "scripts": { + "compile": "$npm_execpath compile:forge", + "compile:forge": "forge build", + "test": "$npm_execpath test:forge", + "test:forge": "forge test" + }, + "devDependencies": { + "@babel/core": "^7.23.9", + "@layerzerolabs/eslint-config-next": "~2.3.3", + "@layerzerolabs/lz-definitions": "^2.3.25", + "@layerzerolabs/lz-evm-messagelib-v2": "^2.3.25", + "@layerzerolabs/lz-evm-oapp-v2": "^2.3.25", + "@layerzerolabs/lz-evm-protocol-v2": "^2.3.25", + "@layerzerolabs/lz-evm-v1-0.7": "^2.3.25", + "@layerzerolabs/lz-v2-utilities": "^2.3.25", + "@layerzerolabs/prettier-config-next": "^2.3.25", + "@layerzerolabs/solhint-config": "^2.3.3", + "@layerzerolabs/test-devtools-evm-foundry": "~0.2.7", + "@layerzerolabs/toolbox-foundry": "~0.1.7", + "@layerzerolabs/toolbox-hardhat": "~0.2.35", + "@nomicfoundation/hardhat-ethers": "^3.0.5", + "@nomiclabs/hardhat-ethers": "^2.2.3", + "@openzeppelin/contracts": "^5.0.2", + "@openzeppelin/contracts-upgradeable": "^5.0.2", + "@rushstack/eslint-patch": "^1.7.0", + "@types/chai": "^4.3.11", + "@types/mocha": "^10.0.6", + "@types/node": "~18.18.14", + "chai": "^4.4.1", + "dotenv": "^16.4.1", + "erc721a": "^4.3.0", + "eslint-plugin-jest-extended": "~2.0.0", + "ethers": "^5.7.2", + "hardhat": "^2.22.3", + "hardhat-contract-sizer": "^2.10.0", + "hardhat-deploy": "^0.12.1", + "mocha": "^10.2.0", + "prettier": "^3.2.5", + "solhint": "^4.1.1", + "solidity-bytes-utils": "^0.8.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.16.0" + } +} diff --git a/packages/onft-evm/test/mocks/ComposerMock.sol b/packages/onft-evm/test/mocks/ComposerMock.sol new file mode 100644 index 000000000..eff6e5907 --- /dev/null +++ b/packages/onft-evm/test/mocks/ComposerMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { IOAppComposer } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppComposer.sol"; + +contract ComposerMock is IOAppComposer { + // default empty values for testing a lzCompose received message + address public from; + bytes32 public guid; + bytes public message; + address public executor; + bytes public extraData; + + function lzCompose( + address _from, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata /*_extraData*/ + ) external payable { + from = _from; + guid = _guid; + message = _message; + executor = _executor; + extraData = _message; + } +} diff --git a/packages/onft-evm/test/mocks/InspectorMock.sol b/packages/onft-evm/test/mocks/InspectorMock.sol new file mode 100644 index 000000000..da0b0a932 --- /dev/null +++ b/packages/onft-evm/test/mocks/InspectorMock.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol"; + +contract InspectorMock is IOAppMsgInspector { + function inspect(bytes calldata _message, bytes calldata _options) external pure returns (bool) { + revert InspectionFailed(_message, _options); + } +} diff --git a/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol b/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol new file mode 100644 index 000000000..af2fea258 --- /dev/null +++ b/packages/onft-evm/test/onft/ONFTBaseTestHelper.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; + +abstract contract ONFTBaseTestHelper is TestHelperOz5 { + uint256 internal constant INITIAL_NATIVE_BALANCE = 1000 ether; + + uint32 internal constant A_EID = 1; + uint32 internal constant B_EID = 2; + uint32 internal constant C_EID = 3; + uint8 internal constant NUM_ENDPOINTS = 3; + + address internal alice = makeAddr("alice"); + address internal bob = makeAddr("bob"); + address internal charlie = makeAddr("charlie"); + + function setUp() public virtual override { + super.setUp(); + setUpEndpoints(NUM_ENDPOINTS, LibraryType.UltraLightNode); + _deal(); + } + + /// @dev deal initial native balance to alice, bob, charlie + function _deal() internal virtual { + vm.deal(alice, INITIAL_NATIVE_BALANCE); + vm.deal(bob, INITIAL_NATIVE_BALANCE); + vm.deal(charlie, INITIAL_NATIVE_BALANCE); + } + + function sliceUintArray(uint[] memory array, uint start, uint end) public pure returns (uint[] memory) { + if (start == end) return new uint[](0); + require(end <= array.length, "end index out of bounds"); + + uint length = end - start; + uint[] memory slicedArray = new uint[](length); + + for (uint i = 0; i < length; i++) { + slicedArray[i] = array[start + i]; + } + + return slicedArray; + } +} diff --git a/packages/onft-evm/test/onft/onft721/ONFT721.t.sol b/packages/onft-evm/test/onft/onft721/ONFT721.t.sol new file mode 100644 index 000000000..2f2578799 --- /dev/null +++ b/packages/onft-evm/test/onft/onft721/ONFT721.t.sol @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol"; +import { ONFTComposeMsgCodec } from "../../../contracts/libs/ONFTComposeMsgCodec.sol"; +import { ONFT721Adapter } from "../../../contracts/onft721/ONFT721Adapter.sol"; + +import { IONFT721 } from "../../../contracts/onft721/interfaces/IONFT721.sol"; +import { ERC721Mock } from "./mocks/ERC721Mock.sol"; +import { ONFT721MsgCodec } from "../../../contracts/onft721/libs/ONFT721MsgCodec.sol"; +import { ComposerMock } from "../../mocks/ComposerMock.sol"; +import { InspectorMock, IOAppMsgInspector } from "../../mocks/InspectorMock.sol"; +import { MessagingFee, MessagingReceipt } from "../../../contracts/onft721/ONFT721Core.sol"; +import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol"; + +import { SendParam } from "../../../contracts/onft721/interfaces/IONFT721.sol"; + +import { ONFT721Mock } from "./mocks/ONFT721Mock.sol"; +import { ONFT721AdapterMock } from "./mocks/ONFT721AdapterMock.sol"; +import { ONFT721Base } from "./ONFT721Base.sol"; + +contract ONFT721Test is ONFT721Base { + using OptionsBuilder for bytes; + + bytes4 internal constant EXPECTED_ONFT721_ID = 0x23e18da6; + uint8 internal constant EXPECTED_ONFT721_VERSION = 1; + + // also tests token() function + function test_constructor() public { + assertEq(aONFT.owner(), address(this)); + assertEq(bONFT.owner(), address(this)); + assertEq(cONFTAdapter.owner(), address(this)); + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID); + + assertEq(aONFT.token(), address(aONFT)); + assertEq(bONFT.token(), address(bONFT)); + assertEq(cONFTAdapter.token(), address(cERC721Mock)); + } + + function test_approvalRequired() public { + assertFalse(aONFT.approvalRequired()); + assertFalse(bONFT.approvalRequired()); + assertTrue(cONFTAdapter.approvalRequired()); + } + + function test_onftVersion() public { + (bytes4 interfaceId, uint64 version) = aONFT.onftVersion(); + bytes4 expectedId = EXPECTED_ONFT721_ID; + assertEq(interfaceId, expectedId); + assertEq(version, EXPECTED_ONFT721_VERSION); + } + + function test_send(uint16 _tokenToSend) public { + // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter + vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3); + + // 2. Set enforced options for SEND + _setMeshDefaultEnforcedSendOption(); + + // 3. Sanity check token balances and _tokenToSend ownership + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(IERC721(cONFTAdapter.token()).ownerOf(_tokenToSend), charlie); + + // 4. Send the same ONFT in a circle 10 times. + // a) C->A + // b) A->B + // c) B->C + for (uint8 i = 0; i < 10; i++) { + vm.startPrank(charlie); + IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend); + vm.stopPrank(); + _sendAndCheck( + _tokenToSend, + C_EID, + A_EID, + charlie, + alice, + DEFAULT_INITIAL_ONFTS_PER_EID, + DEFAULT_INITIAL_ONFTS_PER_EID, + true, + false + ); + _sendAndCheck( + _tokenToSend, + A_EID, + B_EID, + alice, + bob, + DEFAULT_INITIAL_ONFTS_PER_EID + 1, + DEFAULT_INITIAL_ONFTS_PER_EID, + false, + false + ); + _sendAndCheck( + _tokenToSend, + B_EID, + C_EID, + bob, + charlie, + DEFAULT_INITIAL_ONFTS_PER_EID + 1, + DEFAULT_INITIAL_ONFTS_PER_EID - 1, + false, + true + ); + } + + // 5. Check the final balances + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID); + } + + /// @dev Test to ensure that the quoteSend function reverts when the receiver is invalid. + function test_quoteSend_InvalidReceiver(uint16 _tokenToSend) public { + // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter + vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3); + + // 2. Set enforced options for SEND + _setMeshDefaultEnforcedSendOption(); + + SendParam memory sendParam = SendParam(B_EID, addressToBytes32(address(0)), _tokenToSend, "", "", ""); + vm.expectRevert(IONFT721.InvalidReceiver.selector); + IONFT721(onfts[2]).quoteSend(sendParam, false); + } + + /// @dev Test to ensure that the send function reverts when the receiver is invalid. + function test_send_InvalidReceiver(uint16 _tokenToSend) public { + // 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter + vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3); + + // 2. Set enforced options for SEND + _setMeshDefaultEnforcedSendOption(); + + SendParam memory sendParam = SendParam(B_EID, addressToBytes32(address(0)), _tokenToSend, "", "", ""); + MessagingFee memory fee = MessagingFee(200_000, 0); + + vm.startPrank(charlie); + IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend); + vm.expectRevert(IONFT721.InvalidReceiver.selector); + IONFT721(onfts[2]).send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + vm.stopPrank(); + } + + function test_sendAndCompose(uint8 _tokenToSend, bytes memory _composeMsg) public { + vm.assume(_composeMsg.length > 0); + + assertEq(aONFT.ownerOf(_tokenToSend), alice); + + ComposerMock composer = new ComposerMock(); + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(500000, 0) + .addExecutorLzComposeOption(0, 500000, 0); + SendParam memory sendParam = SendParam( + B_EID, + addressToBytes32(address(composer)), + _tokenToSend, + options, + _composeMsg, + "" + ); + MessagingFee memory fee = aONFT.quoteSend(sendParam, false); + + assertEq(bONFT.balanceOf(address(composer)), 0); + + vm.prank(alice); + MessagingReceipt memory msgReceipt = aONFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + verifyPackets(B_EID, addressToBytes32(address(bONFT))); + + // lzCompose params + address from_ = address(bONFT); + bytes memory options_ = options; + bytes32 guid_ = msgReceipt.guid; + address to_ = address(composer); + bytes memory composerMsg_ = ONFTComposeMsgCodec.encode( + msgReceipt.nonce, + A_EID, + abi.encodePacked(addressToBytes32(alice), _composeMsg) + ); + this.lzCompose(B_EID, from_, options_, guid_, to_, composerMsg_); + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID - 1); + assertEq(bONFT.balanceOf(address(composer)), 1); + + assertEq(composer.from(), from_); + assertEq(composer.guid(), guid_); + assertEq(composer.message(), composerMsg_); + assertEq(composer.executor(), address(this)); + assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + } + + function test_ONFTComposeMsgCodec(uint64 _nonce, uint32 _srcEid, bytes memory _composeMsg) public { + vm.assume(_composeMsg.length > 0); + + bytes memory message = ONFTComposeMsgCodec.encode( + _nonce, + _srcEid, + abi.encodePacked(addressToBytes32(msg.sender), _composeMsg) + ); + (uint64 nonce, uint32 srcEid, bytes32 composeFrom, bytes memory composeMsg) = this._decodeONFTComposeMsgCodec( + message + ); + + assertEq(nonce, _nonce); + assertEq(srcEid, _srcEid); + assertEq(composeFrom, addressToBytes32(msg.sender)); + assertEq(composeMsg, _composeMsg); + } + + function _decodeONFTComposeMsgCodec( + bytes calldata _message + ) public pure returns (uint64 nonce, uint32 srcEid, bytes32 composeFrom, bytes memory composeMsg) { + nonce = ONFTComposeMsgCodec.nonce(_message); + srcEid = ONFTComposeMsgCodec.srcEid(_message); + composeFrom = ONFTComposeMsgCodec.composeFrom(_message); + composeMsg = ONFTComposeMsgCodec.composeMsg(_message); + } + + function test_debit(uint256 _tokenId) public { + vm.assume(_tokenId < DEFAULT_INITIAL_ONFTS_PER_EID); + vm.assume(aONFT.ownerOf(_tokenId) == alice); + + uint32 dstEid = A_EID; + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(aONFT.balanceOf(address(this)), 0); + + vm.prank(alice); + aONFT.debit(_tokenId, dstEid); + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID - 1); + assertFalse(aONFT.exists(_tokenId)); + assertEq(aONFT.balanceOf(address(this)), 0); + } + + function test_credit(uint256 _tokenId) public { + vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID); + uint32 srcEid = A_EID; + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(aONFT.balanceOf(address(this)), 0); + + vm.prank(alice); + aONFT.credit(alice, _tokenId, srcEid); + + assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID + 1); + assertEq(aONFT.ownerOf(_tokenId), alice); + assertEq(aONFT.balanceOf(address(this)), 0); + } + + function test_ONFTAdapter_debitAndCredit(uint16 _tokenId) public { + // Ensure that the tokenId is owned by userC + vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID * 2 && _tokenId < DEFAULT_INITIAL_ONFTS_PER_EID * 3); + vm.assume(cERC721Mock.ownerOf(_tokenId) == charlie); + + uint32 dstEid = C_EID; + uint32 srcEid = C_EID; + + assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID); + assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2); + + vm.prank(charlie); + cERC721Mock.approve(address(cONFTAdapter), _tokenId); + vm.prank(charlie); + cONFTAdapter.debit(_tokenId, dstEid); + + // Ensure that + // 1. userC balance is decremented by 1. + // 2. The Adapter balance is incremented by 1. + // 3. The Adapter is the owner of the token + assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID - 1); + assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2 + 1); + assertEq(cERC721Mock.ownerOf(_tokenId), address(cONFTAdapter)); + + vm.prank(charlie); + cONFTAdapter.credit(bob, _tokenId, srcEid); + + // Ensure that: + // 1. userB balance is incremented by 1. + // 2. The Adapter balance is decremented by 1. + // 3. userB owns the token + assertEq(cERC721Mock.balanceOf(address(bob)), 1); + assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2); + assertEq(cERC721Mock.ownerOf(_tokenId), bob); + } + + function _decodeONFTMsgCodec( + bytes calldata _message + ) public pure returns (bool isComposed, bytes32 sendTo, uint256 tokenId, bytes memory composeMsg) { + isComposed = ONFT721MsgCodec.isComposed(_message); + sendTo = ONFT721MsgCodec.sendTo(_message); + tokenId = ONFT721MsgCodec.tokenId(_message); + composeMsg = ONFT721MsgCodec.composeMsg(_message); + } + + function test_buildMsgAndOptions( + uint256 _tokenId, + bytes memory _composeMsg, + uint128 _baseGas, + uint128 _value, + uint128 _composeGas + ) public { + vm.assume(_baseGas > 0); + vm.assume(_composeGas > 0); + + bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(_baseGas, _value); + if (_composeMsg.length > 0) extraOptions = extraOptions.addExecutorLzComposeOption(0, _composeGas, _value); + SendParam memory sendParam = SendParam(B_EID, addressToBytes32(alice), _tokenId, extraOptions, _composeMsg, ""); + + (bytes memory message, bytes memory options) = aONFT.buildMsgAndOptions(sendParam); + + assertEq(options, extraOptions); + (bool isComposed, bytes32 sendTo, uint256 tokenId, bytes memory composeMsg) = this._decodeONFTMsgCodec(message); + assertEq(isComposed, _composeMsg.length > 0); + assertEq(sendTo, addressToBytes32(alice)); + assertEq(tokenId, _tokenId); + bytes memory expectedComposeMsg = abi.encodePacked(addressToBytes32(address(this)), _composeMsg); + assertEq(composeMsg, _composeMsg.length > 0 ? expectedComposeMsg : bytes("")); + } + + function test_buildMsgAndOptions_noComposition( + uint256 _tokenId, + bool _useEnforcedOptions, + bool _useExtraOptions, + uint128 _lzReceiveGas, + uint128 _lzReceiveValue + ) public { + if (_useEnforcedOptions) _setMeshDefaultEnforcedSendOption(); + bytes memory extraOptions = _useExtraOptions + ? OptionsBuilder.newOptions().addExecutorLzReceiveOption(_lzReceiveGas, _lzReceiveValue) + : bytes(""); + SendParam memory sendParam = SendParam(B_EID, addressToBytes32(alice), _tokenId, extraOptions, "", ""); + + (bytes memory message, bytes memory options) = aONFT.buildMsgAndOptions(sendParam); + assertEq(options, aONFT.combineOptions(B_EID, 1, extraOptions)); + (bool isComposed_, bytes32 sendTo_, uint256 tokenId_, bytes memory composeMsg_) = this._decodeONFTMsgCodec( + message + ); + assertEq(isComposed_, false); + assertEq(sendTo_, addressToBytes32(alice)); + assertEq(tokenId_, _tokenId); + assertEq(composeMsg_.length, 0); + assertEq(composeMsg_, ""); + } + + function test_setEnforcedOptions( + uint32 _eid, + uint128 _optionTypeOneGas, + uint128 _optionTypeOneValue, + uint128 _optionTypeTwoGas, + uint128 _optionTypeTwoValue + ) public { + vm.assume( + _optionTypeOneGas > 0 && + _optionTypeOneGas < type(uint128).max && + _optionTypeTwoGas > 0 && + _optionTypeTwoGas < type(uint128).max + ); + + bytes memory optionsTypeOne = OptionsBuilder.newOptions().addExecutorLzReceiveOption( + _optionTypeOneGas, + _optionTypeOneValue + ); + bytes memory optionsTypeTwo = OptionsBuilder.newOptions().addExecutorLzReceiveOption( + _optionTypeTwoGas, + _optionTypeTwoValue + ); + + EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](2); + enforcedOptions[0] = EnforcedOptionParam(_eid, 1, optionsTypeOne); + enforcedOptions[1] = EnforcedOptionParam(_eid, 2, optionsTypeTwo); + + aONFT.setEnforcedOptions(enforcedOptions); + + assertEq(aONFT.enforcedOptions(_eid, 1), optionsTypeOne); + assertEq(aONFT.enforcedOptions(_eid, 2), optionsTypeTwo); + } + + function test_assertOptionsType3(uint32 _eid, bytes2 _prefix) public { + vm.assume(_prefix != bytes2(0x0003)); + + EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](1); + + bytes memory options = new bytes(2); + assembly { + mstore(add(options, 32), _prefix) + } + + enforcedOptions[0] = EnforcedOptionParam(_eid, 1, options); // not type 3 + vm.expectRevert(abi.encodeWithSelector(IOAppOptionsType3.InvalidOptions.selector, options)); + aONFT.setEnforcedOptions(enforcedOptions); + } + + function test_combineOptions( + uint32 _eid, + uint16 _msgType, + uint128 _enforcedOptionGas, + uint128 _enforcedOptionNativeDrop, + uint128 _combinedOptionNativeDrop + ) public { + vm.assume(uint256(_enforcedOptionNativeDrop) + _combinedOptionNativeDrop < type(uint128).max); + + bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption( + _enforcedOptionGas, + _enforcedOptionNativeDrop + ); + EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1); + enforcedOptionsArray[0] = EnforcedOptionParam(_eid, _msgType, enforcedOptions); + aONFT.setEnforcedOptions(enforcedOptionsArray); + + bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption( + _combinedOptionNativeDrop, + addressToBytes32(alice) + ); + + bytes memory expectedOptions = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(_enforcedOptionGas, _enforcedOptionNativeDrop) + .addExecutorNativeDropOption(_combinedOptionNativeDrop, addressToBytes32(alice)); + + bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, extraOptions); + assertEq(combinedOptions, expectedOptions); + } + + function test_combineOptions_noExtraOptions( + uint32 _eid, + uint16 _msgType, + uint128 _enforcedOptionGas, + uint128 _enforcedOptionNativeGas + ) public { + bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption( + _enforcedOptionGas, + _enforcedOptionNativeGas + ); + EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1); + enforcedOptionsArray[0] = EnforcedOptionParam(_eid, _msgType, enforcedOptions); + aONFT.setEnforcedOptions(enforcedOptionsArray); + + bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption( + _enforcedOptionGas, + _enforcedOptionNativeGas + ); + + bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, ""); + assertEq(combinedOptions, expectedOptions); + } + + function test_combineOptions_noEnforcedOptions( + uint32 _eid, + uint16 _msgType, + uint128 _combinedOptionNativeDrop + ) public { + bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption( + _combinedOptionNativeDrop, + addressToBytes32(alice) + ); + + bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption( + _combinedOptionNativeDrop, + addressToBytes32(alice) + ); + + bytes memory combinedOptions = aONFT.combineOptions(_eid, _msgType, extraOptions); + assertEq(combinedOptions, expectedOptions); + } + + function test_OAppInspector_inspect(uint256 _tokenId, bytes32 _to) public { + uint32 dstEid = B_EID; + _setMeshDefaultEnforcedSendOption(); + + SendParam memory sendParam = SendParam(dstEid, _to, _tokenId, "", "", ""); + + // doesnt revert + (bytes memory message, ) = aONFT.buildMsgAndOptions(sendParam); + + // deploy a universal inspector, it automatically reverts + oAppInspector = new InspectorMock(); + aONFT.setMsgInspector(address(oAppInspector)); + + // does revert because inspector is set + vm.expectRevert( + abi.encodeWithSelector( + IOAppMsgInspector.InspectionFailed.selector, + message, + aONFT.enforcedOptions(B_EID, 1) + ) + ); + (message, ) = aONFT.buildMsgAndOptions(sendParam); + } + + function test_setBaseURI(address _user, string memory _baseTokenURI, uint256 _id) public { + vm.assume(_user != address(this)); + + // 1. Test non privileged user + vm.prank(_user); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _user)); + aONFT.setBaseURI(_baseTokenURI); + + // 2. Test setting with owner doesn't throw + aONFT.setBaseURI(_baseTokenURI); + } +} diff --git a/packages/onft-evm/test/onft/onft721/ONFT721Base.sol b/packages/onft-evm/test/onft/onft721/ONFT721Base.sol new file mode 100644 index 000000000..07579cd31 --- /dev/null +++ b/packages/onft-evm/test/onft/onft721/ONFT721Base.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol"; +import { EnforcedOptionParam, OAppOptionsType3 } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol"; +import { MessagingFee } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; + +import { IONFT721, SendParam } from "../../../contracts/onft721/interfaces/IONFT721.sol"; +import { ONFT721Adapter } from "../../../contracts/onft721/ONFT721Adapter.sol"; + +import { ERC721Mock } from "./mocks/ERC721Mock.sol"; +import { ONFT721Mock } from "./mocks/ONFT721Mock.sol"; +import { ONFT721AdapterMock } from "./mocks/ONFT721AdapterMock.sol"; +import { InspectorMock, IOAppMsgInspector } from "../../mocks/InspectorMock.sol"; + +import { ONFTBaseTestHelper } from "../ONFTBaseTestHelper.sol"; + +abstract contract ONFT721Base is ONFTBaseTestHelper { + using OptionsBuilder for bytes; + + string internal constant A_ONFT_NAME = "aONFT"; + string internal constant A_ONFT_SYMBOL = "aONFT"; + string internal constant B_ONFT_NAME = "bONFT"; + string internal constant B_ONFT_SYMBOL = "bONFT"; + string internal constant C_TOKEN_NAME = "cONFT"; + string internal constant C_TOKEN_SYMBOL = "cONFT"; + + uint256 internal constant DEFAULT_INITIAL_ONFTS_PER_EID = 256; + + address[] public onfts; + ONFT721Mock internal aONFT; + ONFT721Mock internal bONFT; + ONFT721AdapterMock internal cONFTAdapter; + ERC721Mock internal cERC721Mock; + + InspectorMock internal oAppInspector; + + function setUp() public virtual override { + super.setUp(); + + _deployONFTs(); + _wireAndMintInitial(); + } + + /// @dev deploy ONFTs + /// @notice this function should deploy aONFT, bONFT, cONFTAdapter, and cERC721Mock + /// @dev Implementations may override this function to deploy TestableONFT721Mock, which contains additional testing functionality. + function _deployONFTs() internal virtual { + aONFT = ONFT721Mock( + _deployOApp( + type(ONFT721Mock).creationCode, + abi.encode(A_ONFT_NAME, A_ONFT_SYMBOL, address(endpoints[A_EID]), address(this)) + ) + ); + + bONFT = ONFT721Mock( + _deployOApp( + type(ONFT721Mock).creationCode, + abi.encode(B_ONFT_NAME, B_ONFT_SYMBOL, address(endpoints[B_EID]), address(this)) + ) + ); + + cERC721Mock = new ERC721Mock(C_TOKEN_NAME, C_TOKEN_SYMBOL); + cONFTAdapter = ONFT721AdapterMock( + _deployOApp( + type(ONFT721AdapterMock).creationCode, + abi.encode(address(cERC721Mock), address(endpoints[C_EID]), address(this)) + ) + ); + } + + /// @dev the initial number of ONFTS to mint per EID + /// @dev Implementations may override this function to change the number of ONFTs minted per EID. + function _initialNumONFTsPerEID() internal pure virtual returns (uint256) { + return DEFAULT_INITIAL_ONFTS_PER_EID; + } + + /// @dev wire the ONFTs and mint the initial ONFTs + function _wireAndMintInitial() internal { + // wire the onfts + onfts = new address[](3); + uint256 onftIndex = 0; + onfts[onftIndex++] = address(aONFT); + onfts[onftIndex++] = address(bONFT); + onfts[onftIndex++] = address(cONFTAdapter); + wireOApps(onfts); + + _mintOnAdapter(); + _distributeAcrossMesh(); + } + + /// @dev mint ONFTs on the adapter + function _mintOnAdapter() internal { + uint256 numONFTsPerEID = _initialNumONFTsPerEID(); + for (uint256 i = 0; i < numONFTsPerEID * 3; i++) { + cERC721Mock.mint(charlie, i); + } + } + + /// @dev distribute ONFTs across the mesh, giving alice 256 on A_EID and bob 256 on B_EID. + function _distributeAcrossMesh() internal { + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200_000, 0); + uint256 numONFTsPerEID = _initialNumONFTsPerEID(); + for (uint256 i = 0; i < numONFTsPerEID; i++) { + vm.startPrank(charlie); + IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), i); + vm.stopPrank(); + _sendAndCheck(i, C_EID, A_EID, charlie, alice, options, numONFTsPerEID * 3 - i, i, true, false); + } + for (uint256 i = numONFTsPerEID; i < numONFTsPerEID * 2; i++) { + vm.startPrank(charlie); + IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), i); + vm.stopPrank(); + _sendAndCheck( + i, + C_EID, + B_EID, + charlie, + bob, + options, + numONFTsPerEID * 3 - i, + i - numONFTsPerEID, + true, + false + ); + } + } + + function _sendAndCheck( + uint256 _tokenToSend, + uint32 _srcEid, + uint32 _dstEid, + address _from, + address _to, + uint256 _srcCount, + uint256 _dstCount, + bool _srcIsAdapter, + bool _dstIsAdapter + ) internal { + _sendAndCheck( + _tokenToSend, + _srcEid, + _dstEid, + _from, + _to, + "", + _srcCount, + _dstCount, + _srcIsAdapter, + _dstIsAdapter + ); + } + + function _sendAndCheck( + uint256 _tokenToSend, + uint32 _srcEid, + uint32 _dstEid, + address _from, + address _to, + bytes memory _options, + uint256 _srcCount, + uint256 _dstCount, + bool _srcIsAdapter, + bool _dstIsAdapter + ) internal { + SendParam memory sendParam = SendParam(_dstEid, addressToBytes32(_to), _tokenToSend, _options, "", ""); + MessagingFee memory fee = IONFT721(onfts[_srcEid - 1]).quoteSend(sendParam, false); + + vm.prank(_from); + IONFT721(onfts[_srcEid - 1]).send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + verifyPackets(_dstEid, addressToBytes32(address(onfts[_dstEid - 1]))); + + assertEq( + IERC721(!_srcIsAdapter ? onfts[_srcEid - 1] : ONFT721Adapter(onfts[_srcEid - 1]).token()).balanceOf(_from), + _srcCount - 1 + ); + assertEq( + IERC721(!_dstIsAdapter ? onfts[_dstEid - 1] : ONFT721Adapter(onfts[_dstEid - 1]).token()).balanceOf(_to), + _dstCount + 1 + ); + assertEq( + IERC721(!_dstIsAdapter ? onfts[_dstEid - 1] : ONFT721Adapter(onfts[_dstEid - 1]).token()).ownerOf( + _tokenToSend + ), + _to + ); + } + + function _setMeshDefaultEnforcedSendOption() internal { + for (uint32 i = 0; i <= 3; i++) { + _setDefaultEnforcedSendOption(address(aONFT), i); + _setDefaultEnforcedSendOption(address(bONFT), i); + _setDefaultEnforcedSendOption(address(cONFTAdapter), i); + } + } + + function _setDefaultEnforcedSendOption(address _onft, uint32 _eid) internal { + _setEnforcedSendOption(_onft, _eid, 200_000); + } + + function _setEnforcedSendOption(address _onft, uint32 _eid, uint128 _gas) internal { + _setEnforcedOption(_onft, _eid, 1, OptionsBuilder.newOptions().addExecutorLzReceiveOption(_gas, 0)); + } + + function _setEnforcedOption(address _onft, uint32 _eid, uint16 _optionId, bytes memory _options) internal { + EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](1); + enforcedOptions[0] = EnforcedOptionParam(_eid, _optionId, _options); + OAppOptionsType3(_onft).setEnforcedOptions(enforcedOptions); + } +} diff --git a/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol b/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol new file mode 100644 index 000000000..6854bc934 --- /dev/null +++ b/packages/onft-evm/test/onft/onft721/mocks/ERC721Mock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract ERC721Mock is ERC721 { + constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} + + function mint(address _to, uint256 _tokenId) public { + _mint(_to, _tokenId); + } +} diff --git a/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol b/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol new file mode 100644 index 000000000..e04efddff --- /dev/null +++ b/packages/onft-evm/test/onft/onft721/mocks/ONFT721AdapterMock.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { ONFT721Adapter } from "../../../../contracts/onft721/ONFT721Adapter.sol"; + +contract ONFT721AdapterMock is ONFT721Adapter { + constructor( + address _token, + address _lzEndpoint, + address _delegate + ) ONFT721Adapter(_token, _lzEndpoint, _delegate) {} + + function debit(uint256 _tokenId, uint32 _dstEid) public { + _debit(msg.sender, _tokenId, _dstEid); + } + + function credit(address _to, uint256 _tokenId, uint32 _srcEid) public { + _credit(_to, _tokenId, _srcEid); + } +} diff --git a/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol b/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol new file mode 100644 index 000000000..7f8624489 --- /dev/null +++ b/packages/onft-evm/test/onft/onft721/mocks/ONFT721Mock.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.22; + +import { ONFT721 } from "../../../../contracts/onft721/ONFT721.sol"; +import { SendParam } from "../../../../contracts/onft721/ONFT721Core.sol"; + +contract ONFT721Mock is ONFT721 { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _tokenId) public { + _mint(_to, _tokenId); + } + + function debit(uint256 _tokenId, uint32 _dstEid) public { + _debit(msg.sender, _tokenId, _dstEid); + } + + function exists(uint256 _tokenId) public view returns (bool) { + return _ownerOf(_tokenId) != address(0); + } + + function credit(address _toAddress, uint256 _tokenId, uint32 _srcEid) public { + _credit(_toAddress, _tokenId, _srcEid); + } + + function buildMsgAndOptions(SendParam calldata _sendParam) public view returns (bytes memory, bytes memory) { + return _buildMsgAndOptions(_sendParam); + } +} diff --git a/packages/onft-evm/tsconfig.json b/packages/onft-evm/tsconfig.json new file mode 100644 index 000000000..027ad0f3f --- /dev/null +++ b/packages/onft-evm/tsconfig.json @@ -0,0 +1,13 @@ +{ + "exclude": ["node_modules"], + "include": ["deploy", "tasks", "test", "hardhat.config.ts"], + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +} diff --git a/packages/onft-evm/turbo.json b/packages/onft-evm/turbo.json new file mode 100644 index 000000000..d663f5d46 --- /dev/null +++ b/packages/onft-evm/turbo.json @@ -0,0 +1,15 @@ +{ + "extends": ["//"], + "pipeline": { + "build": { + "outputs": [ + "dist/**", + "artifacts*/**", + "hh-cache*/**", + "cache*/**", + "src/typechain-types/**", + "out/**" + ] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 720223c1d..5eeae8340 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -286,6 +286,114 @@ importers: specifier: ^5.3.3 version: 5.3.3 + examples/onft721: + devDependencies: + '@babel/core': + specifier: ^7.23.9 + version: 7.23.9 + '@layerzerolabs/eslint-config-next': + specifier: ~2.3.3 + version: 2.3.3(typescript@5.4.5) + '@layerzerolabs/lz-definitions': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/lz-evm-messagelib-v2': + specifier: ^2.3.25 + version: 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-oapp-v2': + specifier: ^2.3.25 + version: 2.3.25(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': + specifier: ^2.3.25 + version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': + specifier: ^2.3.25 + version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) + '@layerzerolabs/lz-v2-utilities': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/onft-evm': + specifier: 0.0.1 + version: link:../../packages/onft-evm + '@layerzerolabs/prettier-config-next': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/solhint-config': + specifier: ^2.3.3 + version: 2.3.3(typescript@5.4.5) + '@layerzerolabs/test-devtools-evm-foundry': + specifier: ~0.2.7 + version: link:../../packages/test-devtools-evm-foundry + '@layerzerolabs/toolbox-foundry': + specifier: ~0.1.7 + version: link:../../packages/toolbox-foundry + '@layerzerolabs/toolbox-hardhat': + specifier: ~0.2.35 + version: link:../../packages/toolbox-hardhat + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@5.7.2)(hardhat@2.22.3) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.3) + '@openzeppelin/contracts': + specifier: ^5.0.1 + version: 5.0.2 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.1 + version: 5.0.2(@openzeppelin/contracts@5.0.2) + '@rushstack/eslint-patch': + specifier: ^1.7.0 + version: 1.7.0 + '@types/chai': + specifier: ^4.3.11 + version: 4.3.11 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ~18.18.14 + version: 18.18.14 + chai: + specifier: ^4.4.1 + version: 4.4.1 + dotenv: + specifier: ^16.4.1 + version: 16.4.5 + eslint-plugin-jest-extended: + specifier: ~2.0.0 + version: 2.0.0(eslint@8.57.0)(typescript@5.4.5) + ethers: + specifier: ^5.7.2 + version: 5.7.2 + hardhat: + specifier: ^2.22.3 + version: 2.22.3(ts-node@10.9.2)(typescript@5.4.5) + hardhat-contract-sizer: + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.22.3) + hardhat-deploy: + specifier: ^0.12.1 + version: 0.12.4 + mocha: + specifier: ^10.2.0 + version: 10.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + solhint: + specifier: ^4.1.1 + version: 4.1.1(typescript@5.4.5) + solidity-bytes-utils: + specifier: ^0.8.2 + version: 0.8.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.14)(typescript@5.4.5) + typescript: + specifier: ^5.3.3 + version: 5.4.5 + packages/build-lz-options: dependencies: yoga-layout-prebuilt: @@ -937,6 +1045,114 @@ importers: specifier: ^3.22.4 version: 3.22.4 + packages/onft-evm: + devDependencies: + '@babel/core': + specifier: ^7.23.9 + version: 7.23.9 + '@layerzerolabs/eslint-config-next': + specifier: ~2.3.3 + version: 2.3.3(typescript@5.4.5) + '@layerzerolabs/lz-definitions': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/lz-evm-messagelib-v2': + specifier: ^2.3.25 + version: 2.3.25(@axelar-network/axelar-gmp-sdk-solidity@5.9.0)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-oapp-v2': + specifier: ^2.3.25 + version: 2.3.25(@layerzerolabs/lz-evm-messagelib-v2@2.3.25)(@layerzerolabs/lz-evm-protocol-v2@2.3.25)(@layerzerolabs/lz-evm-v1-0.7@2.3.25)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': + specifier: ^2.3.25 + version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': + specifier: ^2.3.25 + version: 2.3.25(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.12.4) + '@layerzerolabs/lz-v2-utilities': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/prettier-config-next': + specifier: ^2.3.25 + version: 2.3.25 + '@layerzerolabs/solhint-config': + specifier: ^2.3.3 + version: 2.3.3(typescript@5.4.5) + '@layerzerolabs/test-devtools-evm-foundry': + specifier: ~0.2.7 + version: link:../test-devtools-evm-foundry + '@layerzerolabs/toolbox-foundry': + specifier: ~0.1.7 + version: link:../toolbox-foundry + '@layerzerolabs/toolbox-hardhat': + specifier: ~0.2.35 + version: link:../toolbox-hardhat + '@nomicfoundation/hardhat-ethers': + specifier: ^3.0.5 + version: 3.0.5(ethers@5.7.2)(hardhat@2.22.3) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2)(hardhat@2.22.3) + '@openzeppelin/contracts': + specifier: ^5.0.2 + version: 5.0.2 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.2 + version: 5.0.2(@openzeppelin/contracts@5.0.2) + '@rushstack/eslint-patch': + specifier: ^1.7.0 + version: 1.7.0 + '@types/chai': + specifier: ^4.3.11 + version: 4.3.11 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ~18.18.14 + version: 18.18.14 + chai: + specifier: ^4.4.1 + version: 4.4.1 + dotenv: + specifier: ^16.4.1 + version: 16.4.5 + erc721a: + specifier: ^4.3.0 + version: 4.3.0 + eslint-plugin-jest-extended: + specifier: ~2.0.0 + version: 2.0.0(eslint@8.57.0)(typescript@5.4.5) + ethers: + specifier: ^5.7.2 + version: 5.7.2 + hardhat: + specifier: ^2.22.3 + version: 2.22.3(ts-node@10.9.2)(typescript@5.4.5) + hardhat-contract-sizer: + specifier: ^2.10.0 + version: 2.10.0(hardhat@2.22.3) + hardhat-deploy: + specifier: ^0.12.1 + version: 0.12.4 + mocha: + specifier: ^10.2.0 + version: 10.2.0 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + solhint: + specifier: ^4.1.1 + version: 4.1.1(typescript@5.4.5) + solidity-bytes-utils: + specifier: ^0.8.2 + version: 0.8.2 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.18.14)(typescript@5.4.5) + typescript: + specifier: ^5.3.3 + version: 5.4.5 + packages/protocol-devtools: devDependencies: '@layerzerolabs/devtools': @@ -3907,6 +4123,26 @@ packages: - typescript dev: true + /@layerzerolabs/eslint-config-next@2.3.3(typescript@5.4.5): + resolution: {integrity: sha512-zeq0LZkSPI2tm6M1fRmmPA6gzTAUg3rux074FV+8N+PyC53/DhPUHT/DYvTHl0/+F04nFp8MD+yYSHReVIQLNQ==} + dependencies: + '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-prettier: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) + eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0) + prettier: 3.2.5 + transitivePeerDependencies: + - '@types/eslint' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + - typescript + dev: true + /@layerzerolabs/evm-sdks-core@2.3.25: resolution: {integrity: sha512-2qUeEVMnYFnj3MrEWGmrXKn249ohfvjSyDsjivJJbwu9rlxsk/zclb+May1SCO0UcbPfgILVkldZoL4LyqrwnA==} dependencies: @@ -4339,6 +4575,14 @@ packages: - typescript dev: true + /@layerzerolabs/solhint-config@2.3.3(typescript@5.4.5): + resolution: {integrity: sha512-sL2W+kbZRPFKnLDn8A5hQX796gFi8sEVJFRkQbbpFrtB/KGCPpWK/w8JjU7OoOzOvJF9pQnJGS1ayUjDJKCb0Q==} + dependencies: + solhint: 4.1.1(typescript@5.4.5) + transitivePeerDependencies: + - typescript + dev: true + /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: @@ -5439,6 +5683,35 @@ packages: - supports-color dev: true + /@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-KwfdWXJBOviaBVhxO3p5TJiLpNuh2iyXyjmWN0f1nU87pwyvfS0EmjC6ukQVYVFJd/K1+0NWGPDXiyEyQorn0Q==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.7.1 + '@typescript-eslint/type-utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.7.1 + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3): resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5481,6 +5754,27 @@ packages: - supports-color dev: true + /@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-vmPzBOOtz48F6JAGVS/kZYk4EkXao6iGrD838sp1w3NQQC0W8ry/q641KU4PrG7AKNAf56NOcR8GOpH8l9FPCw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.7.1 + '@typescript-eslint/types': 7.7.1 + '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.7.1 + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager@5.62.0: resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5525,7 +5819,27 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.3.3): + /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.3.3): + resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.3.3) + '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.3.3) + debug: 4.3.4(supports-color@8.1.1) + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/type-utils@7.7.1(eslint@8.57.0)(typescript@5.4.5): resolution: {integrity: sha512-ZksJLW3WF7o75zaBPScdW1Gbkwhd/lyeXGf1kQCxJaOeITscoSl0MjynVvCzuV5boUz/3fOI06Lz8La55mu29Q==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -5535,12 +5849,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.3.3) - '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4(supports-color@8.1.1) eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@5.3.3) - typescript: 5.3.3 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true @@ -5581,6 +5895,27 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.0 + tsutils: 3.21.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3): resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5646,6 +5981,28 @@ packages: - supports-color dev: true + /@typescript-eslint/typescript-estree@7.7.1(typescript@5.4.5): + resolution: {integrity: sha512-CXe0JHCXru8Fa36dteXqmH2YxngKJjkQLjxzoj6LYwzZ7qZvgsLSc+eqItCrqIop8Vl2UKoAi0StVWu97FQZIQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.7.1 + '@typescript-eslint/visitor-keys': 7.7.1 + debug: 4.3.4(supports-color@8.1.1) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/utils@5.62.0(eslint@8.56.0)(typescript@5.5.3): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5686,6 +6043,26 @@ packages: - typescript dev: true + /@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) + eslint: 8.57.0 + eslint-scope: 5.1.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/utils@6.21.0(eslint@8.56.0)(typescript@5.5.3): resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} engines: {node: ^16.0.0 || >=18.0.0} @@ -5724,6 +6101,25 @@ packages: - typescript dev: true + /@typescript-eslint/utils@7.7.1(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-QUvBxPEaBXf41ZBbaidKICgVL8Hin0p6prQDu6bbetWo39BKbWJxRsErOzMNT1rXvTll+J7ChrbmMCXM9rsvOQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.7.1 + '@typescript-eslint/types': 7.7.1 + '@typescript-eslint/typescript-estree': 7.7.1(typescript@5.4.5) + eslint: 8.57.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@typescript-eslint/visitor-keys@5.62.0: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6848,6 +7244,22 @@ packages: typescript: 5.3.3 dev: true + /cosmiconfig@8.3.6(typescript@5.4.5): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + typescript: 5.4.5 + dev: true + /crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -7285,6 +7697,10 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + /erc721a@4.3.0: + resolution: {integrity: sha512-JneTLVEH5I/rJMArAwwJ26CX4HvL3ql9Q3egpQ4QB7sz7vIGZbMdMmqHLBIIKhBlfToZKewtC7OZtdr3qU6jsA==} + dev: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -7562,7 +7978,7 @@ packages: debug: 4.3.4(supports-color@8.1.1) enhanced-resolve: 5.16.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 @@ -7604,37 +8020,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.3.3) - debug: 3.2.7 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + /eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): resolution: {integrity: sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==} engines: {node: '>=4'} peerDependencies: @@ -7658,6 +8044,7 @@ packages: '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.3.3) debug: 3.2.7 eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.1)(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -7740,8 +8127,8 @@ packages: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - hasown: 2.0.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + hasown: 2.0.1 is-core-module: 2.13.1 is-glob: 4.0.3 minimatch: 3.1.2 @@ -7769,6 +8156,19 @@ packages: - typescript dev: true + /eslint-plugin-jest-extended@2.0.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-nMhVVsVcG/+Q6FMshql35WBxwx8xlBhxKgAG08WP3BYWfXrp28oxLpJVu9JSbMpfmfKGVrHwMYJGfPLRKlGB8w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /eslint-plugin-jest@27.6.3(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(typescript@5.5.3): resolution: {integrity: sha512-+YsJFVH6R+tOiO3gCJon5oqn4KWc+mDq2leudk8mrp8RFubLOo9CVyi3cib4L7XMpxExmkmBZQTPDYVBzgpgOA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8263,6 +8663,7 @@ packages: dependencies: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 + bundledDependencies: false /eventemitter3@4.0.4: resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} @@ -9246,6 +9647,70 @@ packages: - utf-8-validate dev: true + /hardhat@2.22.3(ts-node@10.9.2)(typescript@5.4.5): + resolution: {integrity: sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA==} + hasBin: true + peerDependencies: + ts-node: '*' + typescript: '*' + peerDependenciesMeta: + ts-node: + optional: true + typescript: + optional: true + dependencies: + '@ethersproject/abi': 5.7.0 + '@metamask/eth-sig-util': 4.0.1 + '@nomicfoundation/edr': 0.3.5 + '@nomicfoundation/ethereumjs-common': 4.0.4 + '@nomicfoundation/ethereumjs-tx': 5.0.4 + '@nomicfoundation/ethereumjs-util': 9.0.4 + '@nomicfoundation/solidity-analyzer': 0.1.1 + '@sentry/node': 5.30.0 + '@types/bn.js': 5.1.5 + '@types/lru-cache': 5.1.1 + adm-zip: 0.4.16 + aggregate-error: 3.1.0 + ansi-escapes: 4.3.2 + boxen: 5.1.2 + chalk: 2.4.2 + chokidar: 3.6.0 + ci-info: 2.0.0 + debug: 4.3.4(supports-color@8.1.1) + enquirer: 2.4.1 + env-paths: 2.2.1 + ethereum-cryptography: 1.2.0 + ethereumjs-abi: 0.6.8 + find-up: 2.1.0 + fp-ts: 1.19.3 + fs-extra: 7.0.1 + glob: 7.2.0 + immutable: 4.3.4 + io-ts: 1.10.4 + keccak: 3.0.4 + lodash: 4.17.21 + mnemonist: 0.38.5 + mocha: 10.2.0 + p-map: 4.0.0 + raw-body: 2.5.2 + resolve: 1.17.0 + semver: 6.3.1 + solc: 0.7.3(debug@4.3.4) + source-map-support: 0.5.21 + stacktrace-parser: 0.1.10 + ts-node: 10.9.2(@types/node@18.18.14)(typescript@5.4.5) + tsort: 0.0.1 + typescript: 5.4.5 + undici: 5.28.2 + uuid: 8.3.2 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - c-kzg + - supports-color + - utf-8-validate + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -12509,7 +12974,35 @@ packages: pluralize: 8.0.0 semver: 7.6.0 strip-ansi: 6.0.1 - table: 6.8.1 + table: 6.8.2 + text-table: 0.2.0 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - typescript + dev: true + + /solhint@4.1.1(typescript@5.4.5): + resolution: {integrity: sha512-7G4iF8H5hKHc0tR+/uyZesSKtfppFIMvPSW+Ku6MSL25oVRuyFeqNhOsXHfkex64wYJyXs4fe+pvhB069I19Tw==} + hasBin: true + dependencies: + '@solidity-parser/parser': 0.16.2 + ajv: 6.12.6 + antlr4: 4.13.1 + ast-parents: 0.0.1 + chalk: 4.1.2 + commander: 10.0.1 + cosmiconfig: 8.3.6(typescript@5.4.5) + fast-diff: 1.3.0 + glob: 8.1.0 + ignore: 5.3.1 + js-yaml: 4.1.0 + latest-version: 7.0.0 + lodash: 4.17.21 + pluralize: 8.0.0 + semver: 7.6.0 + strip-ansi: 6.0.1 + table: 6.8.2 text-table: 0.2.0 optionalDependencies: prettier: 2.8.8 @@ -12861,17 +13354,6 @@ packages: tslib: 2.6.3 dev: true - /table@6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.12.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /table@6.8.2: resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==} engines: {node: '>=10.0.0'} @@ -12881,7 +13363,6 @@ packages: slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: false /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} @@ -13074,6 +13555,15 @@ packages: typescript: 5.3.3 dev: true + /ts-api-utils@1.3.0(typescript@5.4.5): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.4.5 + dev: true + /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true @@ -13361,6 +13851,16 @@ packages: typescript: 5.3.3 dev: true + /tsutils@3.21.0(typescript@5.4.5): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 5.4.5 + dev: true + /tsutils@3.21.0(typescript@5.5.3): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} diff --git a/tests-user/tests/create-lz-oapp.bats b/tests-user/tests/create-lz-oapp.bats index c2a93ca92..df447a1e4 100644 --- a/tests-user/tests/create-lz-oapp.bats +++ b/tests-user/tests/create-lz-oapp.bats @@ -136,6 +136,17 @@ teardown() { pnpm lint:fix } +@test "should work with pnpm & onft721 example in CI mode" { + local DESTINATION="$PROJECTS_DIRECTORY/pnpm-onft721" + + npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager pnpm + cd "$DESTINATION" + pnpm compile + pnpm test + pnpm lint + pnpm lint:fix +} + @test "should work with yarn & oapp example in CI mode" { local DESTINATION="$PROJECTS_DIRECTORY/yarn-oapp" @@ -158,6 +169,17 @@ teardown() { yarn lint:fix } +@test "should work with yarn & onft721 example in CI mode" { + local DESTINATION="$PROJECTS_DIRECTORY/yarn-oapp" + + npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager yarn + cd "$DESTINATION" + yarn compile + yarn test + yarn lint + yarn lint:fix +} + @test "should work with npm & oapp example in CI mode" { local DESTINATION="$PROJECTS_DIRECTORY/npm-oapp" @@ -178,4 +200,15 @@ teardown() { npm run test npm run lint npm run lint:fix -} \ No newline at end of file +} + +@test "should work with npm & onft721 example in CI mode" { + local DESTINATION="$PROJECTS_DIRECTORY/npm-oft" + + npx --yes create-lz-oapp --ci --example onft721 --destination $DESTINATION --package-manager npm + cd "$DESTINATION" + npm run compile + npm run test + npm run lint + npm run lint:fix +}