31281 - [SC - Low] Approved spender cannot withdraw or merge
Previous31280 - [SC - Critical] Malicious user can mint unlimited flux tokensNext31284 - [SC - Insight] cancel should allow to cancel the proposal of t...
Last updated
Was this helpful?
Was this helpful?
function _burn(uint256 _tokenId, uint256 _value) internal {
address owner = ownerOf(_tokenId);
// Update the total supply of deposited tokens
uint256 supplyBefore = supply;
uint256 supplyAfter = supplyBefore - _value;
supply = supplyAfter;
// Clear approval
//@audit-issue This will revert for approved users
approve(address(0), _tokenId);
// Checkpoint for gov
_moveTokenDelegates(delegates(owner), address(0), _tokenId);
// Remove token
_removeTokenFrom(owner, _tokenId);
emit Transfer(owner, address(0), _tokenId);
emit Supply(supplyBefore, supplyAfter);
} function approve(address _approved, uint256 _tokenId) public {
address owner = idToOwner[_tokenId];
// Throws if `_tokenId` is not a valid token
require(owner != address(0), "owner not found");
// Throws if `_approved` is the current owner
require(_approved != owner, "Approved is already owner");
// Check requirements
bool senderIsOwner = (owner == msg.sender);
bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
//@audit-issue Check will fail for the approved user who calls merge and withdraw
->> require(senderIsOwner || senderIsApprovedForAll, "sender is not owner or approved");
// Set the approval
idToApprovals[_tokenId] = _approved;
emit Approval(owner, _approved, _tokenId);
} function _burn(uint256 _tokenId, uint256 _value) internal {
address owner = ownerOf(_tokenId);
// Update the total supply of deposited tokens
uint256 supplyBefore = supply;
uint256 supplyAfter = supplyBefore - _value;
supply = supplyAfter;
// Clear approval
- approve(address(0), _tokenId);
+ idToApprovals[tokenId] = address(0);
// Checkpoint for gov
_moveTokenDelegates(delegates(owner), address(0), _tokenId);
// Remove token
_removeTokenFrom(owner, _tokenId);
emit Transfer(owner, address(0), _tokenId);
emit Supply(supplyBefore, supplyAfter);
}function testMergeTokensRevertEvenWhenCallerIsApproved() public {
uint256 tokenId1 = createVeAlcx(admin, TOKEN_1, MAXTIME, false);
uint256 tokenId2 = createVeAlcx(admin, TOKEN_100K, MAXTIME / 2, false);
// Approve both token to Beef
hevm.startPrank(admin);
veALCX.approve(beef, tokenId1);
veALCX.approve(beef, tokenId2);
hevm.stopPrank();
hevm.startPrank(beef);
uint256 lockEnd1 = veALCX.lockEnd(tokenId1);
assertEq(lockEnd1, ((block.timestamp + MAXTIME) / ONE_WEEK) * ONE_WEEK);
assertEq(veALCX.lockedAmount(tokenId1), TOKEN_1);
// Vote to trigger flux accrual
hevm.warp(newEpoch());
address[] memory pools = new address[](1);
pools[0] = alETHPool;
uint256[] memory weights = new uint256[](1);
weights[0] = 5000;
voter.vote(tokenId1, pools, weights, 0);
voter.vote(tokenId2, pools, weights, 0);
voter.distribute();
hevm.warp(newEpoch());
// Reset to allow merging of tokens
voter.reset(tokenId1);
voter.reset(tokenId2);
uint256 unclaimedFluxBefore1 = flux.getUnclaimedFlux(tokenId1);
uint256 unclaimedFluxBefore2 = flux.getUnclaimedFlux(tokenId2);
hevm.expectRevert(abi.encodePacked("sender is not owner or approved"));
veALCX.merge(tokenId1, tokenId2); // This will revert but it shouldn't
hevm.stopPrank();
}