-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity ^0.8.12; | ||
|
||
/** | ||
* manage deposits and stakes. | ||
* deposit is just a balance used to pay for UserOperations (either by a paymaster or an account) | ||
* stake is value locked for at least "unstakeDelay" by the staked entity. | ||
*/ | ||
interface IStakeManager { | ||
|
||
event Deposited( | ||
address indexed account, | ||
uint256 totalDeposit | ||
); | ||
|
||
event Withdrawn( | ||
address indexed account, | ||
address withdrawAddress, | ||
uint256 amount | ||
); | ||
|
||
/// Emitted when stake or unstake delay are modified | ||
event StakeLocked( | ||
address indexed account, | ||
uint256 totalStaked, | ||
uint256 unstakeDelaySec | ||
); | ||
|
||
/// Emitted once a stake is scheduled for withdrawal | ||
event StakeUnlocked( | ||
address indexed account, | ||
uint256 withdrawTime | ||
); | ||
|
||
event StakeWithdrawn( | ||
address indexed account, | ||
address withdrawAddress, | ||
uint256 amount | ||
); | ||
|
||
/** | ||
* @param deposit the entity's deposit | ||
* @param staked true if this entity is staked. | ||
* @param stake actual amount of ether staked for this entity. | ||
* @param unstakeDelaySec minimum delay to withdraw the stake. | ||
* @param withdrawTime - first block timestamp where 'withdrawStake' will be callable, or zero if already locked | ||
* @dev sizes were chosen so that (deposit,staked, stake) fit into one cell (used during handleOps) | ||
* and the rest fit into a 2nd cell. | ||
* 112 bit allows for 10^15 eth | ||
* 48 bit for full timestamp | ||
* 32 bit allows 150 years for unstake delay | ||
*/ | ||
struct DepositInfo { | ||
uint112 deposit; | ||
bool staked; | ||
uint112 stake; | ||
uint32 unstakeDelaySec; | ||
uint48 withdrawTime; | ||
} | ||
|
||
//API struct used by getStakeInfo and simulateValidation | ||
struct StakeInfo { | ||
uint256 stake; | ||
uint256 unstakeDelaySec; | ||
} | ||
|
||
/// @return info - full deposit information of given account | ||
function getDepositInfo(address account) external view returns (DepositInfo memory info); | ||
|
||
/// @return the deposit (for gas payment) of the account | ||
function balanceOf(address account) external view returns (uint256); | ||
|
||
/** | ||
* add to the deposit of the given account | ||
*/ | ||
function depositTo(address account) external payable; | ||
|
||
/** | ||
* add to the account's stake - amount and delay | ||
* any pending unstake is first cancelled. | ||
* @param _unstakeDelaySec the new lock duration before the deposit can be withdrawn. | ||
*/ | ||
function addStake(uint32 _unstakeDelaySec) external payable; | ||
|
||
/** | ||
* attempt to unlock the stake. | ||
* the value can be withdrawn (using withdrawStake) after the unstake delay. | ||
*/ | ||
function unlockStake() external; | ||
|
||
/** | ||
* withdraw from the (unlocked) stake. | ||
* must first call unlockStake and wait for the unstakeDelay to pass | ||
* @param withdrawAddress the address to send withdrawn value. | ||
*/ | ||
function withdrawStake(address payable withdrawAddress) external; | ||
|
||
/** | ||
* withdraw from the deposit. | ||
* @param withdrawAddress the address to send withdrawn value. | ||
* @param withdrawAmount the amount to withdraw. | ||
*/ | ||
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
pragma solidity ^0.8.12; | ||
|
||
import "../interfaces/IStakeManager.sol"; | ||
|
||
/* solhint-disable avoid-low-level-calls */ | ||
/* solhint-disable not-rely-on-time */ | ||
/** | ||
* manage deposits and stakes. | ||
* deposit is just a balance used to pay for UserOperations (either by a paymaster or an account) | ||
* stake is value locked for at least "unstakeDelay" by a paymaster. | ||
*/ | ||
contract StakeManager is IStakeManager { | ||
|
||
/// maps paymaster to their deposits and stakes | ||
mapping(address => DepositInfo) public deposits; | ||
|
||
/// @inheritdoc IStakeManager | ||
function getDepositInfo(address account) public view returns (DepositInfo memory info) { | ||
return deposits[account]; | ||
} | ||
|
||
// internal method to return just the stake info | ||
function _getStakeInfo(address addr) internal view returns (StakeInfo memory info) { | ||
DepositInfo storage depositInfo = deposits[addr]; | ||
info.stake = depositInfo.stake; | ||
info.unstakeDelaySec = depositInfo.unstakeDelaySec; | ||
} | ||
|
||
/// return the deposit (for gas payment) of the account | ||
function balanceOf(address account) public view returns (uint256) { | ||
return deposits[account].deposit; | ||
} | ||
|
||
receive() external payable { | ||
depositTo(msg.sender); | ||
} | ||
|
||
function _incrementDeposit(address account, uint256 amount) internal { | ||
DepositInfo storage info = deposits[account]; | ||
uint256 newAmount = info.deposit + amount; | ||
require(newAmount <= type(uint112).max, "deposit overflow"); | ||
info.deposit = uint112(newAmount); | ||
} | ||
|
||
/** | ||
* add to the deposit of the given account | ||
*/ | ||
function depositTo(address account) public payable { | ||
_incrementDeposit(account, msg.value); | ||
DepositInfo storage info = deposits[account]; | ||
emit Deposited(account, info.deposit); | ||
} | ||
|
||
/** | ||
* add to the account's stake - amount and delay | ||
* any pending unstake is first cancelled. | ||
* @param unstakeDelaySec the new lock duration before the deposit can be withdrawn. | ||
*/ | ||
function addStake(uint32 unstakeDelaySec) public payable { | ||
DepositInfo storage info = deposits[msg.sender]; | ||
require(unstakeDelaySec > 0, "must specify unstake delay"); | ||
require(unstakeDelaySec >= info.unstakeDelaySec, "cannot decrease unstake time"); | ||
uint256 stake = info.stake + msg.value; | ||
require(stake > 0, "no stake specified"); | ||
require(stake <= type(uint112).max, "stake overflow"); | ||
deposits[msg.sender] = DepositInfo( | ||
info.deposit, | ||
true, | ||
uint112(stake), | ||
unstakeDelaySec, | ||
0 | ||
); | ||
emit StakeLocked(msg.sender, stake, unstakeDelaySec); | ||
} | ||
|
||
/** | ||
* attempt to unlock the stake. | ||
* the value can be withdrawn (using withdrawStake) after the unstake delay. | ||
*/ | ||
function unlockStake() external { | ||
DepositInfo storage info = deposits[msg.sender]; | ||
require(info.unstakeDelaySec != 0, "not staked"); | ||
require(info.staked, "already unstaking"); | ||
uint48 withdrawTime = uint48(block.timestamp) + info.unstakeDelaySec; | ||
info.withdrawTime = withdrawTime; | ||
info.staked = false; | ||
emit StakeUnlocked(msg.sender, withdrawTime); | ||
} | ||
|
||
|
||
/** | ||
* withdraw from the (unlocked) stake. | ||
* must first call unlockStake and wait for the unstakeDelay to pass | ||
* @param withdrawAddress the address to send withdrawn value. | ||
*/ | ||
function withdrawStake(address payable withdrawAddress) external { | ||
DepositInfo storage info = deposits[msg.sender]; | ||
uint256 stake = info.stake; | ||
require(stake > 0, "No stake to withdraw"); | ||
require(info.withdrawTime > 0, "must call unlockStake() first"); | ||
require(info.withdrawTime <= block.timestamp, "Stake withdrawal is not due"); | ||
info.unstakeDelaySec = 0; | ||
info.withdrawTime = 0; | ||
info.stake = 0; | ||
emit StakeWithdrawn(msg.sender, withdrawAddress, stake); | ||
(bool success,) = withdrawAddress.call{value : stake}(""); | ||
require(success, "failed to withdraw stake"); | ||
} | ||
|
||
/** | ||
* withdraw from the deposit. | ||
* @param withdrawAddress the address to send withdrawn value. | ||
* @param withdrawAmount the amount to withdraw. | ||
*/ | ||
function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external { | ||
DepositInfo storage info = deposits[msg.sender]; | ||
require(withdrawAmount <= info.deposit, "Withdraw amount too large"); | ||
info.deposit = uint112(info.deposit - withdrawAmount); | ||
emit Withdrawn(msg.sender, withdrawAddress, withdrawAmount); | ||
(bool success,) = withdrawAddress.call{value : withdrawAmount}(""); | ||
require(success, "failed to withdraw"); | ||
} | ||
} |