Smart Security Practices From The Best
What do Lido, Red Stone, YieldNest, and Braintrust have in common? They’ve developed effective methods for improving security without drastically increasing costs. Top-tier protocol […]
Full withdrawals do not decrease user voting power when the Locking contract is stopped.
Under specific conditions, the owner may stop the Locking contract for investigation or other reasons using the stop() function. When the contract is stopped, the logic in the if statement is skipped, and user can withdraw all tokens without bias.
This is good and is intended so that users can withdraw their funds in the event of an emergency. The problem is that after withdrawing these funds, user voting power remains unreset, and the balanceOf function still returns the veMENTO from before the withdrawal.
function balanceOf(address account) external view returns (uint256) {
if ((accounts[account].balance.initial.bias == 0) || (stopped)) {
return 0;
}
uint32 currentBlock = getBlockNumber();
uint32 time = roundTimestamp(currentBlock);
return accounts[account].balance.actualValue(time, currentBlock);
}POC below illustrates the issue. To check how voting power is cleared without stops, comment out the lines with the owner’s actions.
[PASS] test_POC_Voting_Power_Remains_Despite_Withdrawal() (gas: 622229)
Logs:
veMento balanceOf(alice) AFTER LOCK 17
Mento balanceOf(alice) AFTER LOCK 40
veMento balanceOf(alice) AFTER WITHDRAW and before START 0
Mento balanceOf(alice) AFTER WITHDRAW and before START 100
veMento balanceOf(alice) AFTER WITHDRAW and AFTER START 17
Mento balanceOf(alice) AFTER WITHDRAW and AFTER START 100// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;
// solhint-disable func-name-mixedcase, contract-name-camelcase
import { Locking_Test } from "./Base.t.sol";
import { MockLocking } from "../../mocks/MockLocking.sol";
import "forge-std/console.sol";
contract Lock_Locking_Test is Locking_Test {
function test_init_shouldSetState() public {
assertEq(address(locking.token()), address(mentoToken));
assertEq(locking.startingPointWeek(), 0);
assertEq(locking.minCliffPeriod(), 0);
assertEq(locking.minSlopePeriod(), 0);
assertEq(locking.owner(), owner);
}
function test_POC_Voting_Power_Remains_Despite_Withdrawal() public {
mentoToken.mint(alice, 100);
//Alice locks her tokens.
vm.prank(alice);
locking.lock(alice, alice, 60, 30, 0);
console.log("veMento balanceOf(alice) AFTER LOCK",locking.balanceOf(alice));
console.log("Mento balanceOf(alice) AFTER LOCK",mentoToken.balanceOf(alice));
_incrementBlock(10);
//Owner stops the contract because of some issues
vm.prank(owner);
locking.stop();
//Alice decides to withdraw her tokens
vm.prank(alice);
locking.withdraw();
console.log("veMento balanceOf(alice) AFTER WITHDRAW and before START",locking.balanceOf(alice));
console.log("Mento balanceOf(alice) AFTER WITHDRAW and before START",mentoToken.balanceOf(alice));
_incrementBlock(10);
//Owner starts the contract again
vm.prank(owner);
locking.start();
//Alice withdraw all the tokens, but her voting power remained
console.log("veMento balanceOf(alice) AFTER WITHDRAW and AFTER START",locking.balanceOf(alice));
console.log("Mento balanceOf(alice) AFTER WITHDRAW and AFTER START",mentoToken.balanceOf(alice));
}
}MEDIUM – Full withdrawals do not decrease user voting power. However, it occurs only in special case when contract is stopped and then started.
In case of full withdrawal when the contract is stopped, adjust the voting power to zero.
Meet Composable Security
Get throughly tested by the creators of Smart Contract Security Verification Standard
Let us help
Get throughly tested by the creators of Smart Contract Security Verification Standard