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 @@ +

+ + LayerZero + +

+ +

+ 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 |