From a91e60354570ef96748b52b23b6e1c9894dfc3ef Mon Sep 17 00:00:00 2001
From: Shankar
Date: Wed, 20 Nov 2024 22:49:46 +0000
Subject: [PATCH 1/3] feat: examples/oft-alt
Signed-off-by: Shankar
---
examples/oft-alt/.env.example | 15 +
examples/oft-alt/.eslintignore | 10 +
examples/oft-alt/.eslintrc.js | 10 +
examples/oft-alt/.gitignore | 24 +
examples/oft-alt/.nvmrc | 1 +
examples/oft-alt/.prettierignore | 10 +
examples/oft-alt/.prettierrc.js | 3 +
examples/oft-alt/README.md | 532 ++++++++++++++++
.../oft-alt/contracts/MyOFTAdapterAlt.sol | 13 +
examples/oft-alt/contracts/MyOFTAlt.sol | 14 +
.../oft-alt/contracts/mocks/MyOFTAltMock.sol | 18 +
examples/oft-alt/deploy/MyOFTAlt.ts | 53 ++
examples/oft-alt/foundry.toml | 31 +
examples/oft-alt/hardhat.config.ts | 82 +++
examples/oft-alt/layerzero.config.ts | 60 ++
examples/oft-alt/package.json | 78 +++
examples/oft-alt/solhint.config.js | 1 +
examples/oft-alt/test/foundry/MyOFTAlt.t.sol | 601 ++++++++++++++++++
.../test/mocks/MyOFTAdapterAltMock.sol | 39 ++
examples/oft-alt/test/mocks/MyOFTAltMock.sol | 60 ++
examples/oft-alt/tsconfig.json | 13 +
21 files changed, 1668 insertions(+)
create mode 100644 examples/oft-alt/.env.example
create mode 100644 examples/oft-alt/.eslintignore
create mode 100644 examples/oft-alt/.eslintrc.js
create mode 100644 examples/oft-alt/.gitignore
create mode 100644 examples/oft-alt/.nvmrc
create mode 100644 examples/oft-alt/.prettierignore
create mode 100644 examples/oft-alt/.prettierrc.js
create mode 100644 examples/oft-alt/README.md
create mode 100644 examples/oft-alt/contracts/MyOFTAdapterAlt.sol
create mode 100644 examples/oft-alt/contracts/MyOFTAlt.sol
create mode 100644 examples/oft-alt/contracts/mocks/MyOFTAltMock.sol
create mode 100644 examples/oft-alt/deploy/MyOFTAlt.ts
create mode 100644 examples/oft-alt/foundry.toml
create mode 100644 examples/oft-alt/hardhat.config.ts
create mode 100644 examples/oft-alt/layerzero.config.ts
create mode 100644 examples/oft-alt/package.json
create mode 100644 examples/oft-alt/solhint.config.js
create mode 100644 examples/oft-alt/test/foundry/MyOFTAlt.t.sol
create mode 100644 examples/oft-alt/test/mocks/MyOFTAdapterAltMock.sol
create mode 100644 examples/oft-alt/test/mocks/MyOFTAltMock.sol
create mode 100644 examples/oft-alt/tsconfig.json
diff --git a/examples/oft-alt/.env.example b/examples/oft-alt/.env.example
new file mode 100644
index 000000000..197ba1d67
--- /dev/null
+++ b/examples/oft-alt/.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/oft-alt/.eslintignore b/examples/oft-alt/.eslintignore
new file mode 100644
index 000000000..ee9f768fd
--- /dev/null
+++ b/examples/oft-alt/.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/oft-alt/.eslintrc.js b/examples/oft-alt/.eslintrc.js
new file mode 100644
index 000000000..f0ea891fd
--- /dev/null
+++ b/examples/oft-alt/.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/oft-alt/.gitignore b/examples/oft-alt/.gitignore
new file mode 100644
index 000000000..e2face954
--- /dev/null
+++ b/examples/oft-alt/.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/oft-alt/.nvmrc b/examples/oft-alt/.nvmrc
new file mode 100644
index 000000000..b714151ef
--- /dev/null
+++ b/examples/oft-alt/.nvmrc
@@ -0,0 +1 @@
+v18.18.0
\ No newline at end of file
diff --git a/examples/oft-alt/.prettierignore b/examples/oft-alt/.prettierignore
new file mode 100644
index 000000000..6e8232f5a
--- /dev/null
+++ b/examples/oft-alt/.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/oft-alt/.prettierrc.js b/examples/oft-alt/.prettierrc.js
new file mode 100644
index 000000000..6f55b4019
--- /dev/null
+++ b/examples/oft-alt/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('@layerzerolabs/prettier-config-next'),
+};
diff --git a/examples/oft-alt/README.md b/examples/oft-alt/README.md
new file mode 100644
index 000000000..709ae3c7a
--- /dev/null
+++ b/examples/oft-alt/README.md
@@ -0,0 +1,532 @@
+
+
+
+
+
+
+
+ Homepage | Docs | Developers
+
+
+
+Omnichain Fungible Token (OFTAdapterAlt and OFTAlt) Example for Alt Endpoints
+
+# ⚠️ ** This code is currently under audit and should not yet be used in production, the readme might not be complete
+
+
+ Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses | DVN Addresses
+
+
+Template project for getting started with LayerZero's OFTAlt
contract standard.
+
+
+
+This standard works by combining the LayerZero OApp Contract Standard with the ERC20 [`_burn`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L80) method, to initiate omnichain send transfers on the source chain, sending a message via the LayerZero protocol, and delivering a function call to the destination contract to [`_mint`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oft/OFT.sol#L96) the same number of tokens burned, creating a unified supply across all networks connected.
+
+Read more about what you can do with OFTs by reading the [OFT Quickstart](https://docs.layerzero.network/v2/developers/evm/oft/quickstart) in the LayerZero Documentation.
+
+## LayerZero Hardhat Helper Tasks
+
+LayerZero Devtools provides several helper hardhat tasks to easily deploy, verify, configure, connect, and send OFTs cross-chain.
+
+
+ npx hardhat lz:deploy
+
+
+
+Deploys your contract to any of the available networks in your [`hardhat.config.ts`](./hardhat.config.ts) when given a deploy tag (by default contract name) and returns a list of available networks to select for the deployment. For specifics around all deployment options, please refer to the [Deploying Contracts](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/deploying) section of the documentation. LayerZero's `lz:deploy` utilizes `hardhat-deploy`.
+
+```yml
+'arbitrum-sepolia': {
+ eid: EndpointId.ARBSEP_V2_TESTNET,
+ url: process.env.RPC_URL_ARBSEP_TESTNET,
+ accounts,
+},
+'base-sepolia': {
+ eid: EndpointId.BASESEP_V2_TESTNET,
+ url: process.env.RPC_URL_BASE_TESTNET,
+ accounts,
+},
+```
+
+
+
+
+ npx hardhat lz:oapp:config:init --oapp-config YOUR_OAPP_CONFIG --contract-name CONTRACT_NAME
+
+
+
+Initializes a `layerzero.config.ts` file for all available pathways between your hardhat networks with the current LayerZero default placeholder settings. This task can be incredibly useful for correctly formatting your config file.
+
+You can run this task by providing the `contract-name` you want to set for the config and `file-name` you want to generate:
+
+```bash
+npx hardhat lz:oapp:config:init --contract-name CONTRACT_NAME --oapp-config FILE_NAME
+```
+
+This will create a `layerzero.config.ts` in your working directory populated with your contract name and connections for every pathway possible between your hardhat networks:
+
+```yml
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+
+const arbsepContract = {
+ eid: EndpointId.ARBSEP_V2_TESTNET,
+ contractName: 'MyOFT',
+}
+const sepoliaContract = {
+ eid: EndpointId.SEPOLIA_V2_TESTNET,
+ contractName: 'MyOFT',
+}
+
+export default {
+ contracts: [{ contract: arbsepContract }, { contract: sepoliaContract }],
+ connections: [
+ {
+ from: arbsepContract,
+ to: sepoliaContract,
+ config: {
+ sendLibrary: '0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E',
+ receiveLibraryConfig: { receiveLibrary: '0x75Db67CDab2824970131D5aa9CECfC9F69c69636', gracePeriod: 0 },
+ sendConfig: {
+ executorConfig: { maxMessageSize: 10000, executor: '0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897' },
+ ulnConfig: {
+ confirmations: 1,
+ requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'],
+ optionalDVNs: [],
+ optionalDVNThreshold: 0,
+ },
+ },
+ // receiveConfig: {
+ // ulnConfig: {
+ // confirmations: 2,
+ // requiredDVNs: ['0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8'],
+ // optionalDVNs: [],
+ // optionalDVNThreshold: 0,
+ // },
+ // },
+ },
+ },
+ {
+ from: sepoliaContract,
+ to: arbsepContract,
+ config: {
+ sendLibrary: '0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE',
+ receiveLibraryConfig: { receiveLibrary: '0xdAf00F5eE2158dD58E0d3857851c432E34A3A851', gracePeriod: 0 },
+ // sendConfig: {
+ // executorConfig: { maxMessageSize: 10000, executor: '0x718B92b5CB0a5552039B593faF724D182A881eDA' },
+ // ulnConfig: {
+ // confirmations: 2,
+ // requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'],
+ // optionalDVNs: [],
+ // optionalDVNThreshold: 0,
+ // },
+ // },
+ receiveConfig: {
+ ulnConfig: {
+ confirmations: 1,
+ requiredDVNs: ['0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193'],
+ optionalDVNs: [],
+ optionalDVNThreshold: 0,
+ },
+ },
+ },
+ },
+ ],
+}
+```
+
+
+
+
+ npx hardhat lz:oapp:config:wire --oapp-config YOUR_OAPP_CONFIG
+
+
+
+Calls the configuration functions between your deployed OApp contracts on every chain based on the provided `layerzero.config.ts`.
+
+Running `lz:oapp:wire` will make the following function calls per pathway connection for a fully defined config file using your specified settings and your environment variables (Private Keys and RPCs):
+
+- function setPeer(uint32 \_eid, bytes32 \_peer) public virtual onlyOwner {}
+
+- function setConfig(address \_oapp, address \_lib, SetConfigParam[] calldata \_params) external onlyRegistered(\_lib) {}
+
+- function setEnforcedOptions(EnforcedOptionParam[] calldata \_enforcedOptions) public virtual onlyOwner {}
+
+- function setSendLibrary(address \_oapp, uint32 \_eid, address \_newLib) external onlyRegisteredOrDefault(\_newLib) isSendLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {}
+
+- function setReceiveLibrary(address \_oapp, uint32 \_eid, address \_newLib, uint256 \_gracePeriod) external onlyRegisteredOrDefault(\_newLib) isReceiveLib(\_newLib) onlySupportedEid(\_newLib, \_eid) {}
+
+To use this task, run:
+
+```bash
+npx hardhat lz:oapp:wire --oapp-config YOUR_LAYERZERO_CONFIG_FILE
+```
+
+Whenever you make changes to the configuration, run `lz:oapp:wire` again. The task will check your current configuration, and only apply NEW changes.
+
+To use a Gnosis Safe multisig as the signer for these transactions, add the following to each network in your `hardhat.config.ts` and add the `--safe` flag to `lz:oapp:wire --safe`:
+
+```yml
+// hardhat.config.ts
+
+networks: {
+ // Include configurations for other networks as needed
+ fuji: {
+ /* ... */
+ // Network-specific settings
+ safeConfig: {
+ safeUrl: 'http://something', // URL of the Safe API, not the Safe itself
+ safeAddress: 'address'
+ }
+ }
+}
+```
+
+
+
+ npx hardhat lz:oapp:config:get --oapp-config YOUR_OAPP_CONFIG
+
+
+
+Returns your current OApp's configuration for each chain and pathway in 3 columns:
+
+- **Custom Configuration**: the changes that your `layerzero.config.ts` currently has set
+
+- **Default Configuration**: the default placeholder configuration that LayerZero provides
+
+- **Active Configuration**: the active configuration that applies to the message pathway (Defaults + Custom Values)
+
+If you do NOT explicitly set each configuration parameter, your OApp will fallback to the placeholder parameters in the default config.
+
+```bash
+┌────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐
+│ │ Custom OApp Config │ Default OApp Config │ Active OApp Config │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ localNetworkName │ arbsep │ arbsep │ arbsep │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ remoteNetworkName │ sepolia │ sepolia │ sepolia │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ sendLibrary │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │ 0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ receiveLibrary │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │ 0x75Db67CDab2824970131D5aa9CECfC9F69c69636 │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ sendUlnConfig │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
+│ │ │ confirmations │ 1 │ │ │ confirmations │ 1 │ │ │ confirmations │ 1 │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │
+│ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │
+│ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │
+│ │ │ │ │ │ │ │ │ │ │ │ │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │
+│ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │
+│ │ │ │ │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ sendExecutorConfig │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ │ ┌────────────────┬────────────────────────────────────────────┐ │
+│ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │ │ executor │ 0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897 │ │
+│ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ │ ├────────────────┼────────────────────────────────────────────┤ │
+│ │ │ maxMessageSize │ 10000 │ │ │ maxMessageSize │ 10000 │ │ │ maxMessageSize │ 10000 │ │
+│ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │ └────────────────┴────────────────────────────────────────────┘ │
+│ │ │ │ │
+├────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤
+│ receiveUlnConfig │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
+│ │ │ confirmations │ 2 │ │ │ confirmations │ 2 │ │ │ confirmations │ 2 │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │ │ requiredDVNs │ ┌───┬────────────────────────────────────────────┐ │ │
+│ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │ │ │ │ 0 │ 0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8 │ │ │
+│ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │ │ │ └───┴────────────────────────────────────────────┘ │ │
+│ │ │ │ │ │ │ │ │ │ │ │ │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │ │ optionalDVNs │ │ │
+│ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │ ├──────────────────────┼────────────────────────────────────────────────────┤ │
+│ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │ │ optionalDVNThreshold │ 0 │ │
+│ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │ └──────────────────────┴────────────────────────────────────────────────────┘ │
+│ │ │ │ │
+└────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘
+```
+
+
+
+ npx hardhat lz:oapp:config:get:executor --oapp-config YOUR_OAPP_CONFIG
+
+
+
+Returns the LayerZero Executor config for each network in your `hardhat.config.ts`. You can use this method to see the max destination gas in wei (`nativeCap`) you can request in your [`execution options`](https://docs.layerzero.network/v2/developers/evm/gas-settings/options).
+
+```bash
+┌───────────────────┬────────────────────────────────────────────┐
+│ localNetworkName │ mantle │
+├───────────────────┼────────────────────────────────────────────┤
+│ remoteNetworkName │ polygon │
+├───────────────────┼────────────────────────────────────────────┤
+│ executorDstConfig │ ┌────────────────┬───────────────────────┐ │
+│ │ │ baseGas │ 85000 │ │
+│ │ ├────────────────┼───────────────────────┤ │
+│ │ │ multiplierBps │ 12000 │ │
+│ │ ├────────────────┼───────────────────────┤ │
+│ │ │ floorMarginUSD │ 5000000000000000000 │ │
+│ │ ├────────────────┼───────────────────────┤ │
+│ │ │ nativeCap │ 681000000000000000000 │ │
+│ │ └────────────────┴───────────────────────┘ │
+│ │ │
+└───────────────────┴────────────────────────────────────────────┘
+```
+
+
+
+## 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"
+```
+
+## 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.
+
+
+
+## Connecting Contracts
+
+### Ethereum Configurations
+
+Fill out your `layerzero.config.ts` with the contracts you want to connect. You can generate the default config file for your declared hardhat networks by running:
+
+```bash
+npx hardhat lz:oapp:config:init --contract-name [YOUR_CONTRACT_NAME] --oapp-config [CONFIG_NAME]
+```
+
+> [!NOTE]
+> You may need to change the contract name if you're deploying multiple OApp contracts on different chains (e.g., OFT and OFT Adapter).
+
+
+
+```typescript
+const ethereumContract: OmniPointHardhat = {
+ eid: EndpointId.ETHEREUM_V2_MAINNET,
+ contractName: "MyOFTAdapter",
+};
+
+const arbitrumContract: OmniPointHardhat = {
+ eid: EndpointId.ARBITRUM_V2_MAINNET,
+ contractName: "MyOFT",
+};
+```
+
+Then define the pathway you want to create from and to each contract:
+
+```typescript
+connections: [
+ // ETH <--> ARB PATHWAY: START
+ {
+ from: ethereumContract,
+ to: arbitrumContract,
+ },
+ {
+ from: arbitrumContract,
+ to: ethereumContract,
+ },
+ // ETH <--> ARB PATHWAY: END
+];
+```
+
+Finally, define the config settings for each direction of the pathway:
+
+```typescript
+connections: [
+ // ETH <--> ARB PATHWAY: START
+ {
+ from: ethereumContract,
+ to: arbitrumContract,
+ config: {
+ sendLibrary: contractsConfig.ethereum.sendLib302,
+ receiveLibraryConfig: {
+ receiveLibrary: contractsConfig.ethereum.receiveLib302,
+ gracePeriod: BigInt(0),
+ },
+ // Optional Receive Library Timeout for when the Old Receive Library Address will no longer be valid
+ receiveLibraryTimeoutConfig: {
+ lib: "0x0000000000000000000000000000000000000000",
+ expiry: BigInt(0),
+ },
+ // Optional Send Configuration
+ // @dev Controls how the `from` chain sends messages to the `to` chain.
+ sendConfig: {
+ executorConfig: {
+ maxMessageSize: 10000,
+ // The configured Executor address
+ executor: contractsConfig.ethereum.executor,
+ },
+ ulnConfig: {
+ // The number of block confirmations to wait on BSC before emitting the message from the source chain.
+ confirmations: BigInt(15),
+ // The address of the DVNs you will pay to verify a sent message on the source chain ).
+ // The destination tx will wait until ALL `requiredDVNs` verify the message.
+ requiredDVNs: [
+ contractsConfig.ethereum.horizenDVN, // Horizen
+ contractsConfig.ethereum.polyhedraDVN, // Polyhedra
+ contractsConfig.ethereum.animocaBlockdaemonDVN, // Animoca-Blockdaemon (only available on ETH <-> Arbitrum One)
+ contractsConfig.ethereum.lzDVN, // LayerZero Labs
+ ],
+ // The address of the DVNs you will pay to verify a sent message on the source chain ).
+ // The destination tx will wait until the configured threshold of `optionalDVNs` verify a message.
+ optionalDVNs: [],
+ // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
+ optionalDVNThreshold: 0,
+ },
+ },
+ // Optional Receive Configuration
+ // @dev Controls how the `from` chain receives messages from the `to` chain.
+ receiveConfig: {
+ ulnConfig: {
+ // The number of block confirmations to expect from the `to` chain.
+ confirmations: BigInt(20),
+ // The address of the DVNs your `receiveConfig` expects to receive verifications from on the `from` chain ).
+ // The `from` chain's OApp will wait until the configured threshold of `requiredDVNs` verify the message.
+ requiredDVNs: [
+ contractsConfig.ethereum.lzDVN, // LayerZero Labs DVN
+ contractsConfig.ethereum.animocaBlockdaemonDVN, // Blockdaemon-Animoca
+ contractsConfig.ethereum.horizenDVN, // Horizen Labs
+ contractsConfig.ethereum.polyhedraDVN, // Polyhedra
+ ],
+ // The address of the `optionalDVNs` you expect to receive verifications from on the `from` chain ).
+ // The destination tx will wait until the configured threshold of `optionalDVNs` verify the message.
+ optionalDVNs: [],
+ // The number of `optionalDVNs` that need to successfully verify the message for it to be considered Verified.
+ optionalDVNThreshold: 0,
+ },
+ },
+ // Optional Enforced Options Configuration
+ // @dev Controls how much gas to use on the `to` chain, which the user pays for on the source `from` chain.
+ enforcedOptions: [
+ {
+ msgType: 1,
+ optionType: ExecutorOptionType.LZ_RECEIVE,
+ gas: 65000,
+ value: 0,
+ },
+ {
+ msgType: 2,
+ optionType: ExecutorOptionType.LZ_RECEIVE,
+ gas: 65000,
+ value: 0,
+ },
+ {
+ msgType: 2,
+ optionType: ExecutorOptionType.COMPOSE,
+ index: 0,
+ gas: 50000,
+ value: 0,
+ },
+ ],
+ },
+ },
+ {
+ from: arbitrumContract,
+ to: ethereumContract,
+ },
+ // ETH <--> ARB PATHWAY: END
+];
+```
+
+To set these config settings, run:
+
+```bash
+npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts
+```
+
+
+ Join our community on Discord | Follow us on Twitter
+
diff --git a/examples/oft-alt/contracts/MyOFTAdapterAlt.sol b/examples/oft-alt/contracts/MyOFTAdapterAlt.sol
new file mode 100644
index 000000000..5444b0ef1
--- /dev/null
+++ b/examples/oft-alt/contracts/MyOFTAdapterAlt.sol
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+import { OFTAdapterAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAdapterAlt.sol";
+
+contract MyOFTAdapterAlt is OFTAdapterAlt {
+ constructor(
+ address _address,
+ address _lzEndpoint,
+ address _delegate
+ ) OFTAdapterAlt(_address, _lzEndpoint, _delegate) Ownable(_delegate) {}
+}
diff --git a/examples/oft-alt/contracts/MyOFTAlt.sol b/examples/oft-alt/contracts/MyOFTAlt.sol
new file mode 100644
index 000000000..8e6b6b2f9
--- /dev/null
+++ b/examples/oft-alt/contracts/MyOFTAlt.sol
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+import { OFTAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAlt.sol";
+
+contract MyOFTAlt is OFTAlt {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) OFTAlt(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
+}
diff --git a/examples/oft-alt/contracts/mocks/MyOFTAltMock.sol b/examples/oft-alt/contracts/mocks/MyOFTAltMock.sol
new file mode 100644
index 000000000..388c62491
--- /dev/null
+++ b/examples/oft-alt/contracts/mocks/MyOFTAltMock.sol
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.22;
+
+import { MyOFTAlt } from "../MyOFTAlt.sol";
+
+// @dev WARNING: This is for testing purposes only
+contract MyOFTAltMock is MyOFTAlt {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) MyOFTAlt(_name, _symbol, _lzEndpoint, _delegate) {}
+
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+}
diff --git a/examples/oft-alt/deploy/MyOFTAlt.ts b/examples/oft-alt/deploy/MyOFTAlt.ts
new file mode 100644
index 000000000..22f925b56
--- /dev/null
+++ b/examples/oft-alt/deploy/MyOFTAlt.ts
@@ -0,0 +1,53 @@
+import assert from 'assert'
+
+import { type DeployFunction } from 'hardhat-deploy/types'
+
+const contractName = 'MyOFTAlt'
+
+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: [
+ 'MyOFTAlt', // name
+ 'MOFTAlt', // 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/oft-alt/foundry.toml b/examples/oft-alt/foundry.toml
new file mode 100644
index 000000000..2b46113c9
--- /dev/null
+++ b/examples/oft-alt/foundry.toml
@@ -0,0 +1,31 @@
+[profile.default]
+solc-version = '0.8.22'
+src = 'contracts'
+out = 'out'
+test = 'test/foundry'
+cache_path = 'cache/foundry'
+verbosity = 3
+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 our 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/',
+]
+
+[fuzz]
+runs = 1000
diff --git a/examples/oft-alt/hardhat.config.ts b/examples/oft-alt/hardhat.config.ts
new file mode 100644
index 000000000..53c186deb
--- /dev/null
+++ b/examples/oft-alt/hardhat.config.ts
@@ -0,0 +1,82 @@
+// 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 = {
+ paths: {
+ cache: 'cache/hardhat',
+ },
+ solidity: {
+ compilers: [
+ {
+ version: '0.8.22',
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ ],
+ },
+ networks: {
+ 'sepolia-testnet': {
+ eid: EndpointId.SEPOLIA_V2_TESTNET,
+ url: process.env.RPC_URL_SEPOLIA || 'https://rpc.sepolia.org/',
+ accounts,
+ },
+ 'avalanche-testnet': {
+ eid: EndpointId.AVALANCHE_V2_TESTNET,
+ url: process.env.RPC_URL_FUJI || 'https://rpc.ankr.com/avalanche_fuji',
+ accounts,
+ },
+ 'amoy-testnet': {
+ eid: EndpointId.AMOY_V2_TESTNET,
+ url: process.env.RPC_URL_AMOY || 'https://polygon-amoy-bor-rpc.publicnode.com',
+ accounts,
+ },
+ hardhat: {
+ // Need this for testing because TestHelperOz5.sol is exceeding the compiled contract size limit
+ allowUnlimitedContractSize: true,
+ },
+ },
+ namedAccounts: {
+ deployer: {
+ default: 0, // wallet address of index[0], of the mnemonic in .env
+ },
+ },
+}
+
+export default config
diff --git a/examples/oft-alt/layerzero.config.ts b/examples/oft-alt/layerzero.config.ts
new file mode 100644
index 000000000..5a239c8ed
--- /dev/null
+++ b/examples/oft-alt/layerzero.config.ts
@@ -0,0 +1,60 @@
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+
+import type { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat'
+
+const sepoliaContract: OmniPointHardhat = {
+ eid: EndpointId.SEPOLIA_V2_TESTNET,
+ contractName: 'MyOFT',
+}
+
+const fujiContract: OmniPointHardhat = {
+ eid: EndpointId.AVALANCHE_V2_TESTNET,
+ contractName: 'MyOFT',
+}
+
+const amoyContract: OmniPointHardhat = {
+ eid: EndpointId.AMOY_V2_TESTNET,
+ contractName: 'MyOFT',
+}
+
+const config: OAppOmniGraphHardhat = {
+ contracts: [
+ {
+ contract: fujiContract,
+ },
+ {
+ contract: sepoliaContract,
+ },
+ {
+ contract: amoyContract,
+ },
+ ],
+ connections: [
+ {
+ from: fujiContract,
+ to: sepoliaContract,
+ },
+ {
+ from: fujiContract,
+ to: amoyContract,
+ },
+ {
+ from: sepoliaContract,
+ to: fujiContract,
+ },
+ {
+ from: sepoliaContract,
+ to: amoyContract,
+ },
+ {
+ from: amoyContract,
+ to: sepoliaContract,
+ },
+ {
+ from: amoyContract,
+ to: fujiContract,
+ },
+ ],
+}
+
+export default config
diff --git a/examples/oft-alt/package.json b/examples/oft-alt/package.json
new file mode 100644
index 000000000..4bf9e5c51
--- /dev/null
+++ b/examples/oft-alt/package.json
@@ -0,0 +1,78 @@
+{
+ "name": "@layerzerolabs/oft-alt-example",
+ "version": "0.0.1",
+ "private": true,
+ "license": "MIT",
+ "scripts": {
+ "clean": "rm -rf artifacts cache out",
+ "compile": "concurrently -c auto --names forge,hardhat '$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": {
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.23.9",
+ "@layerzerolabs/devtools-evm-hardhat": "^2.0.0",
+ "@layerzerolabs/eslint-config-next": "~2.3.39",
+ "@layerzerolabs/lz-definitions": "^3.0.12",
+ "@layerzerolabs/lz-evm-messagelib-v2": "^3.0.12",
+ "@layerzerolabs/lz-evm-protocol-v2": "^3.0.12",
+ "@layerzerolabs/lz-evm-v1-0.7": "^3.0.12",
+ "@layerzerolabs/lz-v2-utilities": "^3.0.12",
+ "@layerzerolabs/oapp-alt-evm": "^0.0.1",
+ "@layerzerolabs/oapp-evm": "^0.3.0",
+ "@layerzerolabs/oft-alt-evm": "^0.0.1",
+ "@layerzerolabs/oft-evm": "^3.0.0",
+ "@layerzerolabs/prettier-config-next": "^2.3.39",
+ "@layerzerolabs/solhint-config": "^3.0.12",
+ "@layerzerolabs/test-devtools-evm-foundry": "~5.1.0",
+ "@layerzerolabs/toolbox-foundry": "~0.1.9",
+ "@layerzerolabs/toolbox-hardhat": "~0.6.0",
+ "@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",
+ "concurrently": "~9.1.0",
+ "dotenv": "^16.4.1",
+ "eslint": "^8.55.0",
+ "eslint-plugin-jest-extended": "~2.0.0",
+ "ethers": "^5.7.2",
+ "hardhat": "^2.22.10",
+ "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.4.4"
+ },
+ "engines": {
+ "node": ">=18.16.0"
+ },
+ "pnpm": {
+ "overrides": {
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ }
+ },
+ "overrides": {
+ "ethers": "^5.7.2",
+ "hardhat-deploy": "^0.12.1"
+ }
+}
diff --git a/examples/oft-alt/solhint.config.js b/examples/oft-alt/solhint.config.js
new file mode 100644
index 000000000..52efe629c
--- /dev/null
+++ b/examples/oft-alt/solhint.config.js
@@ -0,0 +1 @@
+module.exports = require('@layerzerolabs/solhint-config');
diff --git a/examples/oft-alt/test/foundry/MyOFTAlt.t.sol b/examples/oft-alt/test/foundry/MyOFTAlt.t.sol
new file mode 100644
index 000000000..57fe38394
--- /dev/null
+++ b/examples/oft-alt/test/foundry/MyOFTAlt.t.sol
@@ -0,0 +1,601 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
+
+import { MyOFTAltMock } from "../mocks/MyOFTAltMock.sol";
+import { MyOFTAdapterAltMock } from "../mocks/MyOFTAdapterAltMock.sol";
+
+import { MessagingFee, MessagingReceipt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAltCore.sol";
+import { ERC20Mock } from "@layerzerolabs/oft-evm/test/mocks/ERC20Mock.sol";
+
+import { OFTComposerMock } from "@layerzerolabs/oft-evm/test/mocks/OFTComposerMock.sol";
+import { OFTInspectorMock, IOAppMsgInspector } from "@layerzerolabs/oft-evm/test/mocks/OFTInspectorMock.sol";
+import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
+
+import { OAppSenderAlt } from "@layerzerolabs/oapp-alt-evm/contracts/oapp/OAppSenderAlt.sol";
+
+import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol";
+import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";
+
+import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
+import { MyOFTAlt } from "../../contracts/MyOFTAlt.sol";
+import { MyOFTAdapterAlt } from "../../contracts/MyOFTAdapterAlt.sol";
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
+
+import { OFTAltMockCodec } from "@layerzerolabs/oft-alt-evm/test/lib/OFTAltMockCodec.sol";
+import { OFTAdapterAltMockCodec } from "@layerzerolabs/oft-alt-evm/test/lib/OFTAdapterAltMockCodec.sol";
+
+import { console } from "forge-std/console.sol";
+import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol";
+contract MyOFTAltTest is TestHelperOz5 {
+ using OptionsBuilder for bytes;
+ using OFTAltMockCodec for MyOFTAltMock;
+ using OFTAdapterAltMockCodec for MyOFTAdapterAltMock;
+
+ uint32 internal constant A_EID = 1;
+ ERC20Mock nativeTokenMock_A = new ERC20Mock("NativeAltTokens_A", "NAT_A");
+
+ uint32 internal constant B_EID = 2;
+ ERC20Mock nativeTokenMock_B = new ERC20Mock("NativeAltTokens_B", "NAT_B");
+
+ uint32 internal constant C_EID = 3;
+ ERC20Mock nativeTokenMock_C = new ERC20Mock("NativeAltTokens_C", "NAT_C");
+
+ uint32 internal constant D_EID = 4;
+ ERC20Mock nativeTokenMock_D = new ERC20Mock("NativeAltTokens_D", "NAT_D");
+
+ string internal constant A_OFT_NAME = "aOFT";
+ string internal constant A_OFT_SYMBOL = "aOFT";
+ string internal constant B_OFT_NAME = "bOFT";
+ string internal constant B_OFT_SYMBOL = "bOFT";
+ string internal constant C_TOKEN_NAME = "cToken";
+ string internal constant C_TOKEN_SYMBOL = "cToken";
+
+ address[] public altToken;
+
+ MyOFTAltMock internal aOFT;
+ MyOFTAltMock internal bOFT;
+ MyOFTAdapterAltMock internal cOFTAdapter;
+ ERC20Mock internal cERC20Mock;
+
+ OFTInspectorMock internal oAppInspector;
+
+ address public userA = makeAddr("userA");
+ address public userB = makeAddr("userB");
+ address public userC = makeAddr("userC");
+ address public userD = makeAddr("userD");
+ uint256 public initialBalance = 100 ether;
+ uint256 public initialNativeBalance = 1000 ether;
+
+ function setUp() public virtual override {
+ _deal();
+
+ super.setUp();
+ altToken.push(address(nativeTokenMock_A));
+ altToken.push(address(nativeTokenMock_B));
+ altToken.push(address(nativeTokenMock_C));
+ altToken.push(address(nativeTokenMock_D));
+
+ createEndpoints(4, LibraryType.SimpleMessageLib, altToken);
+
+ aOFT = MyOFTAltMock(
+ _deployOApp(
+ type(MyOFTAltMock).creationCode,
+ abi.encode(A_OFT_NAME, A_OFT_SYMBOL, address(endpoints[A_EID]), address(this))
+ )
+ );
+
+ bOFT = MyOFTAltMock(
+ _deployOApp(
+ type(MyOFTAltMock).creationCode,
+ abi.encode(B_OFT_NAME, B_OFT_SYMBOL, address(endpoints[B_EID]), address(this))
+ )
+ );
+
+ cERC20Mock = new ERC20Mock(C_TOKEN_NAME, C_TOKEN_SYMBOL);
+ cOFTAdapter = MyOFTAdapterAltMock(
+ _deployOApp(
+ type(MyOFTAdapterAltMock).creationCode,
+ abi.encode(address(cERC20Mock), address(endpoints[C_EID]), address(this))
+ )
+ );
+
+
+
+ // config and wire the ofts
+ address[] memory ofts = new address[](3);
+ ofts[0] = address(aOFT);
+ ofts[1] = address(bOFT);
+ ofts[2] = address(cOFTAdapter);
+ this.wireOApps(ofts);
+
+ // mint tokens
+ aOFT.asOFTAltMock().mint(userA, initialBalance);
+ bOFT.asOFTAltMock().mint(userB, initialBalance);
+ cERC20Mock.mint(userC, initialBalance);
+
+ // deploy a universal inspector, can be used by each oft
+ oAppInspector = new OFTInspectorMock();
+ }
+
+ function _deal() internal {
+ vm.deal(userA, initialNativeBalance);
+ vm.deal(userB, initialNativeBalance);
+ vm.deal(userC, initialNativeBalance);
+ vm.deal(userD, initialNativeBalance);
+ }
+
+ function test_constructor() public {
+ assertEq(aOFT.owner(), address(this));
+ assertEq(bOFT.owner(), address(this));
+ assertEq(cOFTAdapter.owner(), address(this));
+
+ assertEq(aOFT.balanceOf(userA), initialBalance);
+ assertEq(bOFT.balanceOf(userB), initialBalance);
+ assertEq(IERC20(cOFTAdapter.token()).balanceOf(userC), initialBalance);
+
+ assertEq(aOFT.token(), address(aOFT));
+ assertEq(bOFT.token(), address(bOFT));
+ assertEq(cOFTAdapter.token(), address(cERC20Mock));
+ }
+
+ function test_oftVersion() public {
+ (bytes4 interfaceId, ) = aOFT.oftVersion();
+ bytes4 expectedId = 0x02e49c2c;
+ assertEq(interfaceId, expectedId);
+ }
+
+ function test_send_oft(uint256 tokensToSend) public virtual {
+ vm.assume(tokensToSend > 0.001 ether && tokensToSend < 100 ether); // avoid reverting due to SlippageExceeded
+
+ uint256 preETHBalance_userA = userA.balance;
+ bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ SendParam memory sendParam = SendParam(
+ B_EID,
+ addressToBytes32(userB),
+ tokensToSend,
+ (tokensToSend * 9_500) / 10_000, // allow 1% slippage
+ options,
+ "",
+ ""
+ );
+
+ MessagingFee memory quoteFee = aOFT.quoteSend(sendParam, false);
+
+ assertEq(aOFT.balanceOf(userA), initialBalance);
+ assertEq(bOFT.balanceOf(userB), initialBalance);
+
+ if (aOFT.asOFTAltMock().approvalRequired()) {
+ vm.prank(userA);
+ aOFT.asIERC20().approve(address(aOFT), tokensToSend);
+ }
+
+ vm.startPrank(userA);
+ nativeTokenMock_A.mint(userA, quoteFee.nativeFee);
+ IERC20(nativeTokenMock_A).approve(address(aOFT), quoteFee.nativeFee);
+
+ (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send(
+ sendParam,
+ quoteFee,
+ payable(address(this))
+ );
+ vm.stopPrank();
+
+ verifyPackets(B_EID, addressToBytes32(address(bOFT)));
+ uint256 postETHBalance_userA = userA.balance;
+
+ assertEq(preETHBalance_userA, postETHBalance_userA);
+ assertEq(msgReceipt.fee.nativeFee, quoteFee.nativeFee);
+ assertEq(aOFT.balanceOf(userA), initialBalance - oftReceipt.amountSentLD);
+ assertEq(bOFT.balanceOf(userB), initialBalance + oftReceipt.amountReceivedLD);
+ }
+
+ function test_send_oft_compose_msg(uint256 tokensToSend) public virtual {
+ vm.assume(tokensToSend > 0.001 ether && tokensToSend < 100 ether); // avoid reverting due to SlippageExceeded
+
+ OFTComposerMock composer = new OFTComposerMock();
+
+ bytes memory options = OptionsBuilder
+ .newOptions()
+ .addExecutorLzReceiveOption(200000, 0)
+ .addExecutorLzComposeOption(0, 500000, 0);
+ bytes memory composeMsg = hex"1234";
+ SendParam memory sendParam = SendParam(
+ B_EID,
+ addressToBytes32(address(composer)),
+ tokensToSend,
+ (tokensToSend * 9_500) / 10_000, // allow 1% slippage
+ options,
+ composeMsg,
+ ""
+ );
+ MessagingFee memory quoteFee = aOFT.quoteSend(sendParam, false);
+
+ assertEq(aOFT.balanceOf(userA), initialBalance);
+ assertEq(bOFT.balanceOf(address(composer)), 0);
+
+ if (aOFT.asOFTAltMock().approvalRequired()) {
+ vm.prank(userA);
+ aOFT.asIERC20().approve(address(aOFT), tokensToSend);
+ }
+
+ vm.startPrank(userA);
+ nativeTokenMock_A.mint(userA, quoteFee.nativeFee);
+ IERC20(nativeTokenMock_A).approve(address(aOFT), quoteFee.nativeFee);
+ (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: quoteFee.nativeFee }(
+ sendParam,
+ quoteFee,
+ payable(address(this))
+ );
+ verifyPackets(B_EID, addressToBytes32(address(bOFT)));
+
+ // lzCompose params
+ uint32 dstEid_ = B_EID;
+ address from_ = address(bOFT);
+ bytes memory options_ = options;
+ bytes32 guid_ = msgReceipt.guid;
+ address to_ = address(composer);
+ bytes memory composerMsg_ = OFTComposeMsgCodec.encode(
+ msgReceipt.nonce,
+ A_EID,
+ oftReceipt.amountReceivedLD,
+ abi.encodePacked(addressToBytes32(userA), composeMsg)
+ );
+ this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_);
+
+ assertEq(aOFT.balanceOf(userA), initialBalance - oftReceipt.amountSentLD);
+ assertEq(bOFT.balanceOf(address(composer)), oftReceipt.amountReceivedLD);
+
+ 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_oft_compose_codec(
+ uint64 nonce,
+ uint32 srcEid,
+ uint256 amountCreditLD,
+ bytes memory composeMsg
+ ) public {
+ bytes memory message = OFTComposeMsgCodec.encode(
+ nonce,
+ srcEid,
+ amountCreditLD,
+ abi.encodePacked(addressToBytes32(msg.sender), composeMsg)
+ );
+ (uint64 nonce_, uint32 srcEid_, uint256 amountCreditLD_, bytes32 composeFrom_, bytes memory composeMsg_) = this.decodeOFTComposeMsgCodec(message);
+
+ assertEq(nonce_, nonce);
+ assertEq(srcEid_, srcEid);
+ assertEq(amountCreditLD_, amountCreditLD);
+ assertEq(composeFrom_, addressToBytes32(msg.sender));
+ assertEq(composeMsg_, composeMsg);
+ }
+
+ function decodeOFTComposeMsgCodec(
+ bytes calldata message
+ )
+ public
+ pure
+ returns (uint64 nonce, uint32 srcEid, uint256 amountCreditLD, bytes32 composeFrom, bytes memory composeMsg)
+ {
+ nonce = OFTComposeMsgCodec.nonce(message);
+ srcEid = OFTComposeMsgCodec.srcEid(message);
+ amountCreditLD = OFTComposeMsgCodec.amountLD(message);
+ composeFrom = OFTComposeMsgCodec.composeFrom(message);
+ composeMsg = OFTComposeMsgCodec.composeMsg(message);
+ }
+
+ function test_debit_slippage_removeDust() public virtual {
+ uint256 amountToSendLD = 1.23456789 ether;
+ uint256 minAmountToCreditLD = 1.23456789 ether;
+ uint32 dstEid = A_EID;
+
+ // remove the dust form the shared decimal conversion
+ assertEq(aOFT.asOFTAltMock().removeDust(amountToSendLD), 1.234567 ether);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ IOFT.SlippageExceeded.selector,
+ aOFT.asOFTAltMock().removeDust(amountToSendLD),
+ minAmountToCreditLD
+ )
+ );
+ aOFT.asOFTAltMock().debit(amountToSendLD, minAmountToCreditLD, dstEid);
+ }
+
+ function test_debit_slippage_minAmountToCreditLD() public virtual {
+ uint256 amountToSendLD = 1 ether;
+ uint256 minAmountToCreditLD = 1.00000001 ether;
+ uint32 dstEid = A_EID;
+
+ vm.expectRevert(abi.encodeWithSelector(IOFT.SlippageExceeded.selector, amountToSendLD, minAmountToCreditLD));
+ aOFT.asOFTAltMock().debit(amountToSendLD, minAmountToCreditLD, dstEid);
+ }
+
+ function test_toLD(uint64 amountSD) public {
+ assertEq(amountSD * MyOFTAltMock(address(aOFT)).decimalConversionRate(), aOFT.asOFTAltMock().toLD(uint64(amountSD)));
+ }
+
+ function test_toSD(uint256 amountLD) public {
+ vm.assume(amountLD <= type(uint64).max); // avoid reverting due to overflow
+ assertEq(amountLD / aOFT.asOFTAltMock().decimalConversionRate(), aOFT.asOFTAltMock().toSD(amountLD));
+ }
+
+ function test_oft_debit() public virtual {
+ uint256 amountToSendLD = 1 ether;
+ uint256 minAmountToCreditLD = 1 ether;
+ uint32 dstEid = A_EID;
+
+ assertEq(aOFT.balanceOf(userA), initialBalance);
+ assertEq(aOFT.balanceOf(address(this)), 0);
+
+ vm.prank(userA);
+ (uint256 amountDebitedLD, uint256 amountToCreditLD) = aOFT.asOFTAltMock().debit(
+ amountToSendLD,
+ minAmountToCreditLD,
+ dstEid
+ );
+
+ assertEq(amountDebitedLD, amountToSendLD);
+ assertEq(amountToCreditLD, amountToSendLD);
+
+ assertEq(aOFT.balanceOf(userA), initialBalance - amountToSendLD);
+ assertEq(aOFT.balanceOf(address(this)), 0);
+ }
+
+ function test_oft_credit() public {
+ uint256 amountToCreditLD = 1 ether;
+ uint32 srcEid = A_EID;
+
+ assertEq(aOFT.balanceOf(userA), initialBalance);
+ assertEq(aOFT.balanceOf(address(this)), 0);
+
+ vm.prank(userA);
+ uint256 amountReceived = aOFT.asOFTAltMock().credit(userA, amountToCreditLD, srcEid);
+
+ assertEq(aOFT.balanceOf(userA), initialBalance + amountReceived);
+ assertEq(aOFT.balanceOf(address(this)), 0);
+ }
+
+ function test_oft_adapter_debit() public virtual {
+ uint256 amountToSendLD = 1 ether;
+ uint256 minAmountToCreditLD = 1 ether;
+ uint32 dstEid = C_EID;
+
+ assertEq(cERC20Mock.balanceOf(userC), initialBalance);
+ assertEq(cERC20Mock.balanceOf(address(cOFTAdapter)), 0);
+
+ vm.prank(userC);
+ vm.expectRevert(
+ abi.encodeWithSelector(IOFT.SlippageExceeded.selector, amountToSendLD, minAmountToCreditLD + 1)
+ );
+ cOFTAdapter.asOFTAdapterAltMock().debitView(amountToSendLD, minAmountToCreditLD + 1, dstEid);
+
+ vm.prank(userC);
+ cERC20Mock.approve(address(cOFTAdapter), amountToSendLD);
+ vm.prank(userC);
+ (uint256 amountDebitedLD, uint256 amountToCreditLD) = cOFTAdapter.asOFTAdapterAltMock().debit(
+ amountToSendLD,
+ minAmountToCreditLD,
+ dstEid
+ );
+
+ assertEq(amountDebitedLD, amountToSendLD);
+ assertEq(amountToCreditLD, amountToSendLD);
+
+ assertEq(cERC20Mock.balanceOf(userC), initialBalance - amountToSendLD);
+ assertEq(cERC20Mock.balanceOf(address(cOFTAdapter)), amountToSendLD);
+ }
+
+ function test_oft_adapter_credit() public {
+ uint256 amountToCreditLD = 1 ether;
+ uint32 srcEid = C_EID;
+
+ assertEq(cERC20Mock.balanceOf(userC), initialBalance);
+ assertEq(cERC20Mock.balanceOf(address(cOFTAdapter)), 0);
+
+ vm.prank(userC);
+ cERC20Mock.transfer(address(cOFTAdapter), amountToCreditLD);
+
+ uint256 amountReceived = cOFTAdapter.asOFTAdapterAltMock().credit(userB, amountToCreditLD, srcEid);
+
+ assertEq(cERC20Mock.balanceOf(userC), initialBalance - amountToCreditLD);
+ assertEq(cERC20Mock.balanceOf(address(userB)), amountReceived);
+ assertEq(cERC20Mock.balanceOf(address(cOFTAdapter)), 0);
+ }
+
+ function decodeOFTMsgCodec(
+ bytes calldata message
+ ) public pure returns (bool isComposed, bytes32 sendTo, uint64 amountSD, bytes memory composeMsg) {
+ isComposed = OFTMsgCodec.isComposed(message);
+ sendTo = OFTMsgCodec.sendTo(message);
+ amountSD = OFTMsgCodec.amountSD(message);
+ composeMsg = OFTMsgCodec.composeMsg(message);
+ }
+
+ function test_oft_build_msg(uint32 dstEid, bytes32 to, uint256 amountToSendLD, bytes memory composeMsg) public {
+ vm.assume(composeMsg.length > 0); // ensure there is a composed payload
+ uint256 minAmountToCreditLD = aOFT.asOFTAltMock().removeDust(amountToSendLD);
+
+ // params for buildMsgAndOptions
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ SendParam memory sendParam = SendParam(
+ dstEid,
+ to,
+ amountToSendLD,
+ minAmountToCreditLD,
+ extraOptions,
+ composeMsg,
+ ""
+ );
+ uint256 amountToCreditLD = minAmountToCreditLD;
+
+ (bytes memory message, ) = aOFT.asOFTAltMock().buildMsgAndOptions(sendParam, amountToCreditLD);
+
+ (bool isComposed_, bytes32 sendTo_, uint64 amountSD_, bytes memory composeMsg_) = this.decodeOFTMsgCodec(
+ message
+ );
+
+ assertEq(isComposed_, true);
+ assertEq(sendTo_, to);
+ assertEq(amountSD_, aOFT.asOFTAltMock().toSD(amountToCreditLD));
+ bytes memory expectedComposeMsg = abi.encodePacked(addressToBytes32(address(this)), composeMsg);
+ assertEq(composeMsg_, expectedComposeMsg);
+ }
+
+ function test_oft_build_msg_no_compose_msg(uint32 dstEid, bytes32 to, uint256 amountToSendLD) public {
+ uint256 minAmountToCreditLD = aOFT.asOFTAltMock().removeDust(amountToSendLD);
+
+ // params for buildMsgAndOptions
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ bytes memory composeMsg = "";
+ SendParam memory sendParam = SendParam(
+ dstEid,
+ to,
+ amountToSendLD,
+ minAmountToCreditLD,
+ extraOptions,
+ composeMsg,
+ ""
+ );
+ uint256 amountToCreditLD = minAmountToCreditLD;
+
+ (bytes memory message, ) = aOFT.asOFTAltMock().buildMsgAndOptions(sendParam, amountToCreditLD);
+
+ (bool isComposed_, bytes32 sendTo_, uint64 amountSD_, bytes memory composeMsg_) = this.decodeOFTMsgCodec(
+ message
+ );
+
+ assertEq(isComposed_, false);
+ assertEq(sendTo_, to);
+ assertEq(amountSD_, aOFT.asOFTAltMock().toSD(amountToCreditLD));
+ assertEq(composeMsg_, "");
+ }
+
+ function test_set_enforced_options() public {
+ uint32 eid = 1;
+
+ bytes memory optionsTypeOne = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ bytes memory optionsTypeTwo = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250000, 0);
+
+ EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](2);
+ enforcedOptions[0] = EnforcedOptionParam(eid, 1, optionsTypeOne);
+ enforcedOptions[1] = EnforcedOptionParam(eid, 2, optionsTypeTwo);
+
+ aOFT.setEnforcedOptions(enforcedOptions);
+
+ assertEq(aOFT.enforcedOptions(eid, 1), optionsTypeOne);
+ assertEq(aOFT.enforcedOptions(eid, 2), optionsTypeTwo);
+ }
+
+ function test_assert_options_type3_revert() public {
+ uint32 eid = 1;
+ EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam[](1);
+
+ enforcedOptions[0] = EnforcedOptionParam(eid, 1, hex"0004"); // not type 3
+ vm.expectRevert(abi.encodeWithSelector(IOAppOptionsType3.InvalidOptions.selector, hex"0004"));
+ aOFT.setEnforcedOptions(enforcedOptions);
+
+ enforcedOptions[0] = EnforcedOptionParam(eid, 1, hex"0002"); // not type 3
+ vm.expectRevert(abi.encodeWithSelector(IOAppOptionsType3.InvalidOptions.selector, hex"0002"));
+ aOFT.setEnforcedOptions(enforcedOptions);
+
+ enforcedOptions[0] = EnforcedOptionParam(eid, 1, hex"0001"); // not type 3
+ vm.expectRevert(abi.encodeWithSelector(IOAppOptionsType3.InvalidOptions.selector, hex"0001"));
+ aOFT.setEnforcedOptions(enforcedOptions);
+
+ enforcedOptions[0] = EnforcedOptionParam(eid, 1, hex"0003"); // IS type 3
+ aOFT.setEnforcedOptions(enforcedOptions); // doesnt revert cus option type 3
+ }
+
+ function test_combine_options(uint32 eid, uint128 nativeDropGas, address user) public {
+ uint16 msgType = 1;
+
+ bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1);
+ enforcedOptionsArray[0] = EnforcedOptionParam(eid, msgType, enforcedOptions);
+ aOFT.setEnforcedOptions(enforcedOptionsArray);
+
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ nativeDropGas,
+ addressToBytes32(user)
+ );
+
+ bytes memory expectedOptions = OptionsBuilder
+ .newOptions()
+ .addExecutorLzReceiveOption(200000, 0)
+ .addExecutorNativeDropOption(nativeDropGas, addressToBytes32(user));
+
+ bytes memory combinedOptions = aOFT.combineOptions(eid, msgType, extraOptions);
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_combine_options_no_extra_options(uint32 eid, uint128 gasLimit, uint128 nativeDrop) public {
+ uint16 msgType = 1;
+
+ bytes memory enforcedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(gasLimit, nativeDrop);
+ EnforcedOptionParam[] memory enforcedOptionsArray = new EnforcedOptionParam[](1);
+ enforcedOptionsArray[0] = EnforcedOptionParam(eid, msgType, enforcedOptions);
+ aOFT.setEnforcedOptions(enforcedOptionsArray);
+
+ bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(gasLimit, nativeDrop);
+
+ bytes memory combinedOptions = aOFT.combineOptions(eid, msgType, "");
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_combine_options_no_enforced_options(
+ uint32 eid,
+ uint16 msgType,
+ uint128 nativeDropGas,
+ address user
+ ) public {
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ nativeDropGas,
+ addressToBytes32(user)
+ );
+
+ bytes memory expectedOptions = OptionsBuilder.newOptions().addExecutorNativeDropOption(
+ nativeDropGas,
+ addressToBytes32(user)
+ );
+
+ bytes memory combinedOptions = aOFT.combineOptions(eid, msgType, extraOptions);
+ assertEq(combinedOptions, expectedOptions);
+ }
+
+ function test_oapp_inspector_inspect(uint32 dstEid, address user, uint256 amountToSendLD) public {
+ bytes32 to = addressToBytes32(user);
+ uint256 minAmountToCreditLD = aOFT.asOFTAltMock().removeDust(amountToSendLD);
+
+ // params for buildMsgAndOptions
+ bytes memory extraOptions = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ bytes memory composeMsg = "";
+ SendParam memory sendParam = SendParam(
+ dstEid,
+ to,
+ amountToSendLD,
+ minAmountToCreditLD,
+ extraOptions,
+ composeMsg,
+ ""
+ );
+ uint256 amountToCreditLD = minAmountToCreditLD;
+
+ // doesnt revert
+ (bytes memory message, ) = aOFT.asOFTAltMock().buildMsgAndOptions(sendParam, amountToCreditLD);
+
+ // deploy a universal inspector, it automatically reverts
+ oAppInspector = new OFTInspectorMock();
+ // set the inspector
+ aOFT.setMsgInspector(address(oAppInspector));
+
+ // does revert because inspector is set
+ vm.expectRevert(abi.encodeWithSelector(IOAppMsgInspector.InspectionFailed.selector, message, extraOptions));
+ (message, ) = aOFT.asOFTAltMock().buildMsgAndOptions(sendParam, amountToCreditLD);
+ }
+}
diff --git a/examples/oft-alt/test/mocks/MyOFTAdapterAltMock.sol b/examples/oft-alt/test/mocks/MyOFTAdapterAltMock.sol
new file mode 100644
index 000000000..2f9bb9b65
--- /dev/null
+++ b/examples/oft-alt/test/mocks/MyOFTAdapterAltMock.sol
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+
+import { OFTAdapterAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAdapterAlt.sol";
+
+contract MyOFTAdapterAltMock is OFTAdapterAlt {
+ constructor(
+ address _token,
+ address _lzEndpoint,
+ address _delegate
+ ) OFTAdapterAlt(_token, _lzEndpoint, _delegate) Ownable(_delegate) {}
+
+ // @dev expose internal functions for testing purposes
+ function debit(
+ uint256 _amountToSendLD,
+ uint256 _minAmountToCreditLD,
+ uint32 _dstEid
+ ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
+ return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid);
+ }
+
+ function debitView(
+ uint256 _amountToSendLD,
+ uint256 _minAmountToCreditLD,
+ uint32 _dstEid
+ ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
+ return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid);
+ }
+
+ function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) {
+ return _credit(_to, _amountToCreditLD, _srcEid);
+ }
+
+ function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) {
+ return _removeDust(_amountLD);
+ }
+}
diff --git a/examples/oft-alt/test/mocks/MyOFTAltMock.sol b/examples/oft-alt/test/mocks/MyOFTAltMock.sol
new file mode 100644
index 000000000..49ad75e5b
--- /dev/null
+++ b/examples/oft-alt/test/mocks/MyOFTAltMock.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.0;
+
+import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
+
+import { OFTAlt } from "@layerzerolabs/oft-alt-evm/contracts/OFTAlt.sol";
+import { SendParam } from "@layerzerolabs/oft-alt-evm/contracts/OFTAltCore.sol";
+
+contract MyOFTAltMock is OFTAlt {
+ constructor(
+ string memory _name,
+ string memory _symbol,
+ address _lzEndpoint,
+ address _delegate
+ ) OFTAlt(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
+
+ function mint(address _to, uint256 _amount) public {
+ _mint(_to, _amount);
+ }
+
+ // @dev expose internal functions for testing purposes
+ function debit(
+ uint256 _amountToSendLD,
+ uint256 _minAmountToCreditLD,
+ uint32 _dstEid
+ ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
+ return _debit(msg.sender, _amountToSendLD, _minAmountToCreditLD, _dstEid);
+ }
+
+ function debitView(
+ uint256 _amountToSendLD,
+ uint256 _minAmountToCreditLD,
+ uint32 _dstEid
+ ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) {
+ return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid);
+ }
+
+ function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) {
+ return _removeDust(_amountLD);
+ }
+
+ function toLD(uint64 _amountSD) public view returns (uint256 amountLD) {
+ return _toLD(_amountSD);
+ }
+
+ function toSD(uint256 _amountLD) public view returns (uint64 amountSD) {
+ return _toSD(_amountLD);
+ }
+
+ function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) {
+ return _credit(_to, _amountToCreditLD, _srcEid);
+ }
+
+ function buildMsgAndOptions(
+ SendParam calldata _sendParam,
+ uint256 _amountToCreditLD
+ ) public view returns (bytes memory message, bytes memory options) {
+ return _buildMsgAndOptions(_sendParam, _amountToCreditLD);
+ }
+}
diff --git a/examples/oft-alt/tsconfig.json b/examples/oft-alt/tsconfig.json
new file mode 100644
index 000000000..027ad0f3f
--- /dev/null
+++ b/examples/oft-alt/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
+ }
+}
From a1d0575a6497ab543cbaa3894655323edb4056e1 Mon Sep 17 00:00:00 2001
From: Shankar
Date: Wed, 20 Nov 2024 23:09:49 +0000
Subject: [PATCH 2/3] deploy script for endpointv2
Signed-off-by: Shankar
---
examples/oft-alt/README.md | 4 +-
examples/oft-alt/deploy/MyOFTAdapterAlt.ts | 60 ++++++++++++++++++++
examples/oft-alt/hardhat.config.ts | 5 ++
examples/oft-alt/test/foundry/MyOFTAlt.t.sol | 10 ++--
examples/oft-alt/type-extensions.ts | 23 ++++++++
5 files changed, 95 insertions(+), 7 deletions(-)
create mode 100644 examples/oft-alt/deploy/MyOFTAdapterAlt.ts
create mode 100644 examples/oft-alt/type-extensions.ts
diff --git a/examples/oft-alt/README.md b/examples/oft-alt/README.md
index 709ae3c7a..b896f6177 100644
--- a/examples/oft-alt/README.md
+++ b/examples/oft-alt/README.md
@@ -8,10 +8,9 @@
Homepage | Docs | Developers
-
Omnichain Fungible Token (OFTAdapterAlt and OFTAlt) Example for Alt Endpoints
-# ⚠️ ** This code is currently under audit and should not yet be used in production, the readme might not be complete
+# ⚠️ \*\* This code is currently under audit and should not yet be used in production, the readme might not be complete
Quickstart | Configuration | Message Execution Options | Endpoint, MessageLib, & Executor Addresses |