Skip to content

Commit

Permalink
Adding stake manager
Browse files Browse the repository at this point in the history
  • Loading branch information
shahafn committed Aug 25, 2024
1 parent 0ac9ff0 commit 71bde73
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
104 changes: 104 additions & 0 deletions contracts/interfaces/IStakeManager.sol
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;
}
124 changes: 124 additions & 0 deletions contracts/predeploys/StakeManager.sol
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");
}
}

0 comments on commit 71bde73

Please sign in to comment.