Layer zero offers the following thing
OFT: For new ERC20 cross chain tokens.
OFTAdapter: For existing ERC20 deployed tokens (works by locking tokens in the contract)
There can only be one OFT Adapter lockbox in your omnichain deployment. Multiple adapters break unified liquidity and can cause permanent token loss due to insufficient destination supply.
OVault:
OVault uses ERC4626 contract + a OFTAdapter for the shares tokens + a OFT base asset or OFTAdapter for the base asset if base asset isn’t OFT.
So for existing shares token we can put a OFTAdapter followed by OFT on other chains.
If we go with OVault in new Vaults, it will allow cross chain vault interaction, the ERC4626 vault still stays only on the one chain, but you can call ERC4626 operations cross chain.
we deploy a OFTAdapter for that asset on original chain, and OFT for that token on other chains.
https://docs.stargate.finance/ecosystem/list-on-stargate
As mentioned in their doc they list any OFT and charge a 2bps (0.02%) fee on stargate transfer fee.
When deploying for an existing vault, the ERC-4626 cross-chain functionality isn't supported, correct?
As long as the asset vault uses is an OFT, we can deploy ShareOFTAdapter for existing ERC4626 vault thus enabling cross chain vault interaction via ShareOFTAdapter.
So, for the existing share token, we can deploy an OFTAdapter and then deploy OFTs on other chains. This means the vault share is handled as a plain ERC-20 token.
Yes exactly.
srUSDe deployment so we’ll have a OFTAdapter contract for srUSDe like
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.22;
import { OFTAdapter } from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/// @notice OFTAdapter uses a deployed ERC-20 token and SafeERC20 to interact with the OFTCore contract.
contract SrUSDeAdapter is OFTAdapter {
constructor(
address _token,
address _lzEndpoint,
address _owner
) OFTAdapter(_token, _lzEndpoint, _owner) Ownable(_owner) {}
}
contract SrUSDeOFT is OFT {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _owner
) OFT(_name, _symbol, _lzEndpoint, _owner) Ownable(_owner) {}
}Layerzero config will look like the following, where you link ur OFTAdapter deployment + All deployments of OFT for srUSDe on other chains, and create path between them so they can communicate cross chain (this is a reference config).
import {EndpointId} from '@layerzerolabs/lz-definitions';
import {ExecutorOptionType} from '@layerzerolabs/lz-v2-utilities';
import {TwoWayConfig, generateConnectionsConfig} from '@layerzerolabs/metadata-tools';
import {OAppEnforcedOption, OmniPointHardhat} from '@layerzerolabs/toolbox-hardhat';
// This contract object defines the OApp deployment on Optimism Sepolia testnet
// The config references the contract deployment from your ./deployments folder
const optimismContract: OmniPointHardhat = {
eid: EndpointId.OPTSEP_V2_TESTNET,
contractName: 'MyOFT',
};
const arbitrumContract: OmniPointHardhat = {
eid: EndpointId.ARBSEP_V2_TESTNET,
contractName: 'MyOFT',
};
// For this example's simplicity, we will use the same enforced options values for sending to all chains
// For production, you should ensure `gas` is set to the correct value through profiling the gas usage of calling OApp._lzReceive(...) on the destination chain
// To learn more, read https://docs.layerzero.network/v2/concepts/applications/oapp-standard#execution-options-and-enforced-settings
const EVM_ENFORCED_OPTIONS: OAppEnforcedOption[] = [
{
msgType: 1,
optionType: ExecutorOptionType.LZ_RECEIVE,
gas: 80000,
value: 0,
},
];
// To connect all the above chains to each other, we need the following pathways:
// Optimism <-> Arbitrum
// With the config generator, pathways declared are automatically bidirectional
// i.e. if you declare A,B there's no need to declare B,A
const pathways: TwoWayConfig[] = [
[
optimismContract, // Chain A contract
arbitrumContract, // Chain B contract
[['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ]
[1, 1], // [A to B confirmations, B to A confirmations]
[EVM_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions
],
];
export default async function () {
// Generate the connections config based on the pathways
const connections = await generateConnectionsConfig(pathways);
return {
contracts: [{contract: optimismContract}, {contract: arbitrumContract}],
connections,
};
}Then you config these deployments using layerzero wire command
npx hardhat lz:oapp:wire --oapp-config layerzero.config.tsIf we can upgrade our existing vaults (Junior, Senior), what changes should we apply to make the cross-chain functionality work?
Basically OVault is ERC4626 + OFTAdapter/OFT for base asset + OFAdapter for share token + A composer contract
The composer contract handles the the deposits/redeem post sending assets from hub to spoke chain, think of composer contract in OVault like a post contract hook that gets triggered from the OApp (https://github.com/LayerZero-Labs/devtools/blob/ba9762f99a6995101d7098c43ed8608dd125faab/packages/ovault-evm/contracts/VaultComposerSync.sol#L118)
So, the cross chain deposit/redeem happens using compose msg and composer contract https://github.com/LayerZero-Labs/devtools/blob/36c2cb8c926b6dcc5a1cee9bb1e8579c9b9ae9cc/examples/ovault-evm/tasks/sendOVaultComposer.ts#L406
as long as the address of ERC4626 is same considering the base asset is always constant, we wont need any change in the OVault to adapt new changes, it’ll require redeployment if the Vault address changed.