diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6cc383a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +### Changelog + +All notable changes to this project will be documented in this file. Dates are displayed in UTC. + +Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). + +#### [v0.4.1](https://github.com/sambacha/eigenlayer-template/compare/v0.4.0...v0.4.1) + +> 15 August 2024 + +- feat(tokens): init support [`842722d`](https://github.com/sambacha/eigenlayer-template/commit/842722d87b48639df8e631e8c8d158ca4906bb08) +- fix(fmt): lint apply [`48d367d`](https://github.com/sambacha/eigenlayer-template/commit/48d367d5fda081aa5882529fcb347eaf2faf6063) +- feat(lido): init support rebasing [`cfd5013`](https://github.com/sambacha/eigenlayer-template/commit/cfd5013c4dd20e43b7703dcb7431f1dc7f1d1acb) + +#### v0.4.0 + +> 15 August 2024 + +- contracts setup [`#1`](https://github.com/sambacha/eigenlayer-template/pull/1) +- Removed .gitmodules [`fa96ba3`](https://github.com/sambacha/eigenlayer-template/commit/fa96ba3741ef9e7a6db1ff6d899b956c8dbaf0a7) +- Merge commit '03b4804bedd2e2db3e1e3c30cab1129155cb3066' as 'lib/openzeppelin-contracts-upgradeable' [`f05585f`](https://github.com/sambacha/eigenlayer-template/commit/f05585fb0a13b03d75a25aa43f606154ae7d2a31) +- Squashed 'lib/openzeppelin-contracts-upgradeable/' content from commit 723f8cab0 [`03b4804`](https://github.com/sambacha/eigenlayer-template/commit/03b4804bedd2e2db3e1e3c30cab1129155cb3066) diff --git a/foundry.toml b/foundry.toml deleted file mode 100644 index 2cb6049..0000000 --- a/foundry.toml +++ /dev/null @@ -1,143 +0,0 @@ -[profile.default] -src = "src" -test = "test" -script = "script" -out = "out" -libs = ["lib"] -remappings = [ - "@openzeppelin-upgrades/=lib/eigenlayer-middleware/lib/openzeppelin-contracts-upgradeable/", - "@openzeppelin=/lib/openzeppelin-contracts/contracts/", - "ds-test/=lib/eigenlayer-middleware/lib/ds-test/src/", - "eigenlayer-contracts/=lib/eigenlayer-middleware/lib/eigenlayer-contracts/", - "eigenlayer-middleware/=lib/eigenlayer-middleware/src/", - "forge-std/=lib/eigenlayer-middleware/lib/forge-std/src/", -] -auto_detect_remappings = true -libraries = [] -cache = true -cache_path = "cache" -broadcast = "broadcast" -allow_paths = [] -include_paths = [] -skip = [] -force = false -evm_version = "paris" -gas_reports = ["*"] -gas_reports_ignore = [] -auto_detect_solc = true -offline = false -optimizer = true -optimizer_runs = 200 -verbosity = 0 -ignored_error_codes = [ - "license", - "code-size", - "init-code-size", - "transient-storage", -] -ignored_warnings_from = [] -deny_warnings = false -test_failures_file = "cache/test-failures" -show_progress = false -unchecked_cheatcode_artifacts = false -create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" -ffi = false -always_use_create_2_factory = false -prompt_timeout = 120 -sender = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" -tx_origin = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" -initial_balance = "0xffffffffffffffffffffffff" -block_number = 1 -gas_limit = 1073741824 -block_base_fee_per_gas = 0 -block_coinbase = "0x0000000000000000000000000000000000000000" -block_timestamp = 1 -block_difficulty = 0 -block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000" -memory_limit = 134217728 -extra_output = [] -extra_output_files = [] -names = false -sizes = false -via_ir = false -ast = false -no_storage_caching = false -no_rpc_rate_limit = false -use_literal_content = false -bytecode_hash = "ipfs" -cbor_metadata = true -sparse_mode = false -build_info = false -legacy_assertions = false -assertions_revert = true -disable_block_gas_limit = false -isolate = false - -[[profile.default.fs_permissions]] -access = "read" -path = "out" - -[profile.default.rpc_storage_caching] -chains = "all" -endpoints = "all" - -[fmt] -line_length = 120 -tab_width = 4 -bracket_spacing = false -int_types = "long" -multiline_func_header = "attributes_first" -quote_style = "double" -number_underscore = "preserve" -hex_underscore = "remove" -single_line_statement_blocks = "preserve" -override_spacing = false -wrap_comments = false -ignore = [] -contract_new_lines = false -sort_imports = false - -[doc] -out = "docs" -title = "" -book = "book.toml" -homepage = "README.md" -ignore = [] - -[fuzz] -runs = 256 -max_test_rejects = 65536 -dictionary_weight = 40 -include_storage = true -include_push_bytes = true -max_fuzz_dictionary_addresses = 15728640 -max_fuzz_dictionary_values = 6553600 -gas_report_samples = 256 -failure_persist_dir = "cache/fuzz" -failure_persist_file = "failures" -show_logs = false - -[invariant] -runs = 256 -depth = 500 -fail_on_revert = false -call_override = false -dictionary_weight = 80 -include_storage = true -include_push_bytes = true -max_fuzz_dictionary_addresses = 15728640 -max_fuzz_dictionary_values = 6553600 -shrink_run_limit = 5000 -max_assume_rejects = 65536 -gas_report_samples = 256 -failure_persist_dir = "cache/invariant" - -[labels] - -[vyper] - -[bind_json] -out = "utils/JsonBindings.sol" -include = [] -exclude = [] - diff --git a/src/interfaces/IDelegationManager.sol b/src/interfaces/IDelegationManager.sol index 50fe929..09f651c 100755 --- a/src/interfaces/IDelegationManager.sol +++ b/src/interfaces/IDelegationManager.sol @@ -20,18 +20,13 @@ interface IDelegationManager { uint32 stakerOptOutWindowBlocks; } - event OperatorMetadataURIUpdated( - address indexed operator, - string metadataURI - ); + event OperatorMetadataURIUpdated(address indexed operator, string metadataURI); - function registerAsOperator( - OperatorDetails calldata registeringOperatorDetails, - string calldata metadataURI - ) external; + function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) + external; - function getOperatorShares( - address operator, - IStrategy[] memory strategies - ) external view returns (uint256[] memory); + function getOperatorShares(address operator, IStrategy[] memory strategies) + external + view + returns (uint256[] memory); } diff --git a/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol b/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol index 07f6323..edaec81 100755 --- a/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol +++ b/src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol @@ -36,20 +36,13 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @notice Emitted when the weight required to be an operator changes /// @param oldMinimumWeight The previous weight /// @param newMinimumWeight The updated weight - event UpdateMinimumWeight( - uint256 oldMinimumWeight, - uint256 newMinimumWeight - ); + event UpdateMinimumWeight(uint256 oldMinimumWeight, uint256 newMinimumWeight); /// @notice Emitted when the system updates an operator's weight /// @param _operator The address of the operator updated /// @param oldWeight The operator's weight before the update /// @param newWeight The operator's weight after the update - event OperatorWeightUpdated( - address indexed _operator, - uint256 oldWeight, - uint256 newWeight - ); + event OperatorWeightUpdated(address indexed _operator, uint256 oldWeight, uint256 newWeight); /// @notice Emitted when the system updates the total weight /// @param oldTotalWeight The total weight before the update @@ -65,10 +58,7 @@ interface IECDSAStakeRegistryEventsAndErrors { /// @param newSigningKey The operator's signing key after the update /// @param oldSigningKey The operator's signing key before the update event SigningKeyUpdate( - address indexed operator, - uint256 indexed updateBlock, - address indexed newSigningKey, - address oldSigningKey + address indexed operator, uint256 indexed updateBlock, address indexed newSigningKey, address oldSigningKey ); /// @notice Indicates when the lengths of the signers array and signatures array do not match. diff --git a/src/interfaces/IGasOracle.sol b/src/interfaces/IGasOracle.sol index 1d4251c..e613d43 100755 --- a/src/interfaces/IGasOracle.sol +++ b/src/interfaces/IGasOracle.sol @@ -9,7 +9,8 @@ interface IGasOracle { uint128 gasPrice; } - function getExchangeRateAndGasPrice( - uint32 _destinationDomain - ) external view returns (uint128 tokenExchangeRate, uint128 gasPrice); + function getExchangeRateAndGasPrice(uint32 _destinationDomain) + external + view + returns (uint128 tokenExchangeRate, uint128 gasPrice); } diff --git a/src/interfaces/IInterchainGasPaymaster.sol b/src/interfaces/IInterchainGasPaymaster.sol index 405bd75..18997eb 100755 --- a/src/interfaces/IInterchainGasPaymaster.sol +++ b/src/interfaces/IInterchainGasPaymaster.sol @@ -14,22 +14,11 @@ interface IInterchainGasPaymaster { * @param gasAmount The amount of destination gas paid for. * @param payment The amount of native tokens paid. */ - event GasPayment( - bytes32 indexed messageId, - uint32 indexed destinationDomain, - uint256 gasAmount, - uint256 payment - ); + event GasPayment(bytes32 indexed messageId, uint32 indexed destinationDomain, uint256 gasAmount, uint256 payment); - function payForGas( - bytes32 _messageId, - uint32 _destinationDomain, - uint256 _gasAmount, - address _refundAddress - ) external payable; + function payForGas(bytes32 _messageId, uint32 _destinationDomain, uint256 _gasAmount, address _refundAddress) + external + payable; - function quoteGasPayment( - uint32 _destinationDomain, - uint256 _gasAmount - ) external view returns (uint256); + function quoteGasPayment(uint32 _destinationDomain, uint256 _gasAmount) external view returns (uint256); } diff --git a/src/interfaces/IInterchainSecurityModule.sol b/src/interfaces/IInterchainSecurityModule.sol index 3dd1c94..ccac164 100755 --- a/src/interfaces/IInterchainSecurityModule.sol +++ b/src/interfaces/IInterchainSecurityModule.sol @@ -31,15 +31,9 @@ interface IInterchainSecurityModule { * @param _message Manifold Finance AVS encoded interchain message * @return True if the message was verified */ - function verify( - bytes calldata _metadata, - bytes calldata _message - ) external returns (bool); + function verify(bytes calldata _metadata, bytes calldata _message) external returns (bool); } interface ISpecifiesInterchainSecurityModule { - function interchainSecurityModule() - external - view - returns (IInterchainSecurityModule); + function interchainSecurityModule() external view returns (IInterchainSecurityModule); } diff --git a/src/interfaces/ILiquidityLayerMessageRecipient.sol b/src/interfaces/ILiquidityLayerMessageRecipient.sol index 1fc03e3..883a3df 100755 --- a/src/interfaces/ILiquidityLayerMessageRecipient.sol +++ b/src/interfaces/ILiquidityLayerMessageRecipient.sol @@ -2,11 +2,6 @@ pragma solidity ^0.8.13; interface ILiquidityLayerMessageRecipient { - function handleWithTokens( - uint32 _origin, - bytes32 _sender, - bytes calldata _message, - address _token, - uint256 _amount - ) external; + function handleWithTokens(uint32 _origin, bytes32 _sender, bytes calldata _message, address _token, uint256 _amount) + external; } diff --git a/src/interfaces/IMailbox.sol b/src/interfaces/IMailbox.sol index 893afe6..d8b820a 100755 --- a/src/interfaces/IMailbox.sol +++ b/src/interfaces/IMailbox.sol @@ -13,12 +13,7 @@ interface IMailbox { * @param recipient The message recipient address on `destination` * @param message Raw bytes of message */ - event Dispatch( - address indexed sender, - uint32 indexed destination, - bytes32 indexed recipient, - bytes message - ); + event Dispatch(address indexed sender, uint32 indexed destination, bytes32 indexed recipient, bytes message); /** * @notice Emitted when a new message is dispatched via Manifold Finance AVS @@ -38,11 +33,7 @@ interface IMailbox { * @param sender The message sender address on `origin` * @param recipient The address that handled the message */ - event Process( - uint32 indexed origin, - bytes32 indexed sender, - address indexed recipient - ); + event Process(uint32 indexed origin, bytes32 indexed sender, address indexed recipient); function localDomain() external view returns (uint32); @@ -56,17 +47,15 @@ interface IMailbox { function latestDispatchedId() external view returns (bytes32); - function dispatch( - uint32 destinationDomain, - bytes32 recipientAddress, - bytes calldata messageBody - ) external payable returns (bytes32 messageId); + function dispatch(uint32 destinationDomain, bytes32 recipientAddress, bytes calldata messageBody) + external + payable + returns (bytes32 messageId); - function quoteDispatch( - uint32 destinationDomain, - bytes32 recipientAddress, - bytes calldata messageBody - ) external view returns (uint256 fee); + function quoteDispatch(uint32 destinationDomain, bytes32 recipientAddress, bytes calldata messageBody) + external + view + returns (uint256 fee); function dispatch( uint32 destinationDomain, @@ -98,12 +87,7 @@ interface IMailbox { IPostDispatchHook customHook ) external view returns (uint256 fee); - function process( - bytes calldata metadata, - bytes calldata message - ) external payable; + function process(bytes calldata metadata, bytes calldata message) external payable; - function recipientIsm( - address recipient - ) external view returns (IInterchainSecurityModule module); + function recipientIsm(address recipient) external view returns (IInterchainSecurityModule module); } diff --git a/src/interfaces/IMessageRecipient.sol b/src/interfaces/IMessageRecipient.sol index 187194b..910b613 100755 --- a/src/interfaces/IMessageRecipient.sol +++ b/src/interfaces/IMessageRecipient.sol @@ -2,9 +2,5 @@ pragma solidity >=0.6.11; interface IMessageRecipient { - function handle( - uint32 _origin, - bytes32 _sender, - bytes calldata _message - ) external payable; + function handle(uint32 _origin, bytes32 _sender, bytes calldata _message) external payable; } diff --git a/src/interfaces/IRouter.sol b/src/interfaces/IRouter.sol index a26020d..00df6ae 100755 --- a/src/interfaces/IRouter.sol +++ b/src/interfaces/IRouter.sol @@ -8,8 +8,5 @@ interface IRouter { function enrollRemoteRouter(uint32 _domain, bytes32 _router) external; - function enrollRemoteRouters( - uint32[] calldata _domains, - bytes32[] calldata _routers - ) external; + function enrollRemoteRouters(uint32[] calldata _domains, bytes32[] calldata _routers) external; } diff --git a/src/interfaces/IValidatorAnnounce.sol b/src/interfaces/IValidatorAnnounce.sol index 938eb92..944dfd5 100755 --- a/src/interfaces/IValidatorAnnounce.sol +++ b/src/interfaces/IValidatorAnnounce.sol @@ -10,9 +10,7 @@ interface IValidatorAnnounce { * @param _validators The list of validators to get storage locations for * @return A list of announced storage locations */ - function getAnnouncedStorageLocations( - address[] calldata _validators - ) external view returns (string[][] memory); + function getAnnouncedStorageLocations(address[] calldata _validators) external view returns (string[][] memory); /** * @notice Announces a validator signature storage location @@ -21,9 +19,7 @@ interface IValidatorAnnounce { * @param _signature The signed validator announcement * @return True upon success */ - function announce( - address _validator, - string calldata _storageLocation, - bytes calldata _signature - ) external returns (bool); + function announce(address _validator, string calldata _storageLocation, bytes calldata _signature) + external + returns (bool); } diff --git a/src/interfaces/avs/IRemoteChallenger.sol b/src/interfaces/avs/IRemoteChallenger.sol new file mode 100644 index 0000000..0135782 --- /dev/null +++ b/src/interfaces/avs/IRemoteChallenger.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +interface IRemoteChallenger { + /// @notice Returns the number of blocks that must be mined before a challenge can be handled + /// @return The number of blocks that must be mined before a challenge can be handled + function challengeDelayBlocks() external view returns (uint256); + + /// @notice Handles a challenge for an operator + /// @param operator The address of the operator + function handleChallenge(address operator) external; +} diff --git a/src/interfaces/avs/IAVSDirectory.sol b/src/interfaces/avs/vendored/IAVSDirectory.sol old mode 100644 new mode 100755 similarity index 94% rename from src/interfaces/avs/IAVSDirectory.sol rename to src/interfaces/avs/vendored/IAVSDirectory.sol index b880fb2..d8003a6 --- a/src/interfaces/avs/IAVSDirectory.sol +++ b/src/interfaces/avs/vendored/IAVSDirectory.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0; import "./ISignatureUtils.sol"; -/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the Manifold Finance AVS +/// part of mock interfaces for vendoring necessary Eigenlayer contracts for the hyperlane AVS /// @author Layr Labs, Inc. interface IAVSDirectory is ISignatureUtils { enum OperatorAVSRegistrationStatus { diff --git a/src/interfaces/avs/vendored/IDelegationManager.sol b/src/interfaces/avs/vendored/IDelegationManager.sol new file mode 100755 index 0000000..09f651c --- /dev/null +++ b/src/interfaces/avs/vendored/IDelegationManager.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {IStrategy} from "./IStrategy.sol"; + +/** + * @title DelegationManager + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice This is the contract for delegation in EigenLayer. The main functionalities of this contract are + * - enabling anyone to register as an operator in EigenLayer + * - allowing operators to specify parameters related to stakers who delegate to them + * - enabling any staker to delegate its stake to the operator of its choice (a given staker can only delegate to a single operator at a time) + * - enabling a staker to undelegate its assets from the operator it is delegated to (performed as part of the withdrawal process, initiated through the StrategyManager) + */ +interface IDelegationManager { + struct OperatorDetails { + address earningsReceiver; + address delegationApprover; + uint32 stakerOptOutWindowBlocks; + } + + event OperatorMetadataURIUpdated(address indexed operator, string metadataURI); + + function registerAsOperator(OperatorDetails calldata registeringOperatorDetails, string calldata metadataURI) + external; + + function getOperatorShares(address operator, IStrategy[] memory strategies) + external + view + returns (uint256[] memory); +} diff --git a/src/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol b/src/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol new file mode 100755 index 0000000..edaec81 --- /dev/null +++ b/src/interfaces/avs/vendored/IECDSAStakeRegistryEventsAndErrors.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {IStrategy} from "./IStrategy.sol"; + +struct StrategyParams { + IStrategy strategy; // The strategy contract reference + uint96 multiplier; // The multiplier applied to the strategy +} + +struct Quorum { + StrategyParams[] strategies; // An array of strategy parameters to define the quorum +} + +interface IECDSAStakeRegistryEventsAndErrors { + /// @notice Emitted when the system registers an operator + /// @param _operator The address of the registered operator + /// @param _avs The address of the associated AVS + event OperatorRegistered(address indexed _operator, address indexed _avs); + + /// @notice Emitted when the system deregisters an operator + /// @param _operator The address of the deregistered operator + /// @param _avs The address of the associated AVS + event OperatorDeregistered(address indexed _operator, address indexed _avs); + + /// @notice Emitted when the system updates the quorum + /// @param _old The previous quorum configuration + /// @param _new The new quorum configuration + event QuorumUpdated(Quorum _old, Quorum _new); + + /// @notice Emitted when the weight to join the operator set updates + /// @param _old The previous minimum weight + /// @param _new The new minimumWeight + event MinimumWeightUpdated(uint256 _old, uint256 _new); + + /// @notice Emitted when the weight required to be an operator changes + /// @param oldMinimumWeight The previous weight + /// @param newMinimumWeight The updated weight + event UpdateMinimumWeight(uint256 oldMinimumWeight, uint256 newMinimumWeight); + + /// @notice Emitted when the system updates an operator's weight + /// @param _operator The address of the operator updated + /// @param oldWeight The operator's weight before the update + /// @param newWeight The operator's weight after the update + event OperatorWeightUpdated(address indexed _operator, uint256 oldWeight, uint256 newWeight); + + /// @notice Emitted when the system updates the total weight + /// @param oldTotalWeight The total weight before the update + /// @param newTotalWeight The total weight after the update + event TotalWeightUpdated(uint256 oldTotalWeight, uint256 newTotalWeight); + + /// @notice Emits when setting a new threshold weight. + event ThresholdWeightUpdated(uint256 _thresholdWeight); + + /// @notice Emitted when an operator's signing key is updated + /// @param operator The address of the operator whose signing key was updated + /// @param updateBlock The block number at which the signing key was updated + /// @param newSigningKey The operator's signing key after the update + /// @param oldSigningKey The operator's signing key before the update + event SigningKeyUpdate( + address indexed operator, uint256 indexed updateBlock, address indexed newSigningKey, address oldSigningKey + ); + /// @notice Indicates when the lengths of the signers array and signatures array do not match. + + error LengthMismatch(); + + /// @notice Indicates encountering an invalid length for the signers or signatures array. + error InvalidLength(); + + /// @notice Indicates encountering an invalid signature. + error InvalidSignature(); + + /// @notice Thrown when the threshold update is greater than BPS + error InvalidThreshold(); + + /// @notice Thrown when missing operators in an update + error MustUpdateAllOperators(); + + /// @notice Reference blocks must be for blocks that have already been confirmed + error InvalidReferenceBlock(); + + /// @notice Indicates operator weights were out of sync and the signed weight exceed the total + error InvalidSignedWeight(); + + /// @notice Indicates the total signed stake fails to meet the required threshold. + error InsufficientSignedStake(); + + /// @notice Indicates an individual signer's weight fails to meet the required threshold. + error InsufficientWeight(); + + /// @notice Indicates the quorum is invalid + error InvalidQuorum(); + + /// @notice Indicates the system finds a list of items unsorted + error NotSorted(); + + /// @notice Thrown when registering an already registered operator + error OperatorAlreadyRegistered(); + + /// @notice Thrown when de-registering or updating the stake for an unregistered operator + error OperatorNotRegistered(); +} diff --git a/src/interfaces/avs/vendored/IPaymentCoordinator.sol b/src/interfaces/avs/vendored/IPaymentCoordinator.sol new file mode 100755 index 0000000..818b84d --- /dev/null +++ b/src/interfaces/avs/vendored/IPaymentCoordinator.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IStrategy.sol"; + +/** + * @title Interface for the `IPaymentCoordinator` contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice Allows AVSs to make "Range Payments", which get distributed amongst the AVSs' confirmed + * Operators and the Stakers delegated to those Operators. + * Calculations are performed based on the completed Range Payments, with the results posted in + * a Merkle root against which Stakers & Operators can make claims. + */ +interface IPaymentCoordinator { + /// STRUCTS /// + struct StrategyAndMultiplier { + IStrategy strategy; + // weight used to compare shares in multiple strategies against one another + uint96 multiplier; + } + + struct RangePayment { + // Strategies & relative weights of shares in the strategies + StrategyAndMultiplier[] strategiesAndMultipliers; + IERC20 token; + uint256 amount; + uint64 startTimestamp; + uint64 duration; + } + + /// EXTERNAL FUNCTIONS /// + + /** + * @notice Creates a new range payment on behalf of an AVS, to be split amongst the + * set of stakers delegated to operators who are registered to the `avs` + * @param rangePayments The range payments being created + * @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made + * @dev The duration of the `rangePayment` cannot exceed `MAX_PAYMENT_DURATION` + * @dev The tokens are sent to the `claimingManager` contract + * @dev This function will revert if the `rangePayment` is malformed, + * e.g. if the `strategies` and `weights` arrays are of non-equal lengths + */ + function payForRange(RangePayment[] calldata rangePayments) external; +} diff --git a/src/interfaces/avs/vendored/IServiceManager.sol b/src/interfaces/avs/vendored/IServiceManager.sol new file mode 100755 index 0000000..565c558 --- /dev/null +++ b/src/interfaces/avs/vendored/IServiceManager.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {IPaymentCoordinator} from "./IPaymentCoordinator.sol"; +import {IServiceManagerUI} from "./IServiceManagerUI.sol"; + +/** + * @title Minimal interface for a ServiceManager-type contract that forms the single point for an AVS to push updates to EigenLayer + * @author Layr Labs, Inc. + */ +interface IServiceManager is IServiceManagerUI { + /** + * @notice Creates a new range payment on behalf of an AVS, to be split amongst the + * set of stakers delegated to operators who are registered to the `avs`. + * Note that the owner calling this function must have approved the tokens to be transferred to the ServiceManager + * and of course has the required balances. + * @param rangePayments The range payments being created + * @dev Expected to be called by the ServiceManager of the AVS on behalf of which the payment is being made + * @dev The duration of the `rangePayment` cannot exceed `paymentCoordinator.MAX_PAYMENT_DURATION()` + * @dev The tokens are sent to the `PaymentCoordinator` contract + * @dev Strategies must be in ascending order of addresses to check for duplicates + * @dev This function will revert if the `rangePayment` is malformed, + * e.g. if the `strategies` and `weights` arrays are of non-equal lengths + */ + function payForRange(IPaymentCoordinator.RangePayment[] calldata rangePayments) external; +} diff --git a/src/interfaces/avs/vendored/IServiceManagerUI.sol b/src/interfaces/avs/vendored/IServiceManagerUI.sol new file mode 100755 index 0000000..390237c --- /dev/null +++ b/src/interfaces/avs/vendored/IServiceManagerUI.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.0; + +import {ISignatureUtils} from "./ISignatureUtils.sol"; +import {IDelegationManager} from "./IDelegationManager.sol"; + +/** + * @title Minimal interface for a ServiceManager-type contract that AVS ServiceManager contracts must implement + * for eigenlabs to be able to index their data on the AVS marketplace frontend. + * @author Layr Labs, Inc. + */ +interface IServiceManagerUI { + /** + * Metadata should follow the format outlined by this example. + * { + * "name": "EigenLabs AVS 1", + * "website": "https://www.eigenlayer.xyz/", + * "description": "This is my 1st AVS", + * "logo": "https://holesky-operator-metadata.s3.amazonaws.com/eigenlayer.png", + * "twitter": "https://twitter.com/eigenlayer" + * } + * @notice Updates the metadata URI for the AVS + * @param _metadataURI is the metadata URI for the AVS + */ + function updateAVSMetadataURI(string memory _metadataURI) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator registration with the AVS + * @param operator The address of the operator to register. + * @param operatorSignature The signature, salt, and expiry of the operator's signature. + */ + function registerOperatorToAVS( + address operator, + ISignatureUtils.SignatureWithSaltAndExpiry memory operatorSignature + ) external; + + /** + * @notice Forwards a call to EigenLayer's DelegationManager contract to confirm operator deregistration from the AVS + * @param operator The address of the operator to deregister. + */ + function deregisterOperatorFromAVS(address operator) external; + + /** + * @notice Returns the list of strategies that the operator has potentially restaked on the AVS + * @param operator The address of the operator to get restaked strategies for + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on whether the operator has shares for a strategy in a quorum or uniqueness + * of each element in the returned array. The off-chain service should do that validation separately + */ + function getOperatorRestakedStrategies(address operator) external view returns (address[] memory); + + /** + * @notice Returns the list of strategies that the AVS supports for restaking + * @dev This function is intended to be called off-chain + * @dev No guarantee is made on uniqueness of each element in the returned array. + * The off-chain service should do that validation separately + */ + function getRestakeableStrategies() external view returns (address[] memory); + + /// @notice Returns the EigenLayer AVSDirectory contract. + function avsDirectory() external view returns (address); +} diff --git a/src/interfaces/avs/vendored/ISignatureUtils.sol b/src/interfaces/avs/vendored/ISignatureUtils.sol new file mode 100755 index 0000000..158b325 --- /dev/null +++ b/src/interfaces/avs/vendored/ISignatureUtils.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +/** + * @title The interface for common signature utilities. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ +interface ISignatureUtils { + // @notice Struct that bundles together a signature and an expiration time for the signature. Used primarily for stack management. + struct SignatureWithExpiry { + // the signature itself, formatted as a single bytes object + bytes signature; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } + + // @notice Struct that bundles together a signature, a salt for uniqueness, and an expiration time for the signature. Used primarily for stack management. + struct SignatureWithSaltAndExpiry { + // the signature itself, formatted as a single bytes object + bytes signature; + // the salt used to generate the signature + bytes32 salt; + // the expiration timestamp (UTC) of the signature + uint256 expiry; + } +} diff --git a/src/interfaces/avs/vendored/ISlasher.sol b/src/interfaces/avs/vendored/ISlasher.sol new file mode 100755 index 0000000..577a36e --- /dev/null +++ b/src/interfaces/avs/vendored/ISlasher.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +/** + * @title Interface for the primary 'slashing' contract for EigenLayer. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + */ +interface ISlasher { + function freezeOperator(address toBeFrozen) external; +} diff --git a/src/interfaces/avs/vendored/IStrategy.sol b/src/interfaces/avs/vendored/IStrategy.sol new file mode 100755 index 0000000..5db9a97 --- /dev/null +++ b/src/interfaces/avs/vendored/IStrategy.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.5.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @title Minimal interface for an `Strategy` contract. + * @author Layr Labs, Inc. + * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service + * @notice Custom `Strategy` implementations may expand extensively on this interface. + */ +interface IStrategy { + /** + * @notice Used to deposit tokens into this Strategy + * @param token is the ERC20 token being deposited + * @param amount is the amount of token being deposited + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * `depositIntoStrategy` function, and individual share balances are recorded in the strategyManager as well. + * @return newShares is the number of new shares issued at the current exchange ratio. + */ + function deposit(IERC20 token, uint256 amount) external returns (uint256); + + /** + * @notice Used to withdraw tokens from this Strategy, to the `recipient`'s address + * @param recipient is the address to receive the withdrawn funds + * @param token is the ERC20 token being transferred out + * @param amountShares is the amount of shares being withdrawn + * @dev This function is only callable by the strategyManager contract. It is invoked inside of the strategyManager's + * other functions, and individual share balances are recorded in the strategyManager as well. + */ + function withdraw(address recipient, IERC20 token, uint256 amountShares) external; + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlyingView`, this function **may** make state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlying(uint256 amountShares) external returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToSharesView`, this function **may** make state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of underlying tokens corresponding to the input `amountShares` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToShares(uint256 amountUnderlying) external returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlyingView`, this function **may** make state modifications + */ + function userUnderlying(address user) external returns (uint256); + + /** + * @notice convenience function for fetching the current total shares of `user` in this strategy, by + * querying the `strategyManager` contract + */ + function shares(address user) external view returns (uint256); + + /** + * @notice Used to convert a number of shares to the equivalent amount of underlying tokens for this strategy. + * @notice In contrast to `sharesToUnderlying`, this function guarantees no state modifications + * @param amountShares is the amount of shares to calculate its conversion into the underlying token + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function sharesToUnderlyingView(uint256 amountShares) external view returns (uint256); + + /** + * @notice Used to convert an amount of underlying tokens to the equivalent amount of shares in this strategy. + * @notice In contrast to `underlyingToShares`, this function guarantees no state modifications + * @param amountUnderlying is the amount of `underlyingToken` to calculate its conversion into strategy shares + * @return The amount of shares corresponding to the input `amountUnderlying` + * @dev Implementation for these functions in particular may vary significantly for different strategies + */ + function underlyingToSharesView(uint256 amountUnderlying) external view returns (uint256); + + /** + * @notice convenience function for fetching the current underlying value of all of the `user`'s shares in + * this strategy. In contrast to `userUnderlying`, this function guarantees no state modifications + */ + function userUnderlyingView(address user) external view returns (uint256); + + /// @notice The underlying token for shares in this Strategy + function underlyingToken() external view returns (IERC20); + + /// @notice The total number of extant shares in this Strategy + function totalShares() external view returns (uint256); + + /// @notice Returns either a brief string explaining the strategy's goal & purpose, or a link to metadata that explains in more detail. + function explanation() external view returns (string memory); +} diff --git a/src/interfaces/hooks/IMessageDispatcher.sol b/src/interfaces/hooks/IMessageDispatcher.sol index b905868..8830c73 100755 --- a/src/interfaces/hooks/IMessageDispatcher.sol +++ b/src/interfaces/hooks/IMessageDispatcher.sol @@ -15,16 +15,8 @@ interface IMessageDispatcher { * @param data Data that was dispatched */ event MessageDispatched( - bytes32 indexed messageId, - address indexed from, - uint256 indexed toChainId, - address to, - bytes data + bytes32 indexed messageId, address indexed from, uint256 indexed toChainId, address to, bytes data ); - function dispatchMessage( - uint256 toChainId, - address to, - bytes calldata data - ) external returns (bytes32); + function dispatchMessage(uint256 toChainId, address to, bytes calldata data) external returns (bytes32); } diff --git a/src/interfaces/hooks/IPostDispatchHook.sol b/src/interfaces/hooks/IPostDispatchHook.sol index 65b94f4..87d7901 100755 --- a/src/interfaces/hooks/IPostDispatchHook.sol +++ b/src/interfaces/hooks/IPostDispatchHook.sol @@ -27,19 +27,14 @@ interface IPostDispatchHook { * @param metadata metadata * @return Whether the hook supports metadata */ - function supportsMetadata( - bytes calldata metadata - ) external view returns (bool); + function supportsMetadata(bytes calldata metadata) external view returns (bool); /** * @notice Post action after a message is dispatched via the Mailbox * @param metadata The metadata required for the hook * @param message The message passed from the Mailbox.dispatch() call */ - function postDispatch( - bytes calldata metadata, - bytes calldata message - ) external payable; + function postDispatch(bytes calldata metadata, bytes calldata message) external payable; /** * @notice Compute the payment required by the postDispatch call @@ -47,8 +42,5 @@ interface IPostDispatchHook { * @param message The message passed from the Mailbox.dispatch() call * @return Quoted payment for the postDispatch call */ - function quoteDispatch( - bytes calldata metadata, - bytes calldata message - ) external view returns (uint256); + function quoteDispatch(bytes calldata metadata, bytes calldata message) external view returns (uint256); } diff --git a/src/interfaces/isms/IAggregationIsm.sol b/src/interfaces/isms/IAggregationIsm.sol index 69e2ddd..2657bec 100755 --- a/src/interfaces/isms/IAggregationIsm.sol +++ b/src/interfaces/isms/IAggregationIsm.sol @@ -12,7 +12,8 @@ interface IAggregationIsm is IInterchainSecurityModule { * @return modules The array of ISM addresses * @return threshold The number of modules needed to verify */ - function modulesAndThreshold( - bytes calldata _message - ) external view returns (address[] memory modules, uint8 threshold); + function modulesAndThreshold(bytes calldata _message) + external + view + returns (address[] memory modules, uint8 threshold); } diff --git a/src/interfaces/isms/ICcipReadIsm.sol b/src/interfaces/isms/ICcipReadIsm.sol index d0534b0..d871fa9 100755 --- a/src/interfaces/isms/ICcipReadIsm.sol +++ b/src/interfaces/isms/ICcipReadIsm.sol @@ -10,13 +10,7 @@ interface ICcipReadIsm is IInterchainSecurityModule { /// @param callData context needed for offchain service to service request /// @param callbackFunction function selector to call with offchain information /// @param extraData additional passthrough information to call callbackFunction with - error OffchainLookup( - address sender, - string[] urls, - bytes callData, - bytes4 callbackFunction, - bytes extraData - ); + error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); /** * @notice Reverts with the data needed to query information offchain diff --git a/src/interfaces/isms/IMultisigIsm.sol b/src/interfaces/isms/IMultisigIsm.sol index 89c89e5..9fc42e5 100755 --- a/src/interfaces/isms/IMultisigIsm.sol +++ b/src/interfaces/isms/IMultisigIsm.sol @@ -13,7 +13,8 @@ interface IMultisigIsm is IInterchainSecurityModule { * @return validators The array of validator addresses * @return threshold The number of validator signatures needed */ - function validatorsAndThreshold( - bytes calldata _message - ) external view returns (address[] memory validators, uint8 threshold); + function validatorsAndThreshold(bytes calldata _message) + external + view + returns (address[] memory validators, uint8 threshold); } diff --git a/src/interfaces/isms/IRoutingIsm.sol b/src/interfaces/isms/IRoutingIsm.sol index 1218bc3..ba32b21 100755 --- a/src/interfaces/isms/IRoutingIsm.sol +++ b/src/interfaces/isms/IRoutingIsm.sol @@ -10,7 +10,5 @@ interface IRoutingIsm is IInterchainSecurityModule { * @param _message Formatted Manifold Finance AVS message (see Message.sol). * @return module The ISM to use to verify _message */ - function route( - bytes calldata _message - ) external view returns (IInterchainSecurityModule); + function route(bytes calldata _message) external view returns (IInterchainSecurityModule); } diff --git a/src/interfaces/isms/IWeightedMultisigIsm.sol b/src/interfaces/isms/IWeightedMultisigIsm.sol index 1a19195..b13b84e 100755 --- a/src/interfaces/isms/IWeightedMultisigIsm.sol +++ b/src/interfaces/isms/IWeightedMultisigIsm.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.6.11; - import {IInterchainSecurityModule} from "../IInterchainSecurityModule.sol"; interface IStaticWeightedMultisigIsm is IInterchainSecurityModule { @@ -19,9 +18,7 @@ interface IStaticWeightedMultisigIsm is IInterchainSecurityModule { * @return validators The validators and their weights * @return thresholdWeight The threshold weight required to pass verification */ - function validatorsAndThresholdWeight( - bytes calldata _message - ) + function validatorsAndThresholdWeight(bytes calldata _message) external view returns (ValidatorInfo[] memory validators, uint96 thresholdWeight); diff --git a/src/interfaces/optimism/ICrossDomainMessenger.sol b/src/interfaces/optimism/ICrossDomainMessenger.sol index 9bffafc..0a2849f 100755 --- a/src/interfaces/optimism/ICrossDomainMessenger.sol +++ b/src/interfaces/optimism/ICrossDomainMessenger.sol @@ -12,11 +12,7 @@ interface ICrossDomainMessenger { * @param _message Message to send to the target. * @param _gasLimit Gas limit for the provided message. */ - function sendMessage( - address _target, - bytes calldata _message, - uint32 _gasLimit - ) external payable; + function sendMessage(address _target, bytes calldata _message, uint32 _gasLimit) external payable; function relayMessage( uint256 _nonce, diff --git a/src/libs/EnumerableMap.sol b/src/libs/EnumerableMap.sol new file mode 100644 index 0000000..929ae7c --- /dev/null +++ b/src/libs/EnumerableMap.sol @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableMap.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableMap.js. + +pragma solidity ^0.8.20; + +import {EnumerableSet} from "./EnumerableSet.sol"; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * The following map types are supported: + * + * - `uint256 -> address` (`UintToAddressMap`) since v3.0.0 + * - `address -> uint256` (`AddressToUintMap`) since v4.6.0 + * - `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 + * - `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 + * - `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableMap. + * ==== + */ +library EnumerableMap { + using EnumerableSet for EnumerableSet.Bytes32Set; + + // To implement this library for multiple types with as little code repetition as possible, we write it in + // terms of a generic Map type with bytes32 keys and values. The Map implementation uses private functions, + // and user-facing implementations such as `UintToAddressMap` are just wrappers around the underlying Map. + // This means that we can only create new EnumerableMaps for types that fit in bytes32. + + /** + * @dev Query for a nonexistent map key. + */ + error EnumerableMapNonexistentKey(bytes32 key); + + struct Bytes32ToBytes32Map { + // Storage of keys + EnumerableSet.Bytes32Set _keys; + mapping(bytes32 key => bytes32) _values; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToBytes32Map storage map, bytes32 key, bytes32 value) internal returns (bool) { + map._values[key] = value; + return map._keys.add(key); + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToBytes32Map storage map, bytes32 key) internal returns (bool) { + delete map._values[key]; + return map._keys.remove(key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool) { + return map._keys.contains(key); + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function length(Bytes32ToBytes32Map storage map) internal view returns (uint256) { + return map._keys.length(); + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToBytes32Map storage map, uint256 index) internal view returns (bytes32, bytes32) { + bytes32 key = map._keys.at(index); + return (key, map._values[key]); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bool, bytes32) { + bytes32 value = map._values[key]; + if (value == bytes32(0)) { + return (contains(map, key), bytes32(0)); + } else { + return (true, value); + } + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToBytes32Map storage map, bytes32 key) internal view returns (bytes32) { + bytes32 value = map._values[key]; + if (value == 0 && !contains(map, key)) { + revert EnumerableMapNonexistentKey(key); + } + return value; + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToBytes32Map storage map) internal view returns (bytes32[] memory) { + return map._keys.values(); + } + + // UintToUintMap + + struct UintToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToUintMap storage map, uint256 key, uint256 value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToUintMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToUintMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToUintMap storage map, uint256 index) internal view returns (uint256, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToUintMap storage map, uint256 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToUintMap storage map, uint256 key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(key))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToUintMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintToAddressMap + + struct UintToAddressMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { + return set(map._inner, bytes32(key), bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (uint256(key), address(uint160(uint256(value)))); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(key)); + return (success, address(uint160(uint256(value)))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint160(uint256(get(map._inner, bytes32(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(UintToAddressMap storage map) internal view returns (uint256[] memory) { + bytes32[] memory store = keys(map._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressToUintMap + + struct AddressToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(AddressToUintMap storage map, address key, uint256 value) internal returns (bool) { + return set(map._inner, bytes32(uint256(uint160(key))), bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(AddressToUintMap storage map, address key) internal returns (bool) { + return remove(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(AddressToUintMap storage map, address key) internal view returns (bool) { + return contains(map._inner, bytes32(uint256(uint160(key)))); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(AddressToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressToUintMap storage map, uint256 index) internal view returns (address, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (address(uint160(uint256(key))), uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(AddressToUintMap storage map, address key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, bytes32(uint256(uint160(key)))); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(AddressToUintMap storage map, address key) internal view returns (uint256) { + return uint256(get(map._inner, bytes32(uint256(uint160(key))))); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(AddressToUintMap storage map) internal view returns (address[] memory) { + bytes32[] memory store = keys(map._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // Bytes32ToUintMap + + struct Bytes32ToUintMap { + Bytes32ToBytes32Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(Bytes32ToUintMap storage map, bytes32 key, uint256 value) internal returns (bool) { + return set(map._inner, key, bytes32(value)); + } + + /** + * @dev Removes a value from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(Bytes32ToUintMap storage map, bytes32 key) internal returns (bool) { + return remove(map._inner, key); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool) { + return contains(map._inner, key); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(Bytes32ToUintMap storage map) internal view returns (uint256) { + return length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the map. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32ToUintMap storage map, uint256 index) internal view returns (bytes32, uint256) { + (bytes32 key, bytes32 value) = at(map._inner, index); + return (key, uint256(value)); + } + + /** + * @dev Tries to returns the value associated with `key`. O(1). + * Does not revert if `key` is not in the map. + */ + function tryGet(Bytes32ToUintMap storage map, bytes32 key) internal view returns (bool, uint256) { + (bool success, bytes32 value) = tryGet(map._inner, key); + return (success, uint256(value)); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(Bytes32ToUintMap storage map, bytes32 key) internal view returns (uint256) { + return uint256(get(map._inner, key)); + } + + /** + * @dev Return the an array containing all the keys + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function keys(Bytes32ToUintMap storage map) internal view returns (bytes32[] memory) { + bytes32[] memory store = keys(map._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/src/libs/EnumerableMapEnrollment.sol b/src/libs/EnumerableMapEnrollment.sol index 66a4b0b..954cb21 100644 --- a/src/libs/EnumerableMapEnrollment.sol +++ b/src/libs/EnumerableMapEnrollment.sol @@ -2,8 +2,8 @@ pragma solidity >=0.6.11; // ============ External Imports ============ -import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "./EnumerableMap.sol"; +import "./EnumerableSet.sol"; // ============ Internal Imports ============ import {TypeCasts} from "./TypeCasts.sol"; diff --git a/src/libs/EnumerableSet.sol b/src/libs/EnumerableSet.sol new file mode 100644 index 0000000..4c7fc5e --- /dev/null +++ b/src/libs/EnumerableSet.sol @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (utils/structs/EnumerableSet.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. + +pragma solidity ^0.8.20; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ```solidity + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) + * and `uint256` (`UintSet`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableSet. + * ==== + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + // Position is the index of the value in the `values` array plus 1. + // Position 0 is used to mean a value is not in the set. + mapping(bytes32 value => uint256) _positions; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._positions[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We cache the value's position to prevent multiple reads from the same storage slot + uint256 position = set._positions[value]; + + if (position != 0) { + // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 valueIndex = position - 1; + uint256 lastIndex = set._values.length - 1; + + if (valueIndex != lastIndex) { + bytes32 lastValue = set._values[lastIndex]; + + // Move the lastValue to the index where the value to delete is + set._values[valueIndex] = lastValue; + // Update the tracked position of the lastValue (that was just moved) + set._positions[lastValue] = position; + } + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the tracked position for the deleted slot + delete set._positions[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._positions[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + return set._values[index]; + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + + // Bytes32Set + + struct Bytes32Set { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _add(set._inner, value); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { + return _remove(set._inner, value); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { + return _contains(set._inner, value); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(Bytes32Set storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { + return _at(set._inner, index); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(uint160(value)))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint160(uint256(_at(set._inner, index)))); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } +} diff --git a/src/tokens/README.md b/src/tokens/README.md new file mode 100755 index 0000000..894d94c --- /dev/null +++ b/src/tokens/README.md @@ -0,0 +1,7 @@ +# Tokens + +> [!IMPORTANT] +> Only the 'Exrtension' contracts should be used + +> [!WARNING] +> Repo under active development. Do not use in production. \ No newline at end of file diff --git a/src/tokens/extensions/ManifoldERC20Collateral.sol b/src/tokens/extensions/ManifoldERC20Collateral.sol new file mode 100755 index 0000000..9661ea6 --- /dev/null +++ b/src/tokens/extensions/ManifoldERC20Collateral.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; +import {FastTokenRouter} from "../libs/FastTokenRouter.sol"; +import {TokenRouter} from "../libs/TokenRouter.sol"; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +/** + * @title ERC20 Token Collateral that wraps an existing ERC20 with remote transfer functionality. + * @author Abacus Works + */ +contract FastManifoldERC20Collateral is FastTokenRouter, ManifoldERC20Collateral { + using SafeERC20 for IERC20; + + /** + * @notice Constructor + * @param erc20 Address of the token to keep as collateral + */ + constructor(address erc20, address _mailbox) ManifoldERC20Collateral(erc20, _mailbox) {} + + /** + * @dev delegates transfer logic to `_transferTo`. + * @inheritdoc FastTokenRouter + */ + function _handle(uint32 _origin, bytes32 _sender, bytes calldata _message) + internal + virtual + override(FastTokenRouter, TokenRouter) + { + FastTokenRouter._handle(_origin, _sender, _message); + } + + /** + * @dev Transfers `_amount` of `wrappedToken` to `_recipient`. + * @inheritdoc FastTokenRouter + */ + function _fastTransferTo(address _recipient, uint256 _amount) internal override { + wrappedToken.safeTransfer(_recipient, _amount); + } + + /** + * @dev Transfers in `_amount` of `wrappedToken` from `_recipient`. + * @inheritdoc FastTokenRouter + */ + function _fastRecieveFrom(address _sender, uint256 _amount) internal override { + wrappedToken.safeTransferFrom(_sender, address(this), _amount); + } +} diff --git a/src/tokens/extensions/ManifoldERC4626.sol b/src/tokens/extensions/ManifoldERC4626.sol new file mode 100755 index 0000000..47be562 --- /dev/null +++ b/src/tokens/extensions/ManifoldERC4626.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IXERC20} from "../interfaces/IXERC20.sol"; +import {ManifoldERC20} from "../ManifoldERC20.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Message} from "../../libs/Message.sol"; +import {TokenMessage} from "../libs/TokenMessage.sol"; +import {TokenRouter} from "../libs/TokenRouter.sol"; + +/** + * @title ERC20 Rebasing Token + * @author Abacus Works + */ +contract ManifoldERC4626 is ManifoldERC20 { + using Math for uint256; + using Message for bytes; + using TokenMessage for bytes; + + uint256 public constant PRECISION = 1e10; + uint32 public immutable collateralDomain; + uint256 public exchangeRate; // 1e10 + + constructor(uint8 _decimals, address _mailbox, uint32 _collateralDomain) ManifoldERC20(_decimals, _mailbox) { + collateralDomain = _collateralDomain; + exchangeRate = 1e10; + _disableInitializers(); + } + + /// Override to send shares instead of assets from synthetic + /// @inheritdoc TokenRouter + function _transferRemote( + uint32 _destination, + bytes32 _recipient, + uint256 _amountOrId, + uint256 _value, + bytes memory _hookMetadata, + address _hook + ) internal virtual override returns (bytes32 messageId) { + uint256 _shares = assetsToShares(_amountOrId); + _transferFromSender(_shares); + bytes memory _tokenMessage = TokenMessage.format(_recipient, _shares, bytes("")); + + messageId = _Router_dispatch(_destination, _value, _tokenMessage, _hookMetadata, _hook); + + emit SentTransferRemote(_destination, _recipient, _amountOrId); + } + + function _handle(uint32 _origin, bytes32 _sender, bytes calldata _message) internal virtual override { + if (_origin == collateralDomain) { + exchangeRate = abi.decode(_message.metadata(), (uint256)); + } + super._handle(_origin, _sender, _message); + } + + // Override to send shares locally instead of assets + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, assetsToShares(amount)); + return true; + } + + function shareBalanceOf(address account) public view returns (uint256) { + return super.balanceOf(account); + } + + function balanceOf(address account) public view virtual override returns (uint256) { + uint256 _balance = super.balanceOf(account); + return sharesToAssets(_balance); + } + + function assetsToShares(uint256 _amount) public view returns (uint256) { + return _amount.mulDiv(PRECISION, exchangeRate); + } + + function sharesToAssets(uint256 _shares) public view returns (uint256) { + return _shares.mulDiv(exchangeRate, PRECISION); + } +} diff --git a/src/tokens/extensions/ManifoldERC4626Collateral.sol b/src/tokens/extensions/ManifoldERC4626Collateral.sol new file mode 100755 index 0000000..6965c34 --- /dev/null +++ b/src/tokens/extensions/ManifoldERC4626Collateral.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {TokenMessage} from "../libs/TokenMessage.sol"; +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; +import {TypeCasts} from "../../libs/TypeCasts.sol"; + +/** + * @title ERC4626 Token Collateral with deposits collateral to a vault + * @author Abacus Works + */ +contract ManifoldERC4626Collateral is ManifoldERC20Collateral { + using TypeCasts for address; + using TokenMessage for bytes; + using Math for uint256; + + // Address of the ERC4626 compatible vault + ERC4626 public immutable vault; + uint256 public constant PRECISION = 1e10; + bytes32 public constant NULL_RECIPIENT = 0x0000000000000000000000000000000000000000000000000000000000000001; + + constructor(ERC4626 _vault, address _mailbox) ManifoldERC20Collateral(_vault.asset(), _mailbox) { + vault = _vault; + } + + function initialize(address _hook, address _interchainSecurityModule, address _owner) public override initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + + function _transferRemote( + uint32 _destination, + bytes32 _recipient, + uint256 _amount, + uint256 _value, + bytes memory _hookMetadata, + address _hook + ) internal virtual override returns (bytes32 messageId) { + // Can't override _transferFromSender only because we need to pass shares in the token message + _transferFromSender(_amount); + uint256 _shares = _depositIntoVault(_amount); + uint256 _exchangeRate = PRECISION.mulDiv(vault.totalAssets(), vault.totalSupply(), Math.Rounding.Down); + bytes memory _tokenMetadata = abi.encode(_exchangeRate); + + bytes memory _tokenMessage = TokenMessage.format(_recipient, _shares, _tokenMetadata); + + messageId = _Router_dispatch(_destination, _value, _tokenMessage, _hookMetadata, _hook); + + emit SentTransferRemote(_destination, _recipient, _shares); + } + + /** + * @dev Deposits into the vault and increment assetDeposited + * @param _amount amount to deposit into vault + */ + function _depositIntoVault(uint256 _amount) internal returns (uint256) { + wrappedToken.approve(address(vault), _amount); + return vault.deposit(_amount, address(this)); + } + + /** + * @dev Transfers `_amount` of `wrappedToken` from this contract to `_recipient`, and withdraws from vault + * @inheritdoc ManifoldERC20Collateral + */ + function _transferTo(address _recipient, uint256 _amount, bytes calldata) internal virtual override { + // withdraw with the specified amount of shares + vault.redeem(_amount, _recipient, address(this)); + } + + /** + * @dev Update the exchange rate on the synthetic token by accounting for additional yield accrued to the underlying vault + * @param _destinationDomain domain of the vault + */ + function rebase(uint32 _destinationDomain) public payable { + // force a rebase with an empty transfer to 0x1 + _transferRemote(_destinationDomain, NULL_RECIPIENT, 0, msg.value, bytes(""), address(0)); + } +} diff --git a/src/tokens/extensions/ManifoldERC4626OwnerCollateral.sol b/src/tokens/extensions/ManifoldERC4626OwnerCollateral.sol new file mode 100755 index 0000000..f98e4ec --- /dev/null +++ b/src/tokens/extensions/ManifoldERC4626OwnerCollateral.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; + +/** + * @title ERC20 Token Collateral with deposits collateral to a vault, the yield goes to the owner + * @author ltyu + */ +contract ManifoldERC4626OwnerCollateral is ManifoldERC20Collateral { + // Address of the ERC4626 compatible vault + ERC4626 public immutable vault; + + // Internal balance of total asset deposited + uint256 public assetDeposited; + + event ExcessSharesSwept(uint256 amount, uint256 assetsRedeemed); + + constructor(ERC4626 _vault, address _mailbox) ManifoldERC20Collateral(_vault.asset(), _mailbox) { + vault = _vault; + } + + function initialize(address _hook, address _interchainSecurityModule, address _owner) public override initializer { + wrappedToken.approve(address(vault), type(uint256).max); + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + + /** + * @dev Transfers `_amount` of `wrappedToken` from `msg.sender` to this contract, and deposit into vault + * @inheritdoc ManifoldERC20Collateral + */ + function _transferFromSender(uint256 _amount) internal override returns (bytes memory metadata) { + metadata = super._transferFromSender(_amount); + _depositIntoVault(_amount); + } + + /** + * @dev Deposits into the vault and increment assetDeposited + * @param _amount amount to deposit into vault + */ + function _depositIntoVault(uint256 _amount) internal { + assetDeposited += _amount; + vault.deposit(_amount, address(this)); + } + + /** + * @dev Transfers `_amount` of `wrappedToken` from this contract to `_recipient`, and withdraws from vault + * @inheritdoc ManifoldERC20Collateral + */ + function _transferTo(address _recipient, uint256 _amount, bytes calldata) internal virtual override { + _withdrawFromVault(_amount, _recipient); + } + + /** + * @dev Withdraws from the vault and decrement assetDeposited + * @param _amount amount to withdraw from vault + * @param _recipient address to deposit withdrawn underlying to + */ + function _withdrawFromVault(uint256 _amount, address _recipient) internal { + assetDeposited -= _amount; + vault.withdraw(_amount, _recipient, address(this)); + } + + /** + * @notice Allows the owner to redeem excess shares + */ + function sweep() external onlyOwner { + uint256 excessShares = vault.maxRedeem(address(this)) - vault.convertToShares(assetDeposited); + uint256 assetsRedeemed = vault.redeem(excessShares, owner(), address(this)); + emit ExcessSharesSwept(excessShares, assetsRedeemed); + } +} diff --git a/src/tokens/extensions/ManifoldERC721URICollateral.sol b/src/tokens/extensions/ManifoldERC721URICollateral.sol new file mode 100755 index 0000000..20ce817 --- /dev/null +++ b/src/tokens/extensions/ManifoldERC721URICollateral.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {ManifoldERC721Collateral} from "../ManifoldERC721Collateral.sol"; + +import {IERC721MetadataUpgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721MetadataUpgradeable.sol"; + +/** + * @title ERC721 Token Collateral that wraps an existing ERC721 with remote transfer and URI relay functionality. + * @author Abacus Works + */ +contract ManifoldERC721URICollateral is ManifoldERC721Collateral { + // solhint-disable-next-line no-empty-blocks + constructor(address erc721, address _mailbox) ManifoldERC721Collateral(erc721, _mailbox) {} + + /** + * @dev Transfers `_tokenId` of `wrappedToken` from `msg.sender` to this contract. + * @return The URI of `_tokenId` on `wrappedToken`. + * @inheritdoc ManifoldERC721Collateral + */ + function _transferFromSender(uint256 _tokenId) internal override returns (bytes memory) { + ManifoldERC721Collateral._transferFromSender(_tokenId); + return bytes(IERC721MetadataUpgradeable(address(wrappedToken)).tokenURI(_tokenId)); + } +} diff --git a/src/tokens/extensions/ManifoldERC721URIStorage.sol b/src/tokens/extensions/ManifoldERC721URIStorage.sol new file mode 100755 index 0000000..2f447d2 --- /dev/null +++ b/src/tokens/extensions/ManifoldERC721URIStorage.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {ManifoldERC721} from "../ManifoldERC721.sol"; + +import {ERC721URIStorageUpgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; +import {ERC721EnumerableUpgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; + +/** + * @title ERC721 Token that extends ERC721URIStorage with remote transfer and URI relay functionality. + * @author Abacus Works + */ +contract ManifoldERC721URIStorage is ManifoldERC721, ERC721URIStorageUpgradeable { + constructor(address _mailbox) ManifoldERC721(_mailbox) {} + + function balanceOf(address account) + public + view + override(ManifoldERC721, ERC721Upgradeable, IERC721Upgradeable) + returns (uint256) + { + return ManifoldERC721.balanceOf(account); + } + + /** + * @return _tokenURI The URI of `_tokenId`. + * @inheritdoc ManifoldERC721 + */ + function _transferFromSender(uint256 _tokenId) internal override returns (bytes memory _tokenURI) { + _tokenURI = bytes(tokenURI(_tokenId)); // requires minted + ManifoldERC721._transferFromSender(_tokenId); + } + + /** + * @dev Sets the URI for `_tokenId` to `_tokenURI`. + * @inheritdoc ManifoldERC721 + */ + function _transferTo(address _recipient, uint256 _tokenId, bytes calldata _tokenURI) internal override { + ManifoldERC721._transferTo(_recipient, _tokenId, _tokenURI); + _setTokenURI(_tokenId, string(_tokenURI)); // requires minted + } + + function tokenURI(uint256 tokenId) + public + view + override(ERC721Upgradeable, ERC721URIStorageUpgradeable) + returns (string memory) + { + return ERC721URIStorageUpgradeable.tokenURI(tokenId); + } + + function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) + internal + override(ERC721EnumerableUpgradeable, ERC721Upgradeable) + { + ERC721EnumerableUpgradeable._beforeTokenTransfer(from, to, tokenId, batchSize); + } + + function supportsInterface(bytes4 interfaceId) + public + view + override(ERC721EnumerableUpgradeable, ERC721URIStorageUpgradeable) + returns (bool) + { + return ERC721EnumerableUpgradeable.supportsInterface(interfaceId); + } + + function _burn(uint256 tokenId) internal override(ERC721URIStorageUpgradeable, ERC721Upgradeable) { + ERC721URIStorageUpgradeable._burn(tokenId); + } +} diff --git a/src/tokens/extensions/ManifoldFiatToken.sol b/src/tokens/extensions/ManifoldFiatToken.sol new file mode 100755 index 0000000..2d48b0a --- /dev/null +++ b/src/tokens/extensions/ManifoldFiatToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IFiatToken} from "../interfaces/IFiatToken.sol"; +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; + +// see https://github.com/circlefin/stablecoin-evm/blob/master/doc/tokendesign.md#issuing-and-destroying-tokens +contract ManifoldFiatToken is ManifoldERC20Collateral { + constructor(address _fiatToken, address _mailbox) ManifoldERC20Collateral(_fiatToken, _mailbox) {} + + function _transferFromSender(uint256 _amount) internal override returns (bytes memory metadata) { + // transfer amount to address(this) + metadata = super._transferFromSender(_amount); + // burn amount of address(this) balance + IFiatToken(address(wrappedToken)).burn(_amount); + } + + function _transferTo(address _recipient, uint256 _amount, bytes calldata /*metadata*/ ) internal override { + require(IFiatToken(address(wrappedToken)).mint(_recipient, _amount), "FiatToken mint failed"); + } +} diff --git a/src/tokens/extensions/ManifoldNativeScaled.sol b/src/tokens/extensions/ManifoldNativeScaled.sol new file mode 100755 index 0000000..6f5cb8d --- /dev/null +++ b/src/tokens/extensions/ManifoldNativeScaled.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {ManifoldNative} from "../ManifoldNative.sol"; +import {TokenRouter} from "../libs/TokenRouter.sol"; + +/** + * @title Native Token that scales native value by a fixed factor for consistency with other tokens. + * @dev The scale factor multiplies the `message.amount` to the local native token amount. + * Conversely, it divides the local native `msg.value` amount by `scale` to encode the `message.amount`. + * @author Abacus Works + */ +contract ManifoldNativeScaled is ManifoldNative { + uint256 public immutable scale; + + constructor(uint256 _scale, address _mailbox) ManifoldNative(_mailbox) { + scale = _scale; + } + + /** + * @inheritdoc ManifoldNative + * @dev Sends scaled `msg.value` (divided by `scale`) to `_recipient`. + */ + function transferRemote(uint32 _destination, bytes32 _recipient, uint256 _amount) + external + payable + override + returns (bytes32 messageId) + { + require(msg.value >= _amount, "Native: amount exceeds msg.value"); + uint256 _hookPayment = msg.value - _amount; + uint256 _scaledAmount = _amount / scale; + return _transferRemote(_destination, _recipient, _scaledAmount, _hookPayment); + } + + /** + * @dev Sends scaled `_amount` (multiplied by `scale`) to `_recipient`. + * @inheritdoc TokenRouter + */ + function _transferTo( + address _recipient, + uint256 _amount, + bytes calldata metadata // no metadata + ) internal override { + uint256 scaledAmount = _amount * scale; + ManifoldNative._transferTo(_recipient, scaledAmount, metadata); + } +} diff --git a/src/tokens/extensions/ManifoldXERC20.sol b/src/tokens/extensions/ManifoldXERC20.sol new file mode 100755 index 0000000..f74175f --- /dev/null +++ b/src/tokens/extensions/ManifoldXERC20.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IXERC20} from "../interfaces/IXERC20.sol"; +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; + +contract ManifoldXERC20 is ManifoldERC20Collateral { + constructor(address _xerc20, address _mailbox) ManifoldERC20Collateral(_xerc20, _mailbox) { + _disableInitializers(); + } + + function _transferFromSender(uint256 _amountOrId) internal override returns (bytes memory metadata) { + IXERC20(address(wrappedToken)).burn(msg.sender, _amountOrId); + return ""; + } + + function _transferTo(address _recipient, uint256 _amountOrId, bytes calldata /*metadata*/ ) internal override { + IXERC20(address(wrappedToken)).mint(_recipient, _amountOrId); + } +} diff --git a/src/tokens/extensions/ManifoldXERC20Lockbox.sol b/src/tokens/extensions/ManifoldXERC20Lockbox.sol new file mode 100755 index 0000000..7bebf8b --- /dev/null +++ b/src/tokens/extensions/ManifoldXERC20Lockbox.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import {IXERC20Lockbox} from "../interfaces/IXERC20Lockbox.sol"; +import {IXERC20, IERC20} from "../interfaces/IXERC20.sol"; +import {ManifoldERC20Collateral} from "../ManifoldERC20Collateral.sol"; + +contract ManifoldXERC20Lockbox is ManifoldERC20Collateral { + uint256 constant MAX_INT = 2 ** 256 - 1; + + IXERC20Lockbox public immutable lockbox; + IXERC20 public immutable xERC20; + + constructor(address _lockbox, address _mailbox) + ManifoldERC20Collateral(address(IXERC20Lockbox(_lockbox).ERC20()), _mailbox) + { + lockbox = IXERC20Lockbox(_lockbox); + xERC20 = lockbox.XERC20(); + approveLockbox(); + _disableInitializers(); + } + + /** + * @notice Approve the lockbox to spend the wrapped token and xERC20 + * @dev This function is idempotent and need not be access controlled + */ + function approveLockbox() public { + require(IERC20(wrappedToken).approve(address(lockbox), MAX_INT), "erc20 lockbox approve failed"); + require(xERC20.approve(address(lockbox), MAX_INT), "xerc20 lockbox approve failed"); + } + + /** + * @notice Initialize the contract + * @param _hook The address of the hook contract + * @param _ism The address of the interchain security module + * @param _owner The address of the owner + */ + function initialize(address _hook, address _ism, address _owner) public override initializer { + approveLockbox(); + _MailboxClient_initialize(_hook, _ism, _owner); + } + + function _transferFromSender(uint256 _amount) internal override returns (bytes memory) { + // transfer erc20 from sender + super._transferFromSender(_amount); + // convert erc20 to xERC20 + lockbox.deposit(_amount); + // burn xERC20 + xERC20.burn(address(this), _amount); + return bytes(""); + } + + function _transferTo(address _recipient, uint256 _amount, bytes calldata /*metadata*/ ) internal override { + // mint xERC20 + xERC20.mint(address(this), _amount); + // convert xERC20 to erc20 + lockbox.withdrawTo(_recipient, _amount); + } +} diff --git a/src/tokens/interfaces/IFiatToken.sol b/src/tokens/interfaces/IFiatToken.sol new file mode 100755 index 0000000..11a5afa --- /dev/null +++ b/src/tokens/interfaces/IFiatToken.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +// adapted from https://github.com/circlefin/stablecoin-evm +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IFiatToken is IERC20 { + /** + * @notice Allows a minter to burn some of its own tokens. + * @dev The caller must be a minter, must not be blacklisted, and the amount to burn + * should be less than or equal to the account's balance. + * @param _amount the amount of tokens to be burned. + */ + function burn(uint256 _amount) external; + + /** + * @notice Mints fiat tokens to an address. + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. Must be less than or equal + * to the minterAllowance of the caller. + * @return True if the operation was successful. + */ + function mint(address _to, uint256 _amount) external returns (bool); +} diff --git a/src/tokens/interfaces/IXERC20.sol b/src/tokens/interfaces/IXERC20.sol new file mode 100755 index 0000000..42d668f --- /dev/null +++ b/src/tokens/interfaces/IXERC20.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +// adapted from https://github.com/defi-wonderland/xERC20 + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IXERC20 is IERC20 { + /** + * @notice Mints tokens for a user + * @dev Can only be called by a minter + * @param _user The address of the user who needs tokens minted + * @param _amount The amount of tokens being minted + */ + function mint(address _user, uint256 _amount) external; + + /** + * @notice Burns tokens for a user + * @dev Can only be called by a minter + * @param _user The address of the user who needs tokens burned + * @param _amount The amount of tokens being burned + */ + function burn(address _user, uint256 _amount) external; + + /** + * @notice Updates the limits of any bridge + * @dev Can only be called by the owner + * @param _mintingLimit The updated minting limit we are setting to the bridge + * @param _burningLimit The updated burning limit we are setting to the bridge + * @param _bridge The address of the bridge we are setting the limits too + */ + function setLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external; + + function owner() external returns (address); + + /** + * @notice Returns the current limit of a bridge + * @param _bridge the bridge we are viewing the limits of + * @return _limit The limit the bridge has + */ + function burningCurrentLimitOf(address _bridge) external view returns (uint256 _limit); + + /** + * @notice Returns the current limit of a bridge + * @param _bridge the bridge we are viewing the limits of + * @return _limit The limit the bridge has + */ + function mintingCurrentLimitOf(address _bridge) external view returns (uint256 _limit); + + /** + * @notice Returns the max limit of a minter + * + * @param _minter The minter we are viewing the limits of + * @return _limit The limit the minter has + */ + function mintingMaxLimitOf(address _minter) external view returns (uint256 _limit); + + /** + * @notice Returns the max limit of a bridge + * + * @param _bridge the bridge we are viewing the limits of + * @return _limit The limit the bridge has + */ + function burningMaxLimitOf(address _bridge) external view returns (uint256 _limit); +} diff --git a/src/tokens/interfaces/IXERC20Lockbox.sol b/src/tokens/interfaces/IXERC20Lockbox.sol new file mode 100755 index 0000000..5ebeee7 --- /dev/null +++ b/src/tokens/interfaces/IXERC20Lockbox.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.4 <0.9.0; + +// adapted from https://github.com/defi-wonderland/xERC20 + +import {IXERC20} from "./IXERC20.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IXERC20Lockbox { + /** + * @notice The XERC20 token of this contract + */ + function XERC20() external returns (IXERC20); + + /** + * @notice The ERC20 token of this contract + */ + function ERC20() external returns (IERC20); + + /** + * @notice Deposit ERC20 tokens into the lockbox + * + * @param _amount The amount of tokens to deposit + */ + function deposit(uint256 _amount) external; + + /** + * @notice Deposit ERC20 tokens into the lockbox, and send the XERC20 to a user + * + * @param _user The user to send the XERC20 to + * @param _amount The amount of tokens to deposit + */ + function depositTo(address _user, uint256 _amount) external; + + /** + * @notice Deposit the native asset into the lockbox, and send the XERC20 to a user + * + * @param _user The user to send the XERC20 to + */ + function depositNativeTo(address _user) external payable; + + /** + * @notice Withdraw ERC20 tokens from the lockbox + * + * @param _amount The amount of tokens to withdraw + */ + function withdraw(uint256 _amount) external; + + /** + * @notice Withdraw ERC20 tokens from the lockbox + * + * @param _user The user to withdraw to + * @param _amount The amount of tokens to withdraw + */ + function withdrawTo(address _user, uint256 _amount) external; +} diff --git a/src/tokens/libs/FastTokenRouter.sol b/src/tokens/libs/FastTokenRouter.sol new file mode 100755 index 0000000..e06666b --- /dev/null +++ b/src/tokens/libs/FastTokenRouter.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {TypeCasts} from "../../libs/TypeCasts.sol"; + +import {TokenMessage} from "./TokenMessage.sol"; +import {TokenRouter} from "./TokenRouter.sol"; + +/** + * @title Common FastTokenRouter functionality for ERC20 Tokens with remote transfer support. + * @author Abacus Works + */ +abstract contract FastTokenRouter is TokenRouter { + using TypeCasts for bytes32; + using TokenMessage for bytes; + + uint256 public fastTransferId; + // maps `fastTransferId` to the filler address. + mapping(bytes32 => address) filledFastTransfers; + + /** + * @dev delegates transfer logic to `_transferTo`. + * @inheritdoc TokenRouter + */ + function _handle(uint32 _origin, bytes32, bytes calldata _message) internal virtual override { + bytes32 recipient = _message.recipient(); + uint256 amount = _message.amount(); + bytes calldata metadata = _message.metadata(); + _transferTo(recipient.bytes32ToAddress(), amount, _origin, metadata); + emit ReceivedTransferRemote(_origin, recipient, amount); + } + + /** + * @dev Transfers `_amount` of token to `_recipient`/`fastFiller` who provided LP. + * @dev Called by `handle` after message decoding. + */ + function _transferTo(address _recipient, uint256 _amount, uint32 _origin, bytes calldata _metadata) + internal + virtual + { + address _tokenRecipient = _getTokenRecipient(_recipient, _amount, _origin, _metadata); + + _fastTransferTo(_tokenRecipient, _amount); + } + + /** + * @dev allows an external user to full an unfilled fast transfer order. + * @param _recipient The recipient of the wrapped token on base chain. + * @param _amount The amount of wrapped tokens that is being bridged. + * @param _fastFee The fee the bridging entity will pay. + * @param _fastTransferId Id assigned on the remote chain to uniquely identify the transfer. + */ + function fillFastTransfer( + address _recipient, + uint256 _amount, + uint256 _fastFee, + uint32 _origin, + uint256 _fastTransferId + ) external virtual { + bytes32 filledFastTransfersKey = _getFastTransfersKey(_origin, _fastTransferId, _amount, _fastFee, _recipient); + require(filledFastTransfers[filledFastTransfersKey] == address(0), "request already filled"); + + filledFastTransfers[filledFastTransfersKey] = msg.sender; + + _fastRecieveFrom(msg.sender, _amount - _fastFee); + _fastTransferTo(_recipient, _amount - _fastFee); + } + + /** + * @dev Transfers `_amountOrId` token to `_recipient` on `_destination` domain. + * @dev Delegates transfer logic to `_fastTransferFromSender` implementation. + * @dev Emits `SentTransferRemote` event on the origin chain. + * @param _destination The identifier of the destination chain. + * @param _recipient The address of the recipient on the destination chain. + * @param _amountOrId The amount or identifier of tokens to be sent to the remote recipient. + * @return messageId The identifier of the dispatched message. + */ + function fastTransferRemote(uint32 _destination, bytes32 _recipient, uint256 _amountOrId, uint256 _fastFee) + public + payable + virtual + returns (bytes32 messageId) + { + uint256 _fastTransferId = fastTransferId + 1; + fastTransferId = _fastTransferId; + bytes memory metadata = _fastTransferFromSender(_amountOrId, _fastFee, _fastTransferId); + + messageId = _GasRouter_dispatch( + _destination, msg.value, TokenMessage.format(_recipient, _amountOrId, metadata), address(hook) + ); + emit SentTransferRemote(_destination, _recipient, _amountOrId); + } + + /** + * @dev Burns `_amount` of token from `msg.sender` balance. + * @dev Pays `_fastFee` of tokens to LP on source chain. + * @dev Returns `fastFee` as bytes in the form of metadata. + */ + function _fastTransferFromSender(uint256 _amount, uint256 _fastFee, uint256 _fastTransferId) + internal + virtual + returns (bytes memory) + { + _fastRecieveFrom(msg.sender, _amount); + return abi.encode(_fastFee, _fastTransferId); + } + + /** + * @dev returns an address that indicates who should receive the bridged tokens. + * @dev if _fastFees was included and someone filled the order before the mailbox made the contract call, the filler gets the funds. + */ + function _getTokenRecipient(address _recipient, uint256 _amount, uint32 _origin, bytes calldata _metadata) + internal + view + returns (address) + { + if (_metadata.length == 0) { + return _recipient; + } + + // decode metadata to extract `_fastFee` and `_fastTransferId`. + (uint256 _fastFee, uint256 _fastTransferId) = abi.decode(_metadata, (uint256, uint256)); + + address _fillerAddress = + filledFastTransfers[_getFastTransfersKey(_origin, _fastTransferId, _amount, _fastFee, _recipient)]; + if (_fillerAddress != address(0)) { + return _fillerAddress; + } + + return _recipient; + } + + /** + * @dev generates the key for storing the filler address of fast transfers. + */ + function _getFastTransfersKey( + uint32 _origin, + uint256 _fastTransferId, + uint256 _amount, + uint256 _fastFee, + address _recipient + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(_origin, _fastTransferId, _amount, _fastFee, _recipient)); + } + + /** + * @dev Should transfer `_amount` of tokens to `_recipient`. + * @dev The implementation is delegated. + */ + function _fastTransferTo(address _recipient, uint256 _amount) internal virtual; + + /** + * @dev Should collect `amount` of tokens from `_sender`. + * @dev The implementation is delegated. + */ + function _fastRecieveFrom(address _sender, uint256 _amount) internal virtual; +} diff --git a/src/tokens/libs/TokenMessage.sol b/src/tokens/libs/TokenMessage.sol new file mode 100755 index 0000000..4067632 --- /dev/null +++ b/src/tokens/libs/TokenMessage.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +library TokenMessage { + function format(bytes32 _recipient, uint256 _amount, bytes memory _metadata) internal pure returns (bytes memory) { + return abi.encodePacked(_recipient, _amount, _metadata); + } + + function recipient(bytes calldata message) internal pure returns (bytes32) { + return bytes32(message[0:32]); + } + + function amount(bytes calldata message) internal pure returns (uint256) { + return uint256(bytes32(message[32:64])); + } + + // alias for ERC721 + function tokenId(bytes calldata message) internal pure returns (uint256) { + return amount(message); + } + + function metadata(bytes calldata message) internal pure returns (bytes calldata) { + return message[64:]; + } +}