Skip to content

Commit 10081e0

Browse files
committed
feat(protocol-contracts): revert on 0 shares redeem and redeem request
1 parent d648326 commit 10081e0

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

protocol-contracts/staking/contracts/OperatorStaking.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp
7575
/// @dev Thrown when the controller address is not valid (e.g., zero address).
7676
error InvalidController();
7777

78+
/// @dev Thrown when the number of shares to redeem or request redeem is zero.
79+
error InvalidShares();
80+
7881
modifier onlyOwner() {
7982
require(msg.sender == owner(), CallerNotProtocolStakingOwner(msg.sender));
8083
_;
@@ -173,7 +176,7 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp
173176
* @return releaseTime The timestamp when the assets will be available for withdrawal.
174177
*/
175178
function requestRedeem(uint208 shares, address controller, address ownerRedeem) public virtual returns (uint48) {
176-
if (shares == 0) return type(uint48).max;
179+
require(shares != 0, InvalidShares());
177180
require(controller != address(0), InvalidController());
178181
if (msg.sender != ownerRedeem) {
179182
_spendAllowance(ownerRedeem, msg.sender, shares);
@@ -213,6 +216,7 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp
213216
address receiver,
214217
address controller
215218
) public virtual nonReentrant returns (uint256) {
219+
require(shares != 0, InvalidShares());
216220
require(msg.sender == controller || isOperator(controller, msg.sender), Unauthorized());
217221

218222
uint256 maxShares = maxRedeem(controller);

protocol-contracts/staking/test/OperatorStaking.test.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,20 @@ describe('OperatorStaking', function () {
210210
describe('redeem', async function () {
211211
it('simple redemption', async function () {
212212
await this.mock.connect(this.delegator1).deposit(ethers.parseEther('1'), this.delegator1);
213-
await this.mock
214-
.connect(this.delegator1)
215-
.requestRedeem(await this.mock.balanceOf(this.delegator1), this.delegator1, this.delegator1);
213+
await expect(
214+
this.mock
215+
.connect(this.delegator1)
216+
.requestRedeem(await this.mock.balanceOf(this.delegator1), this.delegator1, this.delegator1),
217+
)
218+
.to.emit(this.mock, 'RedeemRequest')
219+
.withArgs(
220+
this.delegator1,
221+
this.delegator1,
222+
0,
223+
this.delegator1,
224+
ethers.parseEther('1'),
225+
BigInt(await time.latest()) + (await this.protocolStaking.unstakeCooldownPeriod()),
226+
);
216227

217228
await expect(this.mock.pendingRedeemRequest(0, this.delegator1)).to.eventually.eq(ethers.parseEther('1'));
218229
await expect(this.mock.claimableRedeemRequest(0, this.delegator1)).to.eventually.eq(0);
@@ -228,11 +239,13 @@ describe('OperatorStaking', function () {
228239
await expect(this.token.balanceOf(this.mock)).to.eventually.be.eq(0);
229240
});
230241

231-
it('zero redemption should terminate early', async function () {
232-
await expect(this.mock.connect(this.delegator1).requestRedeem(0, this.delegator1, this.delegator1)).to.not.emit(
233-
this.mock,
234-
'RedeemRequest',
235-
);
242+
it('should return release time', async function () {
243+
await this.mock.connect(this.delegator1).deposit(ethers.parseEther('1'), this.delegator1);
244+
await expect(
245+
this.mock
246+
.connect(this.delegator1)
247+
.requestRedeem.staticCall(await this.mock.balanceOf(this.delegator1), this.delegator1, this.delegator1),
248+
).to.eventually.eq(BigInt(await time.latest()) + (await this.protocolStaking.unstakeCooldownPeriod()));
236249
});
237250

238251
it('should not redeem twice', async function () {
@@ -251,6 +264,18 @@ describe('OperatorStaking', function () {
251264
).to.not.emit(this.token, 'Transfer');
252265
});
253266

267+
it('should revert on requestRedeem 0 shares', async function () {
268+
await expect(
269+
this.mock.connect(this.delegator1).requestRedeem(0, this.delegator1, this.delegator1),
270+
).to.be.revertedWithCustomError(this.mock, 'InvalidShares');
271+
});
272+
273+
it('should revert on redeem 0 shares', async function () {
274+
await expect(
275+
this.mock.connect(this.delegator1).redeem(0, this.delegator1, this.delegator1),
276+
).to.be.revertedWithCustomError(this.mock, 'InvalidShares');
277+
});
278+
254279
it('should revert on redeem more than available', async function () {
255280
await this.mock.connect(this.delegator1).deposit(ethers.parseEther('10'), this.delegator1);
256281
await this.mock.connect(this.delegator1).requestRedeem(ethers.parseEther('1'), this.delegator1, this.delegator1);

0 commit comments

Comments
 (0)