Mantle Liquid Staking Platform Contracts.
Mantle LSP is a permissionless ETH liquid staking protocol deployed on Ethereum L1 and is governed by Mantle Governance. It is the second core product of Mantle Ecosystem after Mantle Network L2.
mETH is the receipt token of Mantle LSP. It is a reward accumulating and permissionless ERC-20 token that can be used by various application such as: DeFi applications on Ethereum L1, Mantle Network L2, and any other chains; and centralized applications such as exchanges.
L1
| Contract | Address |
|---|---|
| Staking | 0xe3cBd06D7dadB3F4e6557bAb7EdD924CD1489E8f |
| METH | 0xd5F7838F5C461fefF7FE49ea5ebaF7728bB0ADfa |
| Oracle | 0x8735049F496727f824Cc0f2B174d826f5c408192 |
| OracleQuorumManager | 0x92e56d2146D54d5AEcB25CA36c89D027a6ea0D90 |
| UnstakeRequestsManager | 0x38fDF7b489316e03eD8754ad339cb5c4483FDcf9 |
| Pauser | 0x29Ab878aEd032e2e2c86FF4A9a9B05e3276cf1f8 |
| ReturnsAggregator | 0x1766be66fBb0a1883d41B4cfB0a533c5249D3b82 |
| ConsensusLayerReturnsReceiver | 0xD4e11C28E04c0c2bf370b7a9989498B7eA02493f |
| ExecutionLayerReturnsReceiver | 0xD6E4aA932147A3FE5311dA1b67D9e73da06F9cEf |
L2
| Contract | Address |
|---|---|
| METHL2 | 0xcDA86A272531e8640cD7F1a92c01839911B90bb0 |
The Staking contract is considered the public interface for the project, and is the only contract that users should ever need to interact with. It handles staking/unstaking operations, as well as the accounting for the project (i.e. how much ETH is controlled by the protocol).
The UnstakeRequestsManager contract is responsible for tracking unstake requests. It places each request into a first-in-first-out queue, and determines when requests are eligible for claiming. This is considered an 'internal' contract, and users must transact with the Staking contract to unstake.
The Oracle contract receives and validates oracle "records" which are reported by the off-chain oracle systems. It is responsible for ensuring that oracle records are valid and complete, and has extensive "sanity checks" to confirm that the provided data is within realistic expected bounds.
The OracleQuorumManager is the interface for off-chain oracles to send oracle records to the system. The contract has configurable properties which can be used to ensure that multiple, independent off-chain oracles agree on the data within a record before it is considered to be eligible to be written to the Oracle contract for verifcation and storage.
The ReturnsAggregator is responsible for processing all incoming "returns" (rewards and principals) from the protocol. It uses validated Oracle records to understand the source of the money which has been received, takes protocol fees where appropriate, and sends the remaining amount to the Staking contract to be compounded or to fill unstake requests.
This receiver is a simple contract which all consensus layer withdrawals arrive at. Money can be pulled from here into the protocol by the ReturnsAggregator.
This receiver is a simple contract which all execution layer rewards arrive at. These are gas tips from execution and MEV rewards. Money can be pulled from here into the protocol by the ReturnsAggregator.
The Pauser contract is a centralized pausing system which other contracts call to ensure that operations within the protocol are currently active and allowed. The Pauser may be invoked by other contract (e.g. the Oracle may pause the protocol if an unexpected report is detected), or by off-chain guardians.
The easiest way to get started is by using the bootstrap task. It deploys all the contracts and initiates validators by calling all
the appropriate functions for setup. Pass in additional arguments like number of validators (num) and an operator ID (operatorID).
task devnet:bootstrap start=0 num=2 operatorID=1 -- --broadcastDeploy all contracts using
task devnet:deployAll -- --broadcastUpgrade a contract to its new implementation in the src/ directory. The script will deploy a new implementation contract but you can
control whether it executes the upgrade onchain with the named argument execute. Note that even if you call the upgrade with
execute=false, you must also include the --broadcast option as the implementation contract must be deployed for the eventual upgrade
to work.
execute=false
Deploys the implementation contract and logs the byte encoded TimelockController upgrade call (calldata to schedule and execute) instead of performing the upgrade. This
is required if the upgrader is a multisig. To use, copy the calldata and execute a multisig transaction where the logged ProxyAdmin
address is the to value and the calldata is the data value.
execute=true
Deploys the implementation contract and executes the upgrade transaction onchain. It's useful for testing networks, like Goerli, where an EOA is the upgrader.
Example upgrading the Staking contract on devnet without executing it on chain:
task devnet:upgrade name=Staking execute=false -- --broadcastExample simulating the upgrade on the Staking contract on goerli and executing the upgrade on chain:
task goerli:upgrade name=Staking execute=trueExample upgrading the Staking contract on goerli without executing it on chain. Includes etherscan verification:
ETHERSCAN_API_KEY=<yourapikey> task goerli:upgrade name=Staking execute=false -- --broadcast --verifyNB: If you forgot to verify the contract after upgrading, you can repeat the command including --verify --resume.
Each receiver wallet is upgraded individually. The upgrade script will deploy a new implementation contract for each one. For example, suppose you ran:
task devnet:upgrade name=ConsensusLayerReceiver execute=true -- --broadcastThis will deploy a new ReturnsReceiver implementation contract and upgrade the ConsensusLayerReceiver proxy contract to use it. However,
the ExecutionLayerReceiver proxy contract will remain unchanged.
*Note: To be used after running report generation in services.*
Ensure you have a reports.json file which you get from the report generation done in in the services. Then, you can run the following to modify existing reports on Goerli:
task goerli:modifyExistingRecords file=reports.json -- --slow --broadcastUsing --slow will ensure that each transaction is completed before running the next one.
Add a new initiator (e.g. the default devnet sender). Might need to call devnet:setStakingAllowlistFlag below first.
task devnet:addInitiator initiator=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -- --broadcastAdd a new oracle reporter (e.g. the default devnet sender)
task devnet:addReporter reporter=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -- --broadcastAdd a new allocator service (e.g. the default devnet sender)
task devnet:addAllocator allocator=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -- --broadcastRemoves the allowlist flag in devnet to give initiators the right to stake.
task devnet:setStakingAllowlistFlag isStakingAllowlist=false -- --broadcastHelper task to prepare validators for deposits. Allocates 1000 ETH by default to deposit.
task devnet:prepareDeposits stake=1000 -- --broadcastInitiate new validators via the staking contract. Pass in additional arguments like number of validators (num) and an operator ID (operatorID).
task devnet:initiateValidators start=0 num=2 operatorID=1 -- --broadcastThis stakes 32 ETH per validator and initites new validators using the deposit payloads defined in ../services/devnet/consensus/validator_keys/deposit_data-*.json.
Make sure that num is less than or equal to number of payloads available there. Might need to call devnet:addInitiator first.
Or deposit directly to the beacon deposit contract using the same data
task devnet:depositBeacon start=0 num=2 -- --broadcastUpdate the size of update window for OracleQuorumManager (in number of blocks)
task devnet:setQuorumWindowBlocks num=10 -- --broadcastIf the number of slots in an epoch is different than the usual 32 (as in spec/mainnet), you need to tune here. This is needs to be 2 epochs. For instance, if your devnet has 4 slots per epoch, you'd use 8. See Oracle.sol for more details.
task devnet:setFinalizationBlockNumberDelta num=8 -- --broadcastUpdate the quorum thresholds for OracleQuorumManager. For the example below, abs=1 means at least two reporters' reports must be the same AND rel=5000 means at least 50% of the reporters must agree.
task devnet:setQuorumThresholds abs=2 rel=5000 -- --broadcastUnpause all contracts & operations:
task devnet:unpauseAll -- --broadcastUpdate the minimum size of a report in number of blocks. Useful for speeding up local devnet testing:
task devnet:setMinReportSizeBlocks num=10 -- --broadcastUpdate the maximum gain per block in consensus layer rewards. This is used to circumvent sanity checks we have, when we're using a local devnet (the sanity checks we have are set up according to mainnet parameters)
task devnet:setMaxConsensusLayerGainPerBlockPPT num=190250000 -- --broadcastTo allow cast 4byte to correctly decode our function/event/error selectors, we need to push them to the signature database.
To trigger this run
task pushSelectors