├── .gitignore ├── foundry.toml ├── remappings.txt ├── .gitmodules ├── src ├── layerzero │ ├── token │ │ └── oft │ │ │ ├── interfaces │ │ │ └── IOFTSubscriber.sol │ │ │ └── v1 │ │ │ ├── OFTUpgradeable.sol │ │ │ └── OFTCoreUpgradeable.sol │ └── lzApp │ │ ├── NonblockingLzAppUpgradeable.sol │ │ └── LzAppUpgradeable.sol ├── libraries │ └── RebaseTokenMath.sol └── tokens │ ├── CrossChainToken.sol │ ├── CrossChainRebaseTokenUpgradeable.sol │ ├── LayerZeroRebaseTokenUpgradeable.sol │ └── RebaseTokenUpgradeable.sol ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.ci.fuzz] 2 | runs = 10_000 3 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | @openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ 4 | @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ 5 | @layerzerolabs/contracts/=lib/layerzerolabs/contracts/ 6 | @layerzerolabs/contracts-upgradeable/=src/layerzero/ 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts-upgradeable"] 5 | path = lib/openzeppelin-contracts-upgradeable 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 7 | branch = release-v5.0 8 | [submodule "lib/layerzerolabs"] 9 | path = lib/layerzerolabs 10 | url = https://github.com/LayerZero-Labs/solidity-examples 11 | branch = main 12 | -------------------------------------------------------------------------------- /src/layerzero/token/oft/interfaces/IOFTSubscriber.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.5.0; 4 | 5 | /** 6 | * @dev Interface of the OFT subscriber 7 | */ 8 | interface IOFTSubscriber { 9 | /** 10 | * @notice Notifies the contract about a token credit from a source chain. 11 | * @dev This function allows external systems to inform the contract about credited tokens. 12 | * @param srcChainId Chain ID of the source chain. 13 | * @param initiator Address of the initiator on the source chain. 14 | * @param sender The address on the source chain from which the tokens were sent. 15 | * @param token Address of the credited token. 16 | * @param amount Amount of tokens credited. 17 | */ 18 | function notifyCredit(uint16 srcChainId, address initiator, address sender, address token, uint256 amount) 19 | external; 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Tangible 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/libraries/RebaseTokenMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; 5 | 6 | /** 7 | * @title RebaseTokenMath 8 | * @author Caesar LaVey 9 | * @dev A library that provides functions to convert between token amounts and shares in the context of a rebase 10 | * mechanism. 11 | * 12 | * Note: This library assumes that 1 ether is used as the base unit for the rebase index. 13 | */ 14 | library RebaseTokenMath { 15 | /** 16 | * @dev Converts a token amount to its equivalent shares using the rebase index. 17 | * The function uses the formula: shares = (amount * 1 ether) / rebaseIndex 18 | * 19 | * @param amount The token amount to be converted. 20 | * @param rebaseIndex The current rebase index. 21 | * @return shares The equivalent shares for the given token amount. 22 | */ 23 | function toShares(uint256 amount, uint256 rebaseIndex) internal pure returns (uint256 shares) { 24 | shares = Math.mulDiv(amount, 1 ether, rebaseIndex); 25 | } 26 | 27 | /** 28 | * @dev Converts shares to their equivalent token amount using the rebase index. 29 | * The function uses the formula: amount = (shares * rebaseIndex) / 1 ether 30 | * 31 | * @param shares The number of shares to be converted. 32 | * @param rebaseIndex The current rebase index. 33 | * @return amount The equivalent token amount for the given shares. 34 | */ 35 | function toTokens(uint256 shares, uint256 rebaseIndex) internal pure returns (uint256 amount) { 36 | amount = Math.mulDiv(shares, rebaseIndex, 1 ether); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tokens/CrossChainToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IOFTSubscriber} from "@layerzerolabs/contracts-upgradeable/token/oft/interfaces/IOFTSubscriber.sol"; 5 | 6 | abstract contract CrossChainToken { 7 | /// @custom:oz-upgrades-unsafe-allow state-variable-immutable 8 | bool public immutable isMainChain; 9 | 10 | /// @custom:oz-upgrades-unsafe-allow constructor 11 | constructor(uint256 mainChainId) { 12 | isMainChain = mainChainId == block.chainid; 13 | } 14 | 15 | /** 16 | * @dev Attempts to notify the receiver of the credited amount. 17 | * Inline assembly is used to call the `notifyCredit` function on the receiver in order to prevent LayerZero's 18 | * ExcessivelySafeCall library from tagging the transaction as failed when this call fails. 19 | * 20 | * @param srcChainId The ID of the source chain where the message originated. 21 | * @param initiator The address of the initiator on the source chain. 22 | * @param sender The address on the source chain from which the tokens were sent. 23 | * @param receiver The address of the receiver who received tokens. 24 | * @param amount The amount of tokens credited. 25 | */ 26 | function _tryNotifyReceiver( 27 | uint16 srcChainId, 28 | address initiator, 29 | address sender, 30 | address, 31 | address receiver, 32 | uint256 amount 33 | ) internal returns (bool success) { 34 | bytes memory data = 35 | abi.encodeCall(IOFTSubscriber.notifyCredit, (srcChainId, initiator, sender, address(this), amount)); 36 | assembly { 37 | success := 38 | call( 39 | gas(), // gas remaining 40 | receiver, // destination address 41 | 0, // no ether 42 | add(data, 32), // input buffer (starts after the first 32 bytes in the `data` array) 43 | mload(data), // input length (loaded from the first 32 bytes in the `data` array) 44 | 0, // output buffer 45 | 0 // output length 46 | ) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tangible Solidity Smart Contract Framework 2 | 3 | ## Introduction 4 | The Tangible Framework is a Solidity-based smart contract suite designed for creating cross-chain and rebase tokens. Leveraging LayerZero's omnichain interoperability protocol, it offers developers a robust and upgradeable set of tools to deploy tokens that maintain consistent behaviors and states across multiple blockchain networks. 5 | 6 | ## Features 7 | - **Cross-Chain Token Support**: Utilize `CrossChainToken` and `LzAppUpgradeable` contracts to deploy and manage tokens that can interact across chains. 8 | - **Rebase Token Functionality**: Implement elastic supply tokens with `RebaseTokenUpgradeable` and `RebaseTokenMath` library, allowing for automated supply adjustments. 9 | - **Omnichain Fungible Tokens (OFT)**: Integrate with the OFT standard using `OFTUpgradeable`, enabling tokens to be transacted seamlessly across different blockchains. 10 | - **Advanced Cross-Chain Rebase Tokens**: `CrossChainRebaseTokenUpgradeable` and `LayerZeroRebaseTokenUpgradeable` combine the rebase functionality with cross-chain capabilities, providing a sophisticated mechanism for rebase tokens in a multi-chain environment. 11 | - **Upgradeability**: All key contracts are upgradeable, ensuring your token logic can evolve over time without sacrificing state or continuity. 12 | 13 | ## Getting Started 14 | ### Prerequisites 15 | - Solidity ^0.8.20 16 | - [Foundry](https://github.com/foundry-rs/foundry) for smart contract development and testing 17 | 18 | ### Installation 19 | Clone the repository: 20 | ```bash 21 | git clone https://github.com/TangibleTNFT/tangible-foundation-contracts 22 | cd tangible-foundation-contracts 23 | forge install 24 | ``` 25 | 26 | ## Contract Architecture 27 | ### Cross-Chain Communication 28 | - `LzAppUpgradeable.sol`: Base contract for LayerZero applications, handling message sending and receiving across chains. 29 | 30 | ### Rebase Tokens 31 | - `RebaseTokenUpgradeable.sol`: Upgradeable base contract for implementing rebase tokens with elastic supply. 32 | - `RebaseTokenMath.sol`: Library providing mathematical functions to facilitate rebase token calculations. 33 | 34 | ### Advanced Cross-Chain Rebase Tokens 35 | - `CrossChainRebaseTokenUpgradeable.sol`: Enhances `RebaseTokenUpgradeable` with nonce-based cross-chain functionalities, ensuring the integrity of rebase operations across chains. 36 | - `LayerZeroRebaseTokenUpgradeable.sol`: Builds upon `CrossChainRebaseTokenUpgradeable` to provide specialized cross-chain rebase token operations within the LayerZero network, handling complex message passing and state synchronization. 37 | 38 | ### OFT Standard Implementation 39 | - `OFTUpgradeable.sol`: Implements the LayerZero OFT standard, facilitating seamless fungible token transactions across multiple chains. 40 | 41 | ## Dependencies 42 | This framework uses the following external libraries: 43 | - OpenZeppelin Contracts for secure standard implementations. 44 | - LayerZero Labs Contracts for cross-chain communication. 45 | 46 | ## Development 47 | Setup your development environment with Foundry and write your interaction scripts. Test your contracts thoroughly using Foundry's test environment. 48 | 49 | ## License 50 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 51 | 52 | ## Support 53 | For support, please open an issue in the GitHub repository or contact the development team. 54 | 55 | ## Acknowledgments 56 | - LayerZero Labs for the cross-chain communication protocols. 57 | - OpenZeppelin for the secure contract standards. 58 | - Omniscia for the security audit of the contracts. 59 | -------------------------------------------------------------------------------- /src/tokens/CrossChainRebaseTokenUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {CrossChainToken} from "./CrossChainToken.sol"; 5 | import {RebaseTokenUpgradeable} from "./RebaseTokenUpgradeable.sol"; 6 | 7 | /** 8 | * @title CrossChainRebaseTokenUpgradeable 9 | * @author Caesar LaVey 10 | * @notice This contract extends the functionality of `RebaseTokenUpgradeable` by enabling cross-chain rebase 11 | * operations. It also implements the `ICrossChain` interface. 12 | * 13 | * @dev The contract introduces a nonce mechanism to facilitate cross-chain interactions. It has a new struct, 14 | * `CrossChainRebaseTokenStorage`, to manage this additional state. 15 | * 16 | * The contract overrides the `_setRebaseIndex` function to add nonce-based verification. It provides a new function 17 | * `_setRebaseIndex(uint256 index, uint256 nonce)` to be used in place of the original `_setRebaseIndex` function. 18 | * 19 | * It also includes functions for nonce management and verification. 20 | */ 21 | abstract contract CrossChainRebaseTokenUpgradeable is RebaseTokenUpgradeable, CrossChainToken { 22 | /// @custom:storage-location erc7201:tangible.storage.CrossChainRebaseToken 23 | struct CrossChainRebaseTokenStorage { 24 | uint256 nonce; 25 | } 26 | 27 | // keccak256(abi.encode(uint256(keccak256("tangible.storage.CrossChainRebaseToken")) - 1)) & ~bytes32(uint256(0xff)) 28 | bytes32 private constant CrossChainRebaseTokenStorageLocation = 29 | 0xdc2fee72b887a559c0d0f7379919bb4c097013a85e230aa333d867a22945b500; 30 | 31 | function _getCrossChainRebaseTokenStorage() private pure returns (CrossChainRebaseTokenStorage storage $) { 32 | // slither-disable-next-line assembly 33 | assembly { 34 | $.slot := CrossChainRebaseTokenStorageLocation 35 | } 36 | } 37 | 38 | /** 39 | * @notice Initializes the CrossChainRebaseTokenUpgradeable contract. 40 | * @dev This function should only be called once during the contract deployment. It internally calls 41 | * `__CrossChainRebaseToken_init_unchained` for any further initializations and `__RebaseToken_init` to initialize 42 | * the inherited RebaseTokenUpgradeable contract. 43 | * 44 | * @param name The name of the token. 45 | * @param symbol The symbol of the token. 46 | */ 47 | function __CrossChainRebaseToken_init(string memory name, string memory symbol) internal onlyInitializing { 48 | __CrossChainRebaseToken_init_unchained(); 49 | __RebaseToken_init(name, symbol); 50 | } 51 | 52 | function __CrossChainRebaseToken_init_unchained() internal onlyInitializing {} 53 | 54 | /** 55 | * @notice Retrieves the current rebase nonce. 56 | * @dev The function fetches the current nonce from the `CrossChainRebaseTokenStorage` struct. The nonce is used in 57 | * cross-chain rebase operations to ensure the correct sequence of operations. 58 | * 59 | * @return nonce The current rebase nonce. 60 | */ 61 | function _rebaseNonce() internal view returns (uint256 nonce) { 62 | CrossChainRebaseTokenStorage storage $ = _getCrossChainRebaseTokenStorage(); 63 | nonce = $.nonce; 64 | } 65 | 66 | function _setRebaseIndex(uint256) internal pure override { 67 | revert("use: _setRebaseIndex(uint256 index, uint256 nonce)"); 68 | } 69 | 70 | /** 71 | * @notice Sets a new rebase index if the provided nonce is valid and updates the rebase nonce if it's different 72 | * from the current nonce. 73 | * @dev This function checks that the provided nonce is greater than or equal to the current stored nonce before 74 | * setting the new rebase index. If the nonce is greater than the stored nonce, the stored nonce is updated to the 75 | * new value. It relies on `_setRebaseIndex` from the `RebaseTokenUpgradeable` contract to change the rebase index. 76 | * If the provided nonce is less than the current nonce, no changes occur. 77 | * 78 | * @param index The new rebase index to set. 79 | * @param nonce The rebase nonce for this operation, which must be greater than or equal to the current nonce. 80 | */ 81 | function _setRebaseIndex(uint256 index, uint256 nonce) internal virtual { 82 | CrossChainRebaseTokenStorage storage $ = _getCrossChainRebaseTokenStorage(); 83 | uint256 rebaseNonce = $.nonce; 84 | if (nonce >= rebaseNonce) { 85 | RebaseTokenUpgradeable._setRebaseIndex(index); 86 | if (nonce != rebaseNonce) { 87 | $.nonce = nonce; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/layerzero/token/oft/v1/OFTUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC20, ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 6 | 7 | import {IOFT} from "@layerzerolabs/contracts/token/oft/v1/interfaces/IOFT.sol"; 8 | 9 | import {OFTCoreUpgradeable} from "./OFTCoreUpgradeable.sol"; 10 | 11 | /** 12 | * @title OFTUpgradeable 13 | * @dev This contract is an upgradable implementation of LayerZero's Omnichain Fungible Tokens (OFT) standard. 14 | * It inherits the core functionalities from OFTCoreUpgradeable and extends it by adding ERC-20 token functionalities. 15 | * This contract is designed to allow the token to be transacted across different blockchains in a seamless manner. 16 | * 17 | * Key methods include `_debitFrom` and `_creditTo`, which are overridden to handle the actual token transactions. 18 | * This contract is also compatible with the ERC-165 standard for contract introspection. 19 | */ 20 | contract OFTUpgradeable is OFTCoreUpgradeable, ERC20Upgradeable, IOFT { 21 | /** 22 | * @param endpoint The address of the LayerZero endpoint. 23 | * @custom:oz-upgrades-unsafe-allow constructor 24 | */ 25 | constructor(address endpoint) OFTCoreUpgradeable(endpoint) {} 26 | 27 | /** 28 | * @dev Initializes the OFT token with a given name and symbol. 29 | * It sets the state within this contract and also initializes the inherited ERC20 token with the given name and 30 | * symbol. 31 | * This function should only be called during the contract initialization phase. 32 | * 33 | * @param initialOwner The address of the initial owner. 34 | * @param name The name of the token. 35 | * @param symbol The symbol of the token. 36 | */ 37 | function __OFT_init(address initialOwner, string memory name, string memory symbol) internal onlyInitializing { 38 | __OFT_init_unchained(); 39 | __OFTCore_init(initialOwner); 40 | __ERC20_init(name, symbol); 41 | } 42 | 43 | function __OFT_init_unchained() internal onlyInitializing {} 44 | 45 | /** 46 | * @dev Implements the ERC165 standard for contract introspection. 47 | * Extends the functionality to include the interface IDs of IOFT and IERC20, alongside the inherited interfaces. 48 | * 49 | * @param interfaceId The interface identifier, as specified in ERC-165. 50 | * @return `true` if the contract implements the interface represented by `interfaceId`, otherwise `false`. 51 | */ 52 | function supportsInterface(bytes4 interfaceId) 53 | public 54 | view 55 | virtual 56 | override(OFTCoreUpgradeable, IERC165) 57 | returns (bool) 58 | { 59 | return interfaceId == type(IOFT).interfaceId || interfaceId == type(IERC20).interfaceId 60 | || super.supportsInterface(interfaceId); 61 | } 62 | 63 | /** 64 | * @dev Retrieves the address of the OFT token, which is the address of this contract. 65 | * This function is part of the IOFT interface. 66 | * 67 | * @return The address of this OFT token contract. 68 | */ 69 | function token() public view virtual override returns (address) { 70 | return address(this); 71 | } 72 | 73 | /** 74 | * @dev Returns the total circulating supply of OFT tokens. 75 | * In this implementation, it's equivalent to the total supply as managed by the ERC20 standard. 76 | * This function is part of the IOFT interface. 77 | * 78 | * @return The total circulating supply of OFT tokens. 79 | */ 80 | function circulatingSupply() public view virtual override returns (uint256) { 81 | return totalSupply(); 82 | } 83 | 84 | /** 85 | * @dev Handles the token debit operation when sending tokens to another chain. 86 | * Burns the specified amount of tokens from the sender's account. 87 | * 88 | * @param from The address of the token holder. 89 | * @param amount The amount of tokens to be debited (burned). 90 | * @return The actual amount of tokens that were debited. 91 | */ 92 | function _debitFrom(address from, uint16, bytes memory, uint256 amount) 93 | internal 94 | virtual 95 | override 96 | returns (uint256) 97 | { 98 | address spender = _msgSender(); 99 | if (from != spender) _spendAllowance(from, spender, amount); 100 | _burn(from, amount); 101 | return amount; 102 | } 103 | 104 | /** 105 | * @dev Handles the token credit operation when receiving tokens from another chain. 106 | * Mints the specified amount of tokens to the recipient's account. 107 | * 108 | * @param toAddress The address of the recipient. 109 | * @param amount The amount of tokens to be credited (minted). 110 | * @return The actual amount of tokens that were credited. 111 | */ 112 | function _creditTo(uint16, address toAddress, uint256 amount) internal virtual override returns (uint256) { 113 | _mint(toAddress, amount); 114 | return amount; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/layerzero/lzApp/NonblockingLzAppUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {ExcessivelySafeCall} from "@layerzerolabs/contracts/libraries/ExcessivelySafeCall.sol"; 5 | 6 | import {LzAppUpgradeable} from "./LzAppUpgradeable.sol"; 7 | 8 | /** 9 | * @title Nonblocking LayerZero Application 10 | * @dev This contract extends LzAppUpgradeable and modifies its behavior to be non-blocking. Failed messages are caught 11 | * and stored for future retries, ensuring that the message channel remains unblocked. This contract serves as an 12 | * abstract base class and should be extended by specific implementations. 13 | * 14 | * Note: If the `srcAddress` is not configured properly, it will still block the message pathway from (`srcChainId`, 15 | * `srcAddress`). 16 | */ 17 | abstract contract NonblockingLzAppUpgradeable is LzAppUpgradeable { 18 | using ExcessivelySafeCall for address; 19 | 20 | event MessageFailed(uint16 srcChainId, bytes srcAddress, uint64 nonce, bytes payload, bytes reason); 21 | event RetryMessageSuccess(uint16 srcChainId, bytes srcAddress, uint64 nonce, bytes32 payloadHash); 22 | 23 | /// @custom:storage-location erc7201:layerzero.storage.NonblockingLzApp 24 | struct NonblockingLzAppStorage { 25 | mapping(uint16 => mapping(bytes => mapping(uint64 => bytes32))) failedMessages; 26 | } 27 | 28 | // keccak256(abi.encode(uint256(keccak256("layerzero.storage.NonblockingLzApp")) - 1)) & ~bytes32(uint256(0xff)) 29 | bytes32 private constant NonblockingLzAppStorageLocation = 30 | 0xe5a86fa43fa85f564c84895bd4f80ec5c29d03a57a0c1f7cb91d2cc05b4d8600; 31 | 32 | function _getNonblockingLzAppStorage() private pure returns (NonblockingLzAppStorage storage $) { 33 | // slither-disable-next-line assembly 34 | assembly { 35 | $.slot := NonblockingLzAppStorageLocation 36 | } 37 | } 38 | 39 | /** 40 | * @param endpoint The address of the LayerZero endpoint contract. 41 | * @custom:oz-upgrades-unsafe-allow constructor 42 | */ 43 | constructor(address endpoint) LzAppUpgradeable(endpoint) {} 44 | 45 | /** 46 | * @dev Initializes the contract, setting the initial owner and endpoint addresses. 47 | * Also chains the initialization process with the base `LzAppUpgradeable` contract. 48 | * 49 | * Requirements: 50 | * - Can only be called during contract initialization. 51 | * 52 | * @param initialOwner The address that will initially own the contract. 53 | */ 54 | function __NonblockingLzApp_init(address initialOwner) internal onlyInitializing { 55 | __NonblockingLzApp_init_unchained(); 56 | __LzApp_init(initialOwner); 57 | } 58 | 59 | function __NonblockingLzApp_init_unchained() internal onlyInitializing {} 60 | 61 | /** 62 | * @dev Retrieves the hash of the payload of a failed message for a given source chain, source address, and nonce. 63 | * 64 | * @param srcChainId The ID of the source chain where the message originated. 65 | * @param srcAddress The address on the source chain where the message originated. 66 | * @param nonce The nonce of the failed message. 67 | * @return payloadHash The hash of the payload of the failed message. 68 | */ 69 | function failedMessages(uint16 srcChainId, bytes calldata srcAddress, uint64 nonce) 70 | external 71 | view 72 | returns (bytes32 payloadHash) 73 | { 74 | NonblockingLzAppStorage storage $ = _getNonblockingLzAppStorage(); 75 | return $.failedMessages[srcChainId][srcAddress][nonce]; 76 | } 77 | 78 | /** 79 | * @dev Internal function that receives LayerZero messages and attempts to process them in a non-blocking manner. 80 | * If processing fails, the message is stored for future retries. 81 | * 82 | * @param srcChainId The ID of the source chain where the message originated. 83 | * @param srcAddress The address on the source chain where the message originated. 84 | * @param nonce The nonce of the message. 85 | * @param payload The payload of the message. 86 | */ 87 | function _blockingLzReceive(uint16 srcChainId, bytes memory srcAddress, uint64 nonce, bytes memory payload) 88 | internal 89 | virtual 90 | override 91 | { 92 | (bool success, bytes memory reason) = address(this).excessivelySafeCall( 93 | gasleft(), 94 | 150, 95 | abi.encodeWithSelector(this.nonblockingLzReceive.selector, srcChainId, srcAddress, nonce, payload) 96 | ); 97 | // try-catch all errors/exceptions 98 | if (!success) { 99 | _storeFailedMessage(srcChainId, srcAddress, nonce, payload, reason); 100 | } 101 | } 102 | 103 | /** 104 | * @dev Internal function to store the details of a failed message for future retries. 105 | * 106 | * @param srcChainId The ID of the source chain where the message originated. 107 | * @param srcAddress The address on the source chain where the message originated. 108 | * @param nonce The nonce of the failed message. 109 | * @param payload The payload of the failed message. 110 | * @param reason The reason for the message's failure. 111 | */ 112 | function _storeFailedMessage( 113 | uint16 srcChainId, 114 | bytes memory srcAddress, 115 | uint64 nonce, 116 | bytes memory payload, 117 | bytes memory reason 118 | ) internal virtual { 119 | NonblockingLzAppStorage storage $ = _getNonblockingLzAppStorage(); 120 | $.failedMessages[srcChainId][srcAddress][nonce] = keccak256(payload); 121 | emit MessageFailed(srcChainId, srcAddress, nonce, payload, reason); 122 | } 123 | 124 | /** 125 | * @dev Public wrapper function for handling incoming LayerZero messages in a non-blocking manner. 126 | * It internally calls the `_nonblockingLzReceive` function, which should be overridden in derived contracts. 127 | * 128 | * Requirements: 129 | * - The caller must be the contract itself. 130 | * 131 | * @param srcChainId The ID of the source chain where the message originated. 132 | * @param srcAddress The address on the source chain where the message originated. 133 | * @param nonce The nonce of the message. 134 | * @param payload The payload of the message. 135 | */ 136 | function nonblockingLzReceive(uint16 srcChainId, bytes calldata srcAddress, uint64 nonce, bytes calldata payload) 137 | public 138 | virtual 139 | { 140 | // only internal transaction 141 | require(_msgSender() == address(this), "NonblockingLzApp: caller must be LzApp"); 142 | _nonblockingLzReceive(srcChainId, srcAddress, nonce, payload); 143 | } 144 | 145 | /** 146 | * @dev Internal function that should be overridden in derived contracts to implement the logic 147 | * for processing incoming LayerZero messages in a non-blocking manner. 148 | * 149 | * @param srcChainId The ID of the source chain where the message originated. 150 | * @param srcAddress The address on the source chain where the message originated. 151 | * @param nonce The nonce of the message. 152 | * @param payload The payload of the message. 153 | */ 154 | function _nonblockingLzReceive(uint16 srcChainId, bytes memory srcAddress, uint64 nonce, bytes memory payload) 155 | internal 156 | virtual; 157 | 158 | /** 159 | * @dev Allows for the manual retry of a previously failed message. 160 | * 161 | * Requirements: 162 | * - There must be a stored failed message matching the provided parameters. 163 | * - The payload hash must match the stored failed message. 164 | * 165 | * @param srcChainId The ID of the source chain where the failed message originated. 166 | * @param srcAddress The address on the source chain where the failed message originated. 167 | * @param nonce The nonce of the failed message. 168 | * @param payload The payload of the failed message. 169 | */ 170 | function retryMessage(uint16 srcChainId, bytes calldata srcAddress, uint64 nonce, bytes calldata payload) 171 | public 172 | payable 173 | virtual 174 | { 175 | NonblockingLzAppStorage storage $ = _getNonblockingLzAppStorage(); 176 | mapping(uint64 => bytes32) storage _failedMessages = $.failedMessages[srcChainId][srcAddress]; 177 | 178 | // get the payload hash value 179 | bytes32 payloadHash = _failedMessages[nonce]; 180 | 181 | // assert there is message to retry 182 | require(payloadHash != bytes32(0), "NonblockingLzApp: no stored message"); 183 | require(keccak256(payload) == payloadHash, "NonblockingLzApp: invalid payload"); 184 | 185 | // clear the stored message 186 | _failedMessages[nonce] = bytes32(0); 187 | 188 | // execute the message. revert if it fails again 189 | _nonblockingLzReceive(srcChainId, srcAddress, nonce, payload); 190 | emit RetryMessageSuccess(srcChainId, srcAddress, nonce, payloadHash); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/tokens/LayerZeroRebaseTokenUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 6 | 7 | import {BytesLib} from "@layerzerolabs/contracts/libraries/BytesLib.sol"; 8 | import {OFTUpgradeable} from "@layerzerolabs/contracts-upgradeable/token/oft/v1/OFTUpgradeable.sol"; 9 | 10 | import {RebaseTokenMath} from "../libraries/RebaseTokenMath.sol"; 11 | import {CrossChainRebaseTokenUpgradeable} from "./CrossChainRebaseTokenUpgradeable.sol"; 12 | import {RebaseTokenUpgradeable} from "./RebaseTokenUpgradeable.sol"; 13 | 14 | /** 15 | * @title LayerZeroRebaseTokenUpgradeable 16 | * @author Caesar LaVey 17 | * @notice This contract extends the functionality of `CrossChainRebaseTokenUpgradeable` and implements 18 | * `OFTUpgradeable`. It is designed to support cross-chain rebase token transfers and operations in a LayerZero network. 19 | * 20 | * @dev The contract introduces a new struct, `Message`, to encapsulate the information required for cross-chain 21 | * transfers. This includes shares, the rebase index, and the rebase nonce. 22 | * 23 | * The contract overrides various functions like `totalSupply`, `balanceOf`, and `_update` to utilize the base 24 | * functionalities from `RebaseTokenUpgradeable`. 25 | * 26 | * It also implements specific functions like `_debitFrom` and `_creditTo` to handle LayerZero specific operations. 27 | */ 28 | abstract contract LayerZeroRebaseTokenUpgradeable is CrossChainRebaseTokenUpgradeable, OFTUpgradeable { 29 | using BytesLib for bytes; 30 | using RebaseTokenMath for uint256; 31 | 32 | struct Message { 33 | uint256 shares; 34 | uint256 rebaseIndex; 35 | uint256 nonce; 36 | } 37 | 38 | error CannotBridgeWhenOptedOut(address account); 39 | 40 | /** 41 | * @param endpoint The endpoint for Layer Zero operations. 42 | * @custom:oz-upgrades-unsafe-allow constructor 43 | */ 44 | constructor(address endpoint) OFTUpgradeable(endpoint) {} 45 | 46 | /** 47 | * @notice Initializes the LayerZeroRebaseTokenUpgradeable contract. 48 | * @dev This function is intended to be called once during the contract's deployment. It chains initialization logic 49 | * from `__LayerZeroRebaseToken_init_unchained`, `__CrossChainRebaseToken_init_unchained`, and `__OFT_init`. 50 | * 51 | * @param initialOwner The initial owner of the token contract. 52 | * @param name The name of the token. 53 | * @param symbol The symbol of the token. 54 | */ 55 | function __LayerZeroRebaseToken_init(address initialOwner, string memory name, string memory symbol) 56 | internal 57 | onlyInitializing 58 | { 59 | __LayerZeroRebaseToken_init_unchained(); 60 | __CrossChainRebaseToken_init_unchained(); 61 | __OFT_init(initialOwner, name, symbol); 62 | } 63 | 64 | function __LayerZeroRebaseToken_init_unchained() internal onlyInitializing {} 65 | 66 | function balanceOf(address account) 67 | public 68 | view 69 | override(IERC20, ERC20Upgradeable, RebaseTokenUpgradeable) 70 | returns (uint256) 71 | { 72 | return RebaseTokenUpgradeable.balanceOf(account); 73 | } 74 | 75 | function totalSupply() public view override(IERC20, ERC20Upgradeable, RebaseTokenUpgradeable) returns (uint256) { 76 | return RebaseTokenUpgradeable.totalSupply(); 77 | } 78 | 79 | function _update(address from, address to, uint256 amount) 80 | internal 81 | virtual 82 | override(ERC20Upgradeable, RebaseTokenUpgradeable) 83 | { 84 | RebaseTokenUpgradeable._update(from, to, amount); 85 | } 86 | 87 | /** 88 | * @notice Debits a specified amount of tokens from an account. 89 | * @dev This function performs a series of checks and operations to debit tokens from an account. If the account 90 | * has not opted out of rebasing, it calculates the share equivalent of the specified amount and updates the 91 | * internal state accordingly. If the operation occurs on the main chain, the tokens are moved to the contract's 92 | * address. Otherwise, the tokens are burned. 93 | * 94 | * @param from The address from which the tokens will be debited. 95 | * @param amount The amount to debit from the account. 96 | * @return shares The share equivalent of the debited amount. 97 | */ 98 | function _debitFrom(address from, uint16, bytes memory, uint256 amount) 99 | internal 100 | virtual 101 | override 102 | returns (uint256 shares) 103 | { 104 | shares = _transferableShares(amount, from); 105 | if (from != msg.sender) { 106 | _spendAllowance(from, msg.sender, amount); 107 | } 108 | if (isMainChain) { 109 | _update(from, address(this), amount); 110 | } else { 111 | _update(from, address(0), amount); 112 | } 113 | } 114 | 115 | /** 116 | * @notice Credits a specified number of tokens to an account. 117 | * 118 | * @param to The address to which the shares will be credited. 119 | * @param shares The number of shares to credit to the account. 120 | * @return amount The token equivalent of the credited shares. 121 | */ 122 | function _creditTo(uint16, address to, uint256 shares) internal virtual override returns (uint256 amount) { 123 | amount = shares.toTokens(rebaseIndex()); 124 | if (isMainChain) { 125 | _update(address(this), to, amount); 126 | } else { 127 | _update(address(0), to, amount); 128 | } 129 | return amount; 130 | } 131 | 132 | /** 133 | * @notice Initiates the sending of tokens to another chain. 134 | * @dev This function prepares a message containing the shares, rebase index, and nonce. It then uses LayerZero's 135 | * send functionality to send the tokens to the destination chain. The function checks adapter parameters and emits 136 | * a `SendToChain` event upon successful execution. 137 | * 138 | * @param from The address from which tokens are sent. 139 | * @param dstChainId The destination chain ID. 140 | * @param toAddress The address on the destination chain to which tokens will be sent. 141 | * @param amount The amount of tokens to send. 142 | * @param refundAddress The address for any refunds. 143 | * @param zroPaymentAddress The address for ZRO payment. 144 | * @param adapterParams Additional parameters for the adapter. 145 | */ 146 | function _send( 147 | address from, 148 | uint16 dstChainId, 149 | bytes memory toAddress, 150 | uint256 amount, 151 | address payable refundAddress, 152 | address zroPaymentAddress, 153 | bytes memory adapterParams 154 | ) internal virtual override { 155 | if (optedOut(from)) { 156 | // tokens cannot be bridged if the account has opted out of rebasing 157 | revert CannotBridgeWhenOptedOut(from); 158 | } 159 | 160 | _checkAdapterParams(dstChainId, PT_SEND, adapterParams, NO_EXTRA_GAS); 161 | 162 | Message memory message = Message({ 163 | shares: _debitFrom(from, dstChainId, toAddress, amount), 164 | rebaseIndex: rebaseIndex(), 165 | nonce: _rebaseNonce() 166 | }); 167 | 168 | emit SendToChain(dstChainId, from, toAddress, message.shares.toTokens(message.rebaseIndex)); 169 | 170 | bytes memory lzPayload = abi.encode(PT_SEND, msg.sender, from, toAddress, message); 171 | _lzSend(dstChainId, lzPayload, refundAddress, zroPaymentAddress, adapterParams, msg.value); 172 | } 173 | 174 | /** 175 | * @notice Acknowledges the receipt of tokens from another chain and credits the correct amount to the recipient's 176 | * address. 177 | * @dev Upon receiving a payload, this function decodes it to extract the destination address and the message 178 | * content, which includes shares, rebase index, and nonce. If the current chain is not the main chain, it updates 179 | * the rebase index and nonce accordingly. Then, it credits the token shares to the recipient's address and emits a 180 | * `ReceiveFromChain` event. 181 | * 182 | * The function assumes that `_setRebaseIndex` handles the correctness of the rebase index and nonce update. 183 | * 184 | * @param srcChainId The source chain ID from which tokens are received. 185 | * @param srcAddressBytes The address on the source chain from which the message originated. 186 | * @param payload The payload containing the encoded destination address and message with shares, rebase index, and 187 | * nonce. 188 | */ 189 | function _sendAck(uint16 srcChainId, bytes memory srcAddressBytes, uint64, bytes memory payload) 190 | internal 191 | virtual 192 | override 193 | { 194 | (, address initiator, address from, bytes memory toAddressBytes, Message memory message) = 195 | abi.decode(payload, (uint16, address, address, bytes, Message)); 196 | 197 | if (!isMainChain) { 198 | _setRebaseIndex(message.rebaseIndex, message.nonce); 199 | } 200 | 201 | address src = srcAddressBytes.toAddress(0); 202 | address to = toAddressBytes.toAddress(0); 203 | uint256 amount; 204 | 205 | amount = _creditTo(srcChainId, to, message.shares); 206 | 207 | _tryNotifyReceiver(srcChainId, initiator, from, src, to, amount); 208 | 209 | emit ReceiveFromChain(srcChainId, to, amount); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/layerzero/token/oft/v1/OFTCoreUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC165, ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; 5 | 6 | import {IOFTCore} from "@layerzerolabs/contracts/token/oft/v1/interfaces/IOFTCore.sol"; 7 | import {BytesLib} from "@layerzerolabs/contracts/libraries/BytesLib.sol"; 8 | 9 | import {NonblockingLzAppUpgradeable} from "../../../lzApp/NonblockingLzAppUpgradeable.sol"; 10 | 11 | import {IOFTSubscriber} from "../interfaces/IOFTSubscriber.sol"; 12 | 13 | /** 14 | * @title OFTCoreUpgradeable 15 | * @dev This contract extends NonblockingLzAppUpgradeable to provide a core implementation for OFT (On-Chain Forwarding 16 | * Token). It introduces packet types, custom adapter params, and methods for sending and receiving tokens across 17 | * chains. 18 | * 19 | * This contract is intended to be inherited by other contracts that implement specific token logic. 20 | * 21 | * Packet Types: 22 | * - PT_SEND: Packet type for sending tokens. Value is 0. 23 | * 24 | * Custom Adapter Params: 25 | * - The contract allows for the use of custom adapter parameters which affect the gas usage for cross-chain operations. 26 | * 27 | * Storage: 28 | * - useCustomAdapterParams: A flag to indicate whether to use custom adapter parameters. 29 | */ 30 | abstract contract OFTCoreUpgradeable is NonblockingLzAppUpgradeable, ERC165, IOFTCore { 31 | using BytesLib for bytes; 32 | 33 | uint256 public constant NO_EXTRA_GAS = 0; 34 | 35 | // packet type 36 | uint16 public constant PT_SEND = 0; 37 | 38 | /// @custom:storage-location erc7201:layerzero.storage.OFTCore 39 | struct OFTCoreStorage { 40 | bool useCustomAdapterParams; 41 | } 42 | 43 | // keccak256(abi.encode(uint256(keccak256("layerzero.storage.OFTCore")) - 1)) & ~bytes32(uint256(0xff)) 44 | bytes32 private constant OFTCoreStorageLocation = 0x822492242235517548c4a8cf040400e3c0daf5b82af652ed16dce4fa3ae72800; 45 | 46 | function _getOFTCoreStorage() private pure returns (OFTCoreStorage storage $) { 47 | // slither-disable-next-line assembly 48 | assembly { 49 | $.slot := OFTCoreStorageLocation 50 | } 51 | } 52 | 53 | /** 54 | * @param endpoint The address of the LayerZero endpoint. 55 | * @custom:oz-upgrades-unsafe-allow constructor 56 | */ 57 | constructor(address endpoint) NonblockingLzAppUpgradeable(endpoint) {} 58 | 59 | /** 60 | * @dev Initializes the contract state for `OFTCoreUpgradeable`. 61 | * Calls the initialization functions of parent contracts. 62 | * 63 | * @param initialOwner The address of the initial owner. 64 | */ 65 | function __OFTCore_init(address initialOwner) internal onlyInitializing { 66 | __OFTCore_init_unchained(); 67 | __NonblockingLzApp_init(initialOwner); 68 | } 69 | 70 | function __OFTCore_init_unchained() internal onlyInitializing {} 71 | 72 | /** 73 | * @dev Checks if the contract supports a given interface ID. 74 | * Overrides the implementation in ERC165 to include support for IOFTCore. 75 | * 76 | * @param interfaceId The ID of the interface to check. 77 | * @return bool `true` if the contract supports the given interface ID, `false` otherwise. 78 | */ 79 | function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { 80 | return interfaceId == type(IOFTCore).interfaceId || super.supportsInterface(interfaceId); 81 | } 82 | 83 | /** 84 | * @dev Estimates the fee required for sending tokens to a different chain. 85 | * This function is part of the IOFTCore interface. 86 | * 87 | * @param dstChainId The ID of the destination chain. 88 | * @param toAddress The address to which the tokens will be sent on the destination chain. 89 | * @param amount The amount of tokens to send. 90 | * @param useZro Flag indicating whether to use ZRO for payment. 91 | * @param adapterParams Additional parameters for the adapter. 92 | * @return nativeFee The estimated native chain fee. 93 | * @return zroFee The estimated ZRO fee. 94 | */ 95 | function estimateSendFee( 96 | uint16 dstChainId, 97 | bytes calldata toAddress, 98 | uint256 amount, 99 | bool useZro, 100 | bytes calldata adapterParams 101 | ) public view virtual override returns (uint256 nativeFee, uint256 zroFee) { 102 | // mock the payload for sendFrom() 103 | bytes memory payload = abi.encode(PT_SEND, toAddress, amount); 104 | return lzEndpoint.estimateFees(dstChainId, address(this), payload, useZro, adapterParams); 105 | } 106 | 107 | /** 108 | * @dev Sends tokens from a given address to a destination address on another chain. 109 | * This function is part of the IOFTCore interface. 110 | * 111 | * @param from The address from which tokens will be sent. 112 | * @param dstChainId The ID of the destination chain. 113 | * @param toAddress The address on the destination chain to which tokens will be sent. 114 | * @param amount The amount of tokens to send. 115 | * @param refundAddress The address where any excess native fee will be refunded. 116 | * @param zroPaymentAddress The address used for ZRO payments, if applicable. 117 | * @param adapterParams Additional parameters for the adapter. 118 | */ 119 | function sendFrom( 120 | address from, 121 | uint16 dstChainId, 122 | bytes calldata toAddress, 123 | uint256 amount, 124 | address payable refundAddress, 125 | address zroPaymentAddress, 126 | bytes calldata adapterParams 127 | ) public payable virtual override { 128 | _send(from, dstChainId, toAddress, amount, refundAddress, zroPaymentAddress, adapterParams); 129 | } 130 | 131 | /** 132 | * @dev Toggles the use of custom adapter parameters. 133 | * When enabled, the contract will check gas limits based on the provided adapter parameters. 134 | * 135 | * @param useCustomAdapterParams Flag indicating whether to use custom adapter parameters. 136 | */ 137 | function setUseCustomAdapterParams(bool useCustomAdapterParams) public virtual onlyOwner { 138 | OFTCoreStorage storage $ = _getOFTCoreStorage(); 139 | $.useCustomAdapterParams = useCustomAdapterParams; 140 | emit SetUseCustomAdapterParams(useCustomAdapterParams); 141 | } 142 | 143 | /** 144 | * @dev Handles incoming messages from other chains in a non-blocking fashion. 145 | * This function overrides the abstract implementation in NonblockingLzAppUpgradeable. 146 | * 147 | * @param srcChainId The ID of the source chain. 148 | * @param srcAddress The address on the source chain from which the message originated. 149 | * @param nonce A unique identifier for the message. 150 | * @param payload The actual data sent from the source chain. 151 | */ 152 | function _nonblockingLzReceive(uint16 srcChainId, bytes memory srcAddress, uint64 nonce, bytes memory payload) 153 | internal 154 | virtual 155 | override 156 | { 157 | uint16 packetType; 158 | 159 | // slither-disable-next-line assembly 160 | assembly { 161 | packetType := mload(add(payload, 32)) 162 | } 163 | 164 | if (packetType == PT_SEND) { 165 | _sendAck(srcChainId, srcAddress, nonce, payload); 166 | } else { 167 | revert("OFTCore: unknown packet type"); 168 | } 169 | } 170 | 171 | /** 172 | * @dev Performs the actual sending of tokens to a destination chain. 173 | * This internal function is called by the public wrapper `sendFrom`. 174 | * 175 | * @param from The address from which tokens are sent. 176 | * @param dstChainId The ID of the destination chain. 177 | * @param toAddress The address on the destination chain where tokens will be sent. 178 | * @param amount The amount of tokens to send. 179 | * @param refundAddress The address for refunding any excess native fee. 180 | * @param zroPaymentAddress The address for ZRO payment, if applicable. 181 | * @param adapterParams Additional parameters for the adapter. 182 | */ 183 | function _send( 184 | address from, 185 | uint16 dstChainId, 186 | bytes memory toAddress, 187 | uint256 amount, 188 | address payable refundAddress, 189 | address zroPaymentAddress, 190 | bytes memory adapterParams 191 | ) internal virtual { 192 | _checkAdapterParams(dstChainId, PT_SEND, adapterParams, NO_EXTRA_GAS); 193 | 194 | amount = _debitFrom(from, dstChainId, toAddress, amount); 195 | 196 | bytes memory lzPayload = abi.encode(PT_SEND, toAddress, amount); 197 | _lzSend(dstChainId, lzPayload, refundAddress, zroPaymentAddress, adapterParams, msg.value); 198 | 199 | emit SendToChain(dstChainId, from, toAddress, amount); 200 | } 201 | 202 | /** 203 | * @dev Acknowledges the reception of tokens sent from another chain. 204 | * This function is called internally when a PT_SEND packet type is received. 205 | * 206 | * @param srcChainId The ID of the source chain from which the tokens were sent. 207 | * @param payload The payload containing the details of the sent tokens. 208 | */ 209 | function _sendAck(uint16 srcChainId, bytes memory, uint64, bytes memory payload) internal virtual { 210 | (, bytes memory toAddressBytes, uint256 amount) = abi.decode(payload, (uint16, bytes, uint256)); 211 | 212 | address to = toAddressBytes.toAddress(0); 213 | 214 | amount = _creditTo(srcChainId, to, amount); 215 | 216 | emit ReceiveFromChain(srcChainId, to, amount); 217 | } 218 | 219 | /** 220 | * @dev Validates the adapter parameters for sending tokens. 221 | * This function can be configured to either enforce a gas limit or to accept custom parameters. 222 | * 223 | * @param dstChainId The ID of the destination chain. 224 | * @param pkType The packet type of the message. 225 | * @param adapterParams The additional parameters for the adapter. 226 | * @param extraGas The extra gas that may be needed for execution. 227 | */ 228 | function _checkAdapterParams(uint16 dstChainId, uint16 pkType, bytes memory adapterParams, uint256 extraGas) 229 | internal 230 | virtual 231 | { 232 | OFTCoreStorage storage $ = _getOFTCoreStorage(); 233 | if ($.useCustomAdapterParams) { 234 | _checkGasLimit(dstChainId, pkType, adapterParams, extraGas); 235 | } else { 236 | require(adapterParams.length == 0, "OFTCore: _adapterParams must be empty."); 237 | } 238 | } 239 | 240 | /** 241 | * @dev Debits an amount of tokens from the specified address. 242 | * This is an internal function that should be overridden to handle the actual token transfer logic. 243 | * 244 | * @param from The address from which tokens will be debited. 245 | * @param dstChainId The ID of the destination chain. 246 | * @param toAddress The encoded destination address on the target chain. 247 | * @param amount The amount of tokens to debit. 248 | * @return The final amount of tokens that were debited. This allows for potential adjustments. 249 | */ 250 | function _debitFrom(address from, uint16 dstChainId, bytes memory toAddress, uint256 amount) 251 | internal 252 | virtual 253 | returns (uint256); 254 | 255 | /** 256 | * @dev Credits an amount of tokens to a specific address. 257 | * This is an internal function that should be overridden to handle the actual token crediting logic. 258 | * 259 | * @param srcChainId The ID of the source chain from which the tokens were sent. 260 | * @param toAddress The address to which tokens will be credited. 261 | * @param amount The amount of tokens to credit. 262 | * @return The final amount of tokens that were credited. This allows for potential adjustments. 263 | */ 264 | function _creditTo(uint16 srcChainId, address toAddress, uint256 amount) internal virtual returns (uint256); 265 | } 266 | -------------------------------------------------------------------------------- /src/tokens/RebaseTokenUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | 6 | import {RebaseTokenMath} from "../libraries/RebaseTokenMath.sol"; 7 | 8 | /** 9 | * @title RebaseTokenUpgradeable 10 | * @author Caesar LaVey 11 | * @notice This is an upgradeable ERC20 token contract that introduces a rebase mechanism and allows accounts to opt out 12 | * of rebasing. The contract uses an index-based approach to implement rebasing, allowing for more gas-efficient 13 | * calculations. 14 | * 15 | * @dev The contract inherits from OpenZeppelin's ERC20Upgradeable and utilizes the RebaseTokenMath library for its 16 | * arithmetic operations. It introduces a new struct "RebaseTokenStorage" to manage its state. The state variables 17 | * include `rebaseIndex`, which is the current index value for rebasing, and `totalShares`, which is the total number of 18 | * index-based shares in circulation. 19 | * 20 | * The contract makes use of low-level Solidity features like assembly for optimized storage handling. It adheres to the 21 | * Checks-Effects-Interactions design pattern where applicable and emits events for significant state changes. 22 | */ 23 | abstract contract RebaseTokenUpgradeable is ERC20Upgradeable { 24 | using RebaseTokenMath for uint256; 25 | 26 | /// @custom:storage-location erc7201:tangible.storage.RebaseToken 27 | struct RebaseTokenStorage { 28 | uint256 rebaseIndex; 29 | uint256 totalShares; 30 | mapping(address => uint256) shares; 31 | mapping(address => bool) optOut; 32 | } 33 | 34 | // keccak256(abi.encode(uint256(keccak256("tangible.storage.RebaseToken")) - 1)) & ~bytes32(uint256(0xff)) 35 | bytes32 private constant RebaseTokenStorageLocation = 36 | 0x8a0c9d8ec1d9f8b365393c36404b40a33f47675e34246a2e186fbefd5ecd3b00; 37 | 38 | function _getRebaseTokenStorage() private pure returns (RebaseTokenStorage storage $) { 39 | // slither-disable-next-line assembly 40 | assembly { 41 | $.slot := RebaseTokenStorageLocation 42 | } 43 | } 44 | 45 | event RebaseIndexUpdated(address updatedBy, uint256 index, uint256 totalSupplyBefore, uint256 totalSupplyAfter); 46 | event RebaseEnabled(address indexed account); 47 | event RebaseDisabled(address indexed account); 48 | 49 | error AmountExceedsBalance(address account, uint256 balance, uint256 amount); 50 | 51 | error RebaseOverflow(); 52 | error SupplyOverflow(); 53 | 54 | /** 55 | * @notice Initializes the RebaseTokenUpgradeable contract. 56 | * @dev This function should only be called once during the contract deployment. It internally calls 57 | * `__RebaseToken_init_unchained` for any further initializations and `__ERC20_init` to initialize the inherited 58 | * ERC20 contract. 59 | * 60 | * @param name The name of the token. 61 | * @param symbol The symbol of the token. 62 | */ 63 | function __RebaseToken_init(string memory name, string memory symbol) internal onlyInitializing { 64 | __RebaseToken_init_unchained(); 65 | __ERC20_init(name, symbol); 66 | } 67 | 68 | function __RebaseToken_init_unchained() internal onlyInitializing {} 69 | 70 | /** 71 | * @notice Enables or disables rebasing for a specific account. 72 | * @dev This function updates the `optOut` mapping for the `account` based on the `disable` flag. It also adjusts 73 | * the shares and token balances accordingly if the account has a non-zero balance. This function emits either a 74 | * `RebaseEnabled` or `RebaseDisabled` event. 75 | * 76 | * @param account The address of the account for which rebasing is to be enabled or disabled. 77 | * @param disable A boolean flag indicating whether to disable (true) or enable (false) rebasing for the account. 78 | */ 79 | function _disableRebase(address account, bool disable) internal { 80 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 81 | if ($.optOut[account] != disable) { 82 | uint256 balance = balanceOf(account); 83 | if (balance != 0) { 84 | if (disable) { 85 | RebaseTokenUpgradeable._update(account, address(0), balance); 86 | } else { 87 | ERC20Upgradeable._update(account, address(0), balance); 88 | } 89 | } 90 | $.optOut[account] = disable; 91 | if (balance != 0) { 92 | if (disable) { 93 | ERC20Upgradeable._update(address(0), account, balance); 94 | } else { 95 | RebaseTokenUpgradeable._update(address(0), account, balance); 96 | } 97 | } 98 | if (disable) emit RebaseDisabled(account); 99 | else emit RebaseEnabled(account); 100 | } 101 | } 102 | 103 | /** 104 | * @notice Checks if rebasing is disabled for a specific account. 105 | * @dev This function fetches the `optOut` status from the contract's storage for the specified `account`. 106 | * 107 | * @param account The address of the account to check. 108 | * @return disabled A boolean indicating whether rebasing is disabled (true) or enabled (false) for the account. 109 | */ 110 | function _isRebaseDisabled(address account) internal view returns (bool disabled) { 111 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 112 | disabled = $.optOut[account]; 113 | } 114 | 115 | /** 116 | * @notice Returns the current rebase index of the token. 117 | * @dev This function fetches the `rebaseIndex` from the contract's storage and returns it. The returned index is 118 | * used in various calculations related to token rebasing. 119 | * 120 | * @return index The current rebase index. 121 | */ 122 | function rebaseIndex() public view returns (uint256 index) { 123 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 124 | index = $.rebaseIndex; 125 | } 126 | 127 | /** 128 | * @notice Returns the balance of a specific account, adjusted for the current rebase index. 129 | * @dev This function fetches the `shares` and `rebaseIndex` from the contract's storage for the specified account. 130 | * It then calculates the balance in tokens by converting these shares to their equivalent token amount using the 131 | * current rebase index. 132 | * 133 | * @param account The address of the account whose balance is to be fetched. 134 | * @return balance The balance of the specified account in tokens. 135 | */ 136 | function balanceOf(address account) public view virtual override returns (uint256 balance) { 137 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 138 | if ($.optOut[account]) { 139 | balance = ERC20Upgradeable.balanceOf(account); 140 | } else { 141 | balance = $.shares[account].toTokens($.rebaseIndex); 142 | } 143 | } 144 | 145 | /** 146 | * @notice Returns whether rebasing is disabled for a specific account. 147 | * @param account The address of the account to check. 148 | */ 149 | function optedOut(address account) public view returns (bool) { 150 | return _isRebaseDisabled(account); 151 | } 152 | 153 | /** 154 | * @notice Returns the total supply of the token, taking into account the current rebase index. 155 | * @dev This function fetches the `totalShares` and `rebaseIndex` from the contract's storage. It then calculates 156 | * the total supply of tokens by converting these shares to their equivalent token amount using the current rebase 157 | * index. 158 | * 159 | * @return supply The total supply of tokens. 160 | */ 161 | function totalSupply() public view virtual override returns (uint256 supply) { 162 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 163 | supply = $.totalShares.toTokens($.rebaseIndex) + ERC20Upgradeable.totalSupply(); 164 | } 165 | 166 | /** 167 | * @notice Sets a new rebase index for the token. 168 | * @dev This function updates the `rebaseIndex` state variable if the new index differs from the current one. It 169 | * also performs a check for any potential overflow conditions that could occur with the new index. Emits a 170 | * `RebaseIndexUpdated` event upon successful update. 171 | * 172 | * @param index The new rebase index to set. 173 | */ 174 | function _setRebaseIndex(uint256 index) internal virtual { 175 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 176 | uint256 currentIndex = $.rebaseIndex; 177 | if (currentIndex != index) { 178 | $.rebaseIndex = index; 179 | _checkRebaseOverflow($.totalShares, index); 180 | uint256 constantSupply = ERC20Upgradeable.totalSupply(); 181 | uint256 totalSupplyBefore = $.totalShares.toTokens(currentIndex) + constantSupply; 182 | uint256 totalSupplyAfter = $.totalShares.toTokens(index) + constantSupply; 183 | emit RebaseIndexUpdated(msg.sender, index, totalSupplyBefore, totalSupplyAfter); 184 | } 185 | } 186 | 187 | /** 188 | * @notice Calculates the number of transferable shares for a given amount and account. 189 | * @dev This function fetches the current rebase index and the shares held by the `from` address. It then converts 190 | * these shares to the equivalent token balance. If the `amount` to be transferred exceeds this balance, the 191 | * function reverts with an `AmountExceedsBalance` error. Otherwise, it calculates the number of shares equivalent 192 | * to the `amount` to be transferred. 193 | * 194 | * @param amount The amount of tokens to be transferred. 195 | * @param from The address from which the tokens are to be transferred. 196 | * @return shares The number of shares equivalent to the `amount` to be transferred. 197 | */ 198 | function _transferableShares(uint256 amount, address from) internal view returns (uint256 shares) { 199 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 200 | shares = $.shares[from]; 201 | uint256 index = $.rebaseIndex; 202 | uint256 balance = shares.toTokens(index); 203 | if (amount > balance) { 204 | revert AmountExceedsBalance(from, balance, amount); 205 | } 206 | if (amount < balance) { 207 | shares = amount.toShares(index); 208 | } 209 | } 210 | 211 | /** 212 | * @notice Updates the state of the contract during token transfers, mints, or burns. 213 | * @dev This function adjusts the `totalShares` and individual `shares` of `from` and `to` addresses based on their 214 | * rebasing status (`optOut`). When both parties have opted out of rebasing, the standard ERC20 `_update` is called 215 | * instead. It performs overflow and underflow checks where necessary and delegates to the parent function when 216 | * opt-out applies. 217 | * 218 | * @param from The address from which tokens are transferred or burned. Address(0) implies minting. 219 | * @param to The address to which tokens are transferred or minted. Address(0) implies burning. 220 | * @param amount The amount of tokens to be transferred. 221 | */ 222 | function _update(address from, address to, uint256 amount) internal virtual override { 223 | RebaseTokenStorage storage $ = _getRebaseTokenStorage(); 224 | bool optOutFrom = $.optOut[from]; 225 | bool optOutTo = $.optOut[to]; 226 | if (optOutFrom && optOutTo) { 227 | ERC20Upgradeable._update(from, to, amount); 228 | return; 229 | } 230 | uint256 index = $.rebaseIndex; 231 | uint256 shares = amount.toShares(index); 232 | if (from == address(0)) { 233 | if (optOutTo) { 234 | _checkTotalSupplyOverFlow(amount); 235 | } else { 236 | uint256 totalShares = $.totalShares + shares; // Overflow check required 237 | _checkRebaseOverflow(totalShares, index); 238 | $.totalShares = totalShares; 239 | } 240 | } else { 241 | if (optOutFrom) { 242 | amount = shares.toTokens(index); 243 | ERC20Upgradeable._update(from, address(0), amount); 244 | } else { 245 | shares = _transferableShares(amount, from); 246 | unchecked { 247 | // Underflow not possible: `shares <= $.shares[from] <= totalShares`. 248 | if (optOutTo && to != address(0)) $.totalShares -= shares; 249 | $.shares[from] -= shares; 250 | } 251 | } 252 | } 253 | 254 | if (to == address(0)) { 255 | if (!optOutFrom) { 256 | unchecked { 257 | // Underflow not possible: `shares <= $.totalShares` or `shares <= $.shares[from] <= $.totalShares`. 258 | $.totalShares -= shares; 259 | } 260 | } 261 | } else { 262 | if (optOutTo) { 263 | // At this point we know that `from` has not opted out. 264 | ERC20Upgradeable._update(address(0), to, amount); 265 | } else { 266 | // At this point we know that `from` has opted out. 267 | unchecked { 268 | // Overflow not possible: `$.shares[to] + shares` is at most `$.totalShares`, which we know fits 269 | // into a `uint256`. 270 | $.shares[to] += shares; 271 | if (optOutFrom && from != address(0)) $.totalShares += shares; 272 | } 273 | } 274 | } 275 | 276 | if (optOutFrom) from = address(0); 277 | if (optOutTo) to = address(0); 278 | 279 | if (from != to) { 280 | emit Transfer(from, to, shares.toTokens(index)); 281 | } 282 | } 283 | 284 | /** 285 | * @notice Checks for potential overflow conditions in token-to-share calculations. 286 | * @dev This function uses an `assert` statement to ensure that converting shares to tokens using the provided 287 | * `index` will not result in an overflow. It leverages the `toTokens` function from the `RebaseTokenMath` library 288 | * to perform this check. 289 | * 290 | * @param shares The number of shares involved in the operation. 291 | * @param index The current rebase index. 292 | */ 293 | function _checkRebaseOverflow(uint256 shares, uint256 index) private view { 294 | // Using an unchecked block to avoid overflow checks, as overflow will be handled explicitly. 295 | uint256 _elasticSupply = shares.toTokens(index); 296 | unchecked { 297 | if (_elasticSupply + ERC20Upgradeable.totalSupply() < _elasticSupply) { 298 | revert RebaseOverflow(); 299 | } 300 | } 301 | } 302 | 303 | /** 304 | * @notice Checks for potential overflow conditions in USTB totalSupply. 305 | * @dev This function ensures whenever a new mint, the addition of 306 | * new mintedAmount + totalShares + ERC20Upgradeable.totalSupply() doesn't over flow 307 | * 308 | * @param amount The amount of tokens involved in the operation. 309 | */ 310 | function _checkTotalSupplyOverFlow(uint256 amount) private view { 311 | unchecked { 312 | uint256 _totalSupply = totalSupply(); 313 | if (amount + _totalSupply < _totalSupply) { 314 | revert SupplyOverflow(); 315 | } 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/layerzero/lzApp/LzAppUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | 6 | import {ILayerZeroReceiver} from "@layerzerolabs/contracts/lzApp/interfaces/ILayerZeroReceiver.sol"; 7 | import {ILayerZeroUserApplicationConfig} from 8 | "@layerzerolabs/contracts/lzApp/interfaces/ILayerZeroUserApplicationConfig.sol"; 9 | import {ILayerZeroEndpoint} from "@layerzerolabs/contracts/lzApp/interfaces/ILayerZeroEndpoint.sol"; 10 | import {BytesLib} from "@layerzerolabs/contracts/libraries/BytesLib.sol"; 11 | 12 | /** 13 | * @title LzAppUpgradeable 14 | * @dev This is a generic implementation of LzReceiver, designed for LayerZero cross-chain communication. 15 | * 16 | * The contract inherits from `OwnableUpgradeable` and implements `ILayerZeroReceiver` and 17 | * `ILayerZeroUserApplicationConfig` interfaces. It provides functionality for setting and managing trusted remote 18 | * chains and their corresponding paths, configuring minimum destination gas, payload size limitations, and more. 19 | * 20 | * The contract uses a custom storage location `LzAppStorage`, which includes various mappings and state variables such 21 | * as `trustedRemoteLookup`, `minDstGasLookup`, and `payloadSizeLimitLookup`. 22 | * 23 | * Events: 24 | * - `SetPrecrime(address)`: Emitted when the precrime address is set. 25 | * - `SetTrustedRemote(uint16, bytes)`: Emitted when a trusted remote chain is set with its path. 26 | * - `SetTrustedRemoteAddress(uint16, bytes)`: Emitted when a trusted remote chain is set with its address. 27 | * - `SetMinDstGas(uint16, uint16, uint256)`: Emitted when minimum destination gas is set for a chain and packet type. 28 | * 29 | * Initialization: 30 | * The contract should be initialized by calling `__LzApp_init` function. 31 | * 32 | * Permissions: 33 | * Most administrative tasks require the sender to be the contract's owner. 34 | * 35 | * Note: 36 | * The contract includes the Checks-Effects-Interactions pattern and optimizes for gas-efficiency wherever applicable. 37 | */ 38 | abstract contract LzAppUpgradeable is OwnableUpgradeable, ILayerZeroReceiver, ILayerZeroUserApplicationConfig { 39 | using BytesLib for bytes; 40 | 41 | // ua can not send payload larger than this by default, but it can be changed by the ua owner 42 | uint256 public constant DEFAULT_PAYLOAD_SIZE_LIMIT = 10_000; 43 | 44 | event SetPrecrime(address precrime); 45 | event SetTrustedRemote(uint16 _remoteChainId, bytes _path); 46 | event SetTrustedRemoteAddress(uint16 _remoteChainId, bytes _remoteAddress); 47 | event SetMinDstGas(uint16 _dstChainId, uint16 _type, uint256 _minDstGas); 48 | 49 | /// @custom:storage-location erc7201:layerzero.storage.LzApp 50 | struct LzAppStorage { 51 | mapping(uint16 => bytes) trustedRemoteLookup; 52 | mapping(uint16 => mapping(uint16 => uint256)) minDstGasLookup; 53 | mapping(uint16 => uint256) payloadSizeLimitLookup; 54 | address precrime; 55 | } 56 | 57 | // keccak256(abi.encode(uint256(keccak256("layerzero.storage.LzApp")) - 1)) & ~bytes32(uint256(0xff)) 58 | bytes32 private constant LzAppStorageLocation = 0x111388274dd962a0529050efb131321f60015c2ab1a99387d94540f430037b00; 59 | 60 | function _getLzAppStorage() private pure returns (LzAppStorage storage $) { 61 | // slither-disable-next-line assembly 62 | assembly { 63 | $.slot := LzAppStorageLocation 64 | } 65 | } 66 | 67 | /// @custom:oz-upgrades-unsafe-allow state-variable-immutable 68 | ILayerZeroEndpoint public immutable lzEndpoint; 69 | 70 | /** 71 | * @param endpoint Address of the LayerZero endpoint contract. 72 | * @custom:oz-upgrades-unsafe-allow constructor 73 | */ 74 | constructor(address endpoint) { 75 | lzEndpoint = ILayerZeroEndpoint(endpoint); 76 | } 77 | 78 | /** 79 | * @dev Initializes the contract with the given `initialOwner`. 80 | * 81 | * Requirements: 82 | * - The function should only be called during the initialization process. 83 | * 84 | * @param initialOwner Address of the initial owner of the contract. 85 | */ 86 | function __LzApp_init(address initialOwner) internal onlyInitializing { 87 | __LzApp_init_unchained(); 88 | __Ownable_init(initialOwner); 89 | } 90 | 91 | function __LzApp_init_unchained() internal onlyInitializing {} 92 | 93 | /** 94 | * @dev Returns the trusted path for a given remote chain ID. 95 | * 96 | * @param remoteChainId The ID of the remote chain to query for a trusted path. 97 | * @return path Bytes representation of the trusted path for the specified remote chain ID. 98 | */ 99 | function trustedRemoteLookup(uint16 remoteChainId) external view returns (bytes memory path) { 100 | LzAppStorage storage $ = _getLzAppStorage(); 101 | path = $.trustedRemoteLookup[remoteChainId]; 102 | } 103 | 104 | /** 105 | * @dev Returns the minimum gas required for a given destination chain ID and packet type. 106 | * 107 | * @param dstChainId The ID of the destination chain to query for a minimum gas limit. 108 | * @param packetType The type of packet for which the minimum gas limit is to be fetched. 109 | * @return minGas The minimum gas limit required for the specified destination chain ID and packet type. 110 | */ 111 | function minDstGasLookup(uint16 dstChainId, uint16 packetType) external view returns (uint256 minGas) { 112 | LzAppStorage storage $ = _getLzAppStorage(); 113 | minGas = $.minDstGasLookup[dstChainId][packetType]; 114 | } 115 | 116 | /** 117 | * @dev Returns the payload size limit for a given destination chain ID. 118 | * 119 | * @param dstChainId The ID of the destination chain to query for a payload size limit. 120 | * @return size The maximum allowable payload size in bytes for the specified destination chain ID. 121 | */ 122 | function payloadSizeLimitLookup(uint16 dstChainId) external view returns (uint256 size) { 123 | LzAppStorage storage $ = _getLzAppStorage(); 124 | size = $.payloadSizeLimitLookup[dstChainId]; 125 | } 126 | 127 | /** 128 | * @dev Returns the address of the precrime contract. 129 | * 130 | * @return _precrime The address of the precrime contract. 131 | */ 132 | function precrime() external view returns (address _precrime) { 133 | LzAppStorage storage $ = _getLzAppStorage(); 134 | _precrime = $.precrime; 135 | } 136 | 137 | /** 138 | * @dev Handles incoming LayerZero messages from a source chain. 139 | * This function must be called by the LayerZero endpoint and validates the source of the message. 140 | * 141 | * Requirements: 142 | * - Caller must be the LayerZero endpoint. 143 | * - Source address must be a trusted remote address. 144 | * 145 | * @param srcChainId The ID of the source chain from which the message is sent. 146 | * @param srcAddress The address on the source chain that is sending the message. 147 | * @param nonce A unique identifier for the message. 148 | * @param payload The actual data payload of the message. 149 | */ 150 | function lzReceive(uint16 srcChainId, bytes calldata srcAddress, uint64 nonce, bytes calldata payload) 151 | public 152 | virtual 153 | override 154 | { 155 | LzAppStorage storage $ = _getLzAppStorage(); 156 | 157 | // lzReceive must be called by the endpoint for security 158 | require(_msgSender() == address(lzEndpoint), "LzApp: invalid endpoint caller"); 159 | 160 | bytes memory trustedRemote = $.trustedRemoteLookup[srcChainId]; 161 | // if will still block the message pathway from (srcChainId, srcAddress). should not receive message from 162 | // untrusted remote. 163 | require( 164 | srcAddress.length == trustedRemote.length && trustedRemote.length != 0 165 | && keccak256(srcAddress) == keccak256(trustedRemote), 166 | "LzApp: invalid source sending contract" 167 | ); 168 | 169 | _blockingLzReceive(srcChainId, srcAddress, nonce, payload); 170 | } 171 | 172 | /** 173 | * @dev Internal function that handles incoming LayerZero messages in a blocking manner. 174 | * This is an abstract function and should be implemented by derived contracts. 175 | * 176 | * @param srcChainId The ID of the source chain from which the message is sent. 177 | * @param srcAddress The address on the source chain that is sending the message. 178 | * @param nonce A unique identifier for the message. 179 | * @param payload The actual data payload of the message. 180 | */ 181 | function _blockingLzReceive(uint16 srcChainId, bytes memory srcAddress, uint64 nonce, bytes memory payload) 182 | internal 183 | virtual; 184 | 185 | /** 186 | * @dev Internal function to send a LayerZero message to a destination chain. 187 | * It performs a series of validations before sending the message. 188 | * 189 | * Requirements: 190 | * - Destination chain must be a trusted remote. 191 | * - Payload size must be within the configured limit. 192 | * 193 | * @param dstChainId The ID of the destination chain. 194 | * @param payload The actual data payload to be sent. 195 | * @param refundAddress The address to which any refunds should be sent. 196 | * @param zroPaymentAddress The address for the ZRO token payment. 197 | * @param adapterParams Additional parameters required for the adapter. 198 | * @param nativeFee The native fee to be sent along with the message. 199 | */ 200 | function _lzSend( 201 | uint16 dstChainId, 202 | bytes memory payload, 203 | address payable refundAddress, 204 | address zroPaymentAddress, 205 | bytes memory adapterParams, 206 | uint256 nativeFee 207 | ) internal virtual { 208 | LzAppStorage storage $ = _getLzAppStorage(); 209 | bytes memory trustedRemote = $.trustedRemoteLookup[dstChainId]; 210 | require(trustedRemote.length != 0, "LzApp: destination chain is not a trusted source"); 211 | _checkPayloadSize(dstChainId, payload.length); 212 | lzEndpoint.send{value: nativeFee}( 213 | dstChainId, trustedRemote, payload, refundAddress, zroPaymentAddress, adapterParams 214 | ); 215 | } 216 | 217 | /** 218 | * @dev Internal function to validate if the provided gas limit meets the minimum requirement for a given packet 219 | * type and destination chain. 220 | * 221 | * Requirements: 222 | * - The minimum destination gas limit must be set for the given packet type and destination chain. 223 | * - Provided gas limit should be greater than or equal to the sum of the minimum gas limit and any extra gas. 224 | * 225 | * @param dstChainId The ID of the destination chain. 226 | * @param packetType The type of the packet being sent. 227 | * @param adapterParams Additional parameters required for the adapter. 228 | * @param extraGas Extra gas to be added to the minimum required gas. 229 | */ 230 | function _checkGasLimit(uint16 dstChainId, uint16 packetType, bytes memory adapterParams, uint256 extraGas) 231 | internal 232 | view 233 | virtual 234 | { 235 | LzAppStorage storage $ = _getLzAppStorage(); 236 | uint256 providedGasLimit = _getGasLimit(adapterParams); 237 | uint256 minGasLimit = $.minDstGasLookup[dstChainId][packetType]; 238 | require(minGasLimit != 0, "LzApp: minGasLimit not set"); 239 | require(providedGasLimit >= minGasLimit + extraGas, "LzApp: gas limit is too low"); 240 | } 241 | 242 | /** 243 | * @dev Internal function to extract the gas limit from the adapter parameters. 244 | * 245 | * Requirements: 246 | * - The `adapterParams` must be at least 34 bytes long to contain the gas limit. 247 | * 248 | * @param _adapterParams The adapter parameters from which the gas limit is to be extracted. 249 | * @return gasLimit The extracted gas limit. 250 | */ 251 | function _getGasLimit(bytes memory _adapterParams) internal pure virtual returns (uint256 gasLimit) { 252 | require(_adapterParams.length >= 34, "LzApp: invalid adapterParams"); 253 | // slither-disable-next-line assembly 254 | assembly { 255 | gasLimit := mload(add(_adapterParams, 34)) 256 | } 257 | } 258 | 259 | /** 260 | * @dev Internal function to validate the size of the payload against the configured limit for a given destination 261 | * chain. 262 | * 263 | * Requirements: 264 | * - Payload size must be less than or equal to the configured size limit for the given destination chain. 265 | * 266 | * @param _dstChainId The ID of the destination chain. 267 | * @param _payloadSize The size of the payload in bytes. 268 | */ 269 | function _checkPayloadSize(uint16 _dstChainId, uint256 _payloadSize) internal view virtual { 270 | LzAppStorage storage $ = _getLzAppStorage(); 271 | uint256 payloadSizeLimit = $.payloadSizeLimitLookup[_dstChainId]; 272 | if (payloadSizeLimit == 0) { 273 | // use default if not set 274 | payloadSizeLimit = DEFAULT_PAYLOAD_SIZE_LIMIT; 275 | } 276 | require(_payloadSize <= payloadSizeLimit, "LzApp: payload size is too large"); 277 | } 278 | 279 | /** 280 | * @dev Retrieves the configuration of the LayerZero user application for a given version, chain ID, and config 281 | * type. 282 | * 283 | * @param version The version for which the configuration is to be fetched. 284 | * @param chainId The ID of the chain for which the configuration is needed. 285 | * @param configType The type of the configuration to be retrieved. 286 | * @return The bytes representation of the configuration. 287 | */ 288 | function getConfig(uint16 version, uint16 chainId, address, uint256 configType) 289 | external 290 | view 291 | returns (bytes memory) 292 | { 293 | return lzEndpoint.getConfig(version, chainId, address(this), configType); 294 | } 295 | 296 | /** 297 | * @dev Sets the configuration of the LayerZero user application for a given version, chain ID, and config type. 298 | * 299 | * Requirements: 300 | * - Only the owner can set the configuration. 301 | * 302 | * @param version The version for which the configuration is to be set. 303 | * @param chainId The ID of the chain for which the configuration is being set. 304 | * @param configType The type of the configuration to be set. 305 | * @param config The actual configuration data in bytes format. 306 | */ 307 | function setConfig(uint16 version, uint16 chainId, uint256 configType, bytes calldata config) 308 | external 309 | override 310 | onlyOwner 311 | { 312 | lzEndpoint.setConfig(version, chainId, configType, config); 313 | } 314 | 315 | /** 316 | * @dev Sets the version to be used for sending LayerZero messages. 317 | * 318 | * Requirements: 319 | * - Only the owner can set the send version. 320 | * 321 | * @param version The version to be set for sending messages. 322 | */ 323 | function setSendVersion(uint16 version) external override onlyOwner { 324 | lzEndpoint.setSendVersion(version); 325 | } 326 | 327 | /** 328 | * @dev Sets the version to be used for receiving LayerZero messages. 329 | * 330 | * Requirements: 331 | * - Only the owner can set the receive version. 332 | * 333 | * @param version The version to be set for receiving messages. 334 | */ 335 | function setReceiveVersion(uint16 version) external override onlyOwner { 336 | lzEndpoint.setReceiveVersion(version); 337 | } 338 | 339 | /** 340 | * @dev Resumes the reception of LayerZero messages from a specific source chain and address. 341 | * 342 | * Requirements: 343 | * - Only the owner can force the resumption of message reception. 344 | * 345 | * @param srcChainId The ID of the source chain from which message reception is to be resumed. 346 | * @param srcAddress The address on the source chain for which message reception is to be resumed. 347 | */ 348 | function forceResumeReceive(uint16 srcChainId, bytes calldata srcAddress) external override onlyOwner { 349 | lzEndpoint.forceResumeReceive(srcChainId, srcAddress); 350 | } 351 | 352 | /** 353 | * @dev Sets the trusted path for cross-chain communication with a specified remote chain. 354 | * 355 | * Requirements: 356 | * - Only the owner can set the trusted path. 357 | * 358 | * @param remoteChainId The ID of the remote chain for which the trusted path is being set. 359 | * @param path The trusted path encoded as bytes. 360 | */ 361 | function setTrustedRemote(uint16 remoteChainId, bytes calldata path) external onlyOwner { 362 | LzAppStorage storage $ = _getLzAppStorage(); 363 | $.trustedRemoteLookup[remoteChainId] = path; 364 | emit SetTrustedRemote(remoteChainId, path); 365 | } 366 | 367 | /** 368 | * @dev Sets the trusted remote address for cross-chain communication with a specified remote chain. 369 | * The function also automatically appends the contract's own address to the path. 370 | * 371 | * Requirements: 372 | * - Only the owner can set the trusted remote address. 373 | * 374 | * @param remoteChainId The ID of the remote chain for which the trusted address is being set. 375 | * @param remoteAddress The trusted remote address encoded as bytes. 376 | */ 377 | function setTrustedRemoteAddress(uint16 remoteChainId, bytes calldata remoteAddress) external onlyOwner { 378 | LzAppStorage storage $ = _getLzAppStorage(); 379 | $.trustedRemoteLookup[remoteChainId] = abi.encodePacked(remoteAddress, address(this)); 380 | emit SetTrustedRemoteAddress(remoteChainId, remoteAddress); 381 | } 382 | 383 | /** 384 | * @dev Retrieves the trusted remote address for a given remote chain. 385 | * 386 | * Requirements: 387 | * - A trusted path record must exist for the specified remote chain. 388 | * 389 | * @param remoteChainId The ID of the remote chain for which the trusted address is needed. 390 | * @return The trusted remote address encoded as bytes. 391 | */ 392 | function getTrustedRemoteAddress(uint16 remoteChainId) external view returns (bytes memory) { 393 | LzAppStorage storage $ = _getLzAppStorage(); 394 | bytes memory path = $.trustedRemoteLookup[remoteChainId]; 395 | require(path.length != 0, "LzApp: no trusted path record"); 396 | return path.slice(0, path.length - 20); // the last 20 bytes should be address(this) 397 | } 398 | 399 | /** 400 | * @dev Sets the "Precrime" address, which could be an address for handling fraudulent activities or other specific 401 | * behaviors. 402 | * 403 | * Requirements: 404 | * - Only the owner can set the Precrime address. 405 | * 406 | * @param _precrime The address to be set as Precrime. 407 | */ 408 | function setPrecrime(address _precrime) external onlyOwner { 409 | LzAppStorage storage $ = _getLzAppStorage(); 410 | $.precrime = _precrime; 411 | emit SetPrecrime(_precrime); 412 | } 413 | 414 | /** 415 | * @dev Sets the minimum required gas for a specific packet type and destination chain. 416 | * 417 | * Requirements: 418 | * - Only the owner can set the minimum destination gas. 419 | * 420 | * @param dstChainId The ID of the destination chain for which the minimum gas is being set. 421 | * @param packetType The type of the packet for which the minimum gas is being set. 422 | * @param minGas The minimum required gas in units. 423 | */ 424 | function setMinDstGas(uint16 dstChainId, uint16 packetType, uint256 minGas) external onlyOwner { 425 | LzAppStorage storage $ = _getLzAppStorage(); 426 | $.minDstGasLookup[dstChainId][packetType] = minGas; 427 | emit SetMinDstGas(dstChainId, packetType, minGas); 428 | } 429 | 430 | /** 431 | * @dev Sets the payload size limit for a specific destination chain. 432 | * 433 | * Requirements: 434 | * - Only the owner can set the payload size limit. 435 | * 436 | * @param dstChainId The ID of the destination chain for which the payload size limit is being set. 437 | * @param size The size limit in bytes. 438 | */ 439 | function setPayloadSizeLimit(uint16 dstChainId, uint256 size) external onlyOwner { 440 | LzAppStorage storage $ = _getLzAppStorage(); 441 | $.payloadSizeLimitLookup[dstChainId] = size; 442 | } 443 | 444 | /** 445 | * @dev Checks whether a given source chain and address are trusted for receiving LayerZero messages. 446 | * 447 | * @param srcChainId The ID of the source chain to be checked. 448 | * @param srcAddress The address on the source chain to be verified. 449 | * @return A boolean indicating whether the source chain and address are trusted. 450 | */ 451 | function isTrustedRemote(uint16 srcChainId, bytes calldata srcAddress) external view returns (bool) { 452 | LzAppStorage storage $ = _getLzAppStorage(); 453 | bytes memory trustedSource = $.trustedRemoteLookup[srcChainId]; 454 | return keccak256(trustedSource) == keccak256(srcAddress); 455 | } 456 | } 457 | --------------------------------------------------------------------------------