@@ -4,7 +4,8 @@ pragma solidity ^0.8.20;
44import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol " ;
55import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol " ;
66import "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
7- import "@openzeppelin/contracts/security/ReentrancyGuard.sol " ;
7+ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
8+ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol " ;
89import "./ValidatorLogic.sol " ;
910
1011/**
@@ -16,6 +17,7 @@ import "./ValidatorLogic.sol";
1617 * Manages staking positions for granular stake tracking.
1718 */
1819contract ValidatorFactory is ReentrancyGuard {
20+ using SafeERC20 for IERC20 ;
1921 // Events
2022 event ValidatorCreated (address indexed validator , address indexed proxy , uint256 stake );
2123 event ValidatorRemoved (address indexed validator );
@@ -41,6 +43,7 @@ contract ValidatorFactory is ReentrancyGuard {
4143 mapping (address => bool ) public isValidator;
4244 address [] public validators;
4345
46+ // Custom errors
4447 error SenderNotValidator ();
4548 error InsufficientStakeAmount ();
4649 error AlreadyValidator ();
@@ -65,189 +68,65 @@ contract ValidatorFactory is ReentrancyGuard {
6568 }
6669
6770 /**
68- * @dev Create a new validator
69- * @return proxy Address of the created proxy
71+ * @dev Stake tokens as a validator. Deploys proxy if not already present.
7072 */
71- function registerValidator (uint256 amount ) external nonReentrant returns (address proxy ) {
72- if (amount < minimumStake) {
73- revert InsufficientStakeAmount ();
74- }
75- if (isValidator[msg .sender ]) {
76- revert AlreadyValidator ();
77- }
78- if (validators.length >= maxValidators) {
79- revert MaxValidatorsReached ();
80- }
81- if (! stakingToken.safeTransferFrom (msg .sender , address (this ), amount)) { //TODO: CREATE2
82- revert TransferFailed ();
83- }
84-
85- proxy = _registerValidator (amount);
86-
87- emit ValidatorCreated (msg .sender , proxy, amount);
88- }
73+ function stake (uint256 amount ) external nonReentrant {
74+ require (amount >= minimumStake, "Stake below minimum " );
75+ require (! isValidator[msg .sender ], "Already validator " );
76+ require (validators.length < maxValidators, "Max validators reached " );
8977
90- /**
91- * @dev Create a new staking position for a validator
92- * @param amount Amount to stake
93- * @param description Description for the position
94- * @return positionId ID of the created position
95- */
96- function createStakingPosition (uint256 amount , string memory description ) external nonReentrant onlyValidator returns (uint256 positionId ) {
97- if (amount == 0 ) {
98- revert InsufficientStakeAmount ();
99- }
100-
101- address proxy = validatorToProxy[msg .sender ];
102-
103- // Transfer tokens to factory first
104- if (! stakingToken.safeTransferFrom (msg .sender , address (this ), amount)) {
105- revert TransferFailed ();
106- }
107-
108- // Transfer to proxy
109- stakingToken.safeTransfer (proxy, amount);
110-
111- // Create position
112- positionId = ValidatorLogic (proxy).createStakingPosition (amount, description);
113-
114- emit StakingPositionCreated (msg .sender , positionId, amount, description);
115- return positionId;
116- }
78+ // Compute proxy address
79+ address proxy = computeProxyAddress (msg .sender , amount);
80+ // Pre-fund the proxy
81+ require (stakingToken.transferFrom (msg .sender , proxy, amount), "Transfer failed " );
11782
118- /**
119- * @dev Increase stake in an existing position
120- * @param positionId ID of the position to increase
121- * @param amount Amount to add
122- * @return newAmount New total amount in the position
123- */
124- function increasePositionStake (uint256 positionId , uint256 amount ) external nonReentrant onlyValidator returns (uint256 newAmount ) {
125- if (amount == 0 ) {
126- revert InsufficientStakeAmount ();
127- }
128-
129- address proxy = validatorToProxy[msg .sender ];
130-
131- // Transfer tokens to factory first
132- if (! stakingToken.safeTransferFrom (msg .sender , address (this ), amount)) {
133- revert TransferFailed ();
134- }
135-
136- // Transfer to proxy
137- stakingToken.safeTransfer (proxy, amount);
138-
139- // Increase position stake
140- newAmount = ValidatorLogic (proxy).increasePositionStake (positionId, amount);
141-
142- uint256 totalStake = getValidatorStake (msg .sender );
143- emit StakeUpdated (msg .sender , totalStake - amount, totalStake);
144-
145- return newAmount;
146- }
147-
148- /**
149- * @dev Close a staking position (partial unstake)
150- * @param positionId ID of the position to close
151- * @return amount Amount that was in the position
152- */
153- function closeStakingPosition (uint256 positionId ) external nonReentrant onlyValidator returns (uint256 amount ) {
154- address proxy = validatorToProxy[msg .sender ];
155-
156- // Close position
157- amount = ValidatorLogic (proxy).closeStakingPosition (positionId);
158-
159- // Transfer tokens back to validator
160- stakingToken.safeTransfer (msg .sender , amount);
161-
162- emit StakingPositionClosed (msg .sender , positionId, amount);
163- emit Unstaked (msg .sender , amount);
164-
165- return amount;
166- }
167-
168- /**
169- * @dev Get all active positions for a validator
170- * @param validator Validator address
171- * @return positions Array of active position IDs
172- */
173- function getValidatorActivePositions (address validator ) external view returns (uint256 [] memory positions ) {
174- if (! isValidator[validator]) {
175- return new uint256 [](0 );
83+ // Deploy proxy with CREATE2
84+ bytes memory data = abi.encodeWithSelector (
85+ ValidatorLogic.initialize.selector ,
86+ msg .sender ,
87+ address (stakingToken),
88+ amount
89+ );
90+ bytes32 salt = keccak256 (abi.encodePacked (msg .sender ));
91+ bytes memory bytecode = abi.encodePacked (
92+ type (BeaconProxy).creationCode,
93+ abi.encode (address (beacon), data)
94+ );
95+ assembly {
96+ let deployed := create2 (0 , add (bytecode, 0x20 ), mload (bytecode), salt)
97+ if iszero (deployed) { revert (0 , 0 ) }
17698 }
177-
178- address proxy = validatorToProxy[validator];
179- return ValidatorLogic (proxy).getValidatorActivePositions (validator);
99+ validatorToProxy[msg .sender ] = proxy;
100+ isValidator[msg .sender ] = true ;
101+ validators.push (msg .sender );
102+ emit ValidatorCreated (msg .sender , proxy, amount);
180103 }
181104
182105 /**
183- * @dev Get position details
184- * @param validator Validator address
185- * @param positionId ID of the position
186- * @return position StakingPosition struct
106+ * @dev Unstake tokens as a validator. If the remaining stake is below minimum, remove validator and recursively unstake the rest.
187107 */
188- function getStakingPosition (address validator , uint256 positionId ) external view returns (ValidatorLogic.StakingPosition memory position ) {
189- if (! isValidator[validator]) {
190- revert PositionNotFound ();
191- }
192-
193- address proxy = validatorToProxy[validator];
194- return ValidatorLogic (proxy).getStakingPosition (positionId);
195- }
196-
197- function increaseStake (uint256 amount ) external nonReentrant onlyValidator {
198- uint256 totalStake = getValidatorStake (msg .sender );
108+ function unstake (uint256 amount ) external nonReentrant onlyValidator {
199109 address proxy = validatorToProxy[msg .sender ];
200-
201- uint256 stakedAmount = ValidatorLogic (proxy). stake (amount );
202-
203- emit StakeUpdated ( msg . sender , totalStake, totalStake + stakedAmount);
204- }
205-
206- function decreaseStake ( uint256 amount ) external nonReentrant onlyValidator {
207- uint256 totalStake = getValidatorStake ( msg . sender ) ;
208- address proxy = validatorToProxy[ msg . sender ] ;
209-
210- if (amount > totalStake) {
211- revert AmountExceedsTotalStake () ;
110+ uint256 unstaked = ValidatorLogic (proxy). unstake (amount);
111+ require (stakingToken. transfer ( msg . sender , unstaked), " Transfer failed " );
112+ emit Unstaked ( msg . sender , unstaked);
113+
114+ uint256 remainingStake = ValidatorLogic (proxy). getStakeAmount ();
115+ if (remainingStake < minimumStake && remainingStake > 0 ) {
116+ // Unstake the residual below minimum
117+ uint256 residual = remainingStake ;
118+ uint256 unstakedResidual = ValidatorLogic (proxy). unstake (residual) ;
119+ require (stakingToken. transfer ( msg . sender , unstakedResidual), " Transfer failed " );
120+ emit Unstaked ( msg . sender , unstakedResidual);
121+ remainingStake = 0 ;
212122 }
213-
214- (uint256 unstakedAmount , bool shouldRemove ) = ValidatorLogic (proxy).unstake (amount);
215-
216- if (shouldRemove) {
123+ if (remainingStake == 0 ) {
217124 _removeValidatorFromArray (msg .sender );
218125 isValidator[msg .sender ] = false ;
219126 emit ValidatorRemoved (msg .sender );
220127 }
221- emit Unstaked (msg .sender , unstakedAmount);
222128 }
223129
224- /**
225- * @dev Slash validator stake (for malicious behavior)
226- * @param validator Validator to slash
227- * @param amount Amount to slash
228- * @param reason Reason for slashing
229- */
230- function slashValidator (address validator , uint256 amount , string memory reason ) external onlyConsensusModule {
231- if (! isValidator[validator]) {
232- revert SenderNotValidator ();
233- }
234-
235- address proxy = validatorToProxy[validator];
236- uint256 currentStake = getValidatorStake (validator);
237-
238- if (amount > currentStake) {
239- amount = currentStake; // Slash entire stake if amount exceeds
240- }
241-
242- ValidatorLogic (proxy).slashStake (amount);
243-
244- // Transfer slashed tokens to consensus module
245- stakingToken.safeTransfer (consensusModule, amount);
246-
247- emit ValidatorSlashed (validator, amount, reason);
248- }
249-
250-
251130 /**
252131 * @dev Get top N validators by stake using optimized QuickSelect algorithm
253132 * @param count Number of validators to return
@@ -350,15 +229,6 @@ contract ValidatorFactory is ReentrancyGuard {
350229 return i;
351230 }
352231
353- /**
354- * @dev Get top N validators by stake (simplified version for backward compatibility)
355- * @param count Number of validators to return
356- * @return topValidators Array of validator addresses
357- */
358- function getTopNValidatorsSimple (uint256 count ) external view returns (address [] memory topValidators ) {
359- (topValidators, ) = getTopNValidators (count);
360- }
361-
362232 /**
363233 * @dev Get all validators
364234 * @return allValidators Array of all validator addresses
@@ -389,7 +259,7 @@ contract ValidatorFactory is ReentrancyGuard {
389259 * @param validator Validator address
390260 * @return stakeAmount Stake amount
391261 */
392- function getValidatorStake (address validator ) external view returns (uint256 ) {
262+ function getValidatorStake (address validator ) public view returns (uint256 ) {
393263 if (! isValidator[validator]) return 0 ;
394264 address proxy = validatorToProxy[validator];
395265 return ValidatorLogic (proxy).getStakeAmount ();
@@ -457,7 +327,7 @@ contract ValidatorFactory is ReentrancyGuard {
457327 abi.encode (address (beacon), data)
458328 );
459329
460- address predicted = computeProxyAddress (msg .sender );
330+ address predicted = computeProxyAddress (msg .sender , _amount );
461331 stakingToken.safeTransferFrom (msg .sender , predicted, _amount);
462332
463333 assembly {
0 commit comments