Arcade.xyz - Juntao's results

The first of its kind Web3 platform to enable liquid lending markets for NFTs.

General Information

Platform: Code4rena

Start Date: 21/07/2023

Pot Size: $90,500 USDC

Total HM: 8

Participants: 60

Period: 7 days

Judge: 0xean

Total Solo HM: 2

Id: 264

League: ETH

Arcade.xyz

Findings Distribution

Researcher Performance

Rank: 15/60

Findings: 1

Award: $613.47

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: Juntao

Also found by: Jiamin, Juntao, UniversalCrypto, auditsea, circlelooper, crunch, lanrebayode77, vangrim, zaevlad

Labels

bug
2 (Med Risk)
disagree with severity
downgraded by judge
primary issue
selected for report
sponsor confirmed
edited-by-warden
M-08

Awards

613.4664 USDC - $613.47

External Links

Lines of code

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/ArcadeTreasury.sol#L189-L201

Vulnerability details

Impact

Approved gscApprove allowance to an address may not able to be decreased.

Proof of Concept

GSC_CORE_VOTING_ROLE calls gscApprove(...) function to approve tokens to be pulled from the treasury:

function gscApprove( address token, address spender, uint256 amount ) external onlyRole(GSC_CORE_VOTING_ROLE) nonReentrant { if (spender == address(0)) revert T_ZeroAddress("spender"); if (amount == 0) revert T_ZeroAmount(); // Will underflow if amount is greater than remaining allowance gscAllowance[token] -= amount; _approve(token, spender, amount, spendThresholds[token].small); }

Each approval will decrease the gscAllowance[token] by the approved allowance amount, this is problematic as GSC_CORE_VOTING_ROLE may not able to decrease the approved allowance.

Consider the following scenario:

  1. gscAllowance[token] is 100;
  2. GSC_CORE_VOTING_ROLE calls gscApprove(...) to give 60 allowance to a third party;
  3. gscAllowance[token] is 40 now;
  4. Later GSC_CORE_VOTING_ROLE finds the approved allowance is a bit too high and want to decrease the allowance to 50;
  5. gscApprove(...) is called again but the ransaction reverts due to underflow error (gscAllowance[token] is less than 50)

Please see the tests:

contract ArcadeTreasuryTest is Test { address timelock = address(1); address coreVoting = address(2); address gscCoreVoting = address(3); address other = address(4); ArcadeTreasury treasury; MockToken mockToken; function setUp() public { treasury = new ArcadeTreasury(timelock); vm.startPrank(timelock); treasury.grantRole(treasury.CORE_VOTING_ROLE(), coreVoting); treasury.grantRole(treasury.GSC_CORE_VOTING_ROLE(), gscCoreVoting); mockToken = new MockToken("Mock Token", "MT"); IArcadeTreasury.SpendThreshold memory threshold = IArcadeTreasury.SpendThreshold(100 ether, 200 ether, 300 ether); treasury.setThreshold(address(mockToken), threshold); vm.stopPrank(); } function testGscApprove() public { vm.warp(7 days); vm.prank(timelock); treasury.setGSCAllowance(address(mockToken), 100 ether); vm.startPrank(gscCoreVoting); treasury.gscApprove(address(mockToken), address(4), 60 ether); vm.expectRevert(stdError.arithmeticError); treasury.gscApprove(address(mockToken), address(4), 50 ether); vm.stopPrank(); } } contract MockToken is ERC20 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} function mint(address account, uint256 amount) public { _mint(account, amount); } }

Tools Used

Manual Review

When approve to a particular address, compare the new allowance with current allowance (IERC20.allowance(...)):

  1. If the current allowance is equal to new allowance, do nothing;
  2. If the current allowance is less than new allowance, gscAllowance[token] -= (new allowance - current allowance);
  3. IF the current allowance is larger than new allowance, gscAllowance[token] += (current allowance - new allowance).

Assessed type

Access Control

#0 - c4-pre-sort

2023-07-29T14:01:41Z

141345 marked the issue as primary issue

#1 - 141345

2023-07-29T14:01:51Z

Already approved allowance can not be decreased.

Might be expected behavior.

Because in this report, the focus is inner accounting gscAllowance, not the external erc20 allownance (as oppose to https://github.com/code-423n4/2023-07-arcade-findings/issues/85). Here the approved allowance seems different from the "ERC20 approved allowance". Here it can be seen as account payable, the amount is spent, but the spender has not taken yet.

Or, if the designed behavior is to also provide way to revoke the allowance, this inner accounting needs some improvement.

To my understanding, the issue roots in the unsync of internal and external allowance accounting.

https://github.com/code-423n4/2023-07-arcade-findings/issues/85 also talks about allownance issue, focus more on erc20 approved allowance override, which seems better pointing out the potential impact of with this mechanism. Some way to revoke the allowance, if the allowance is overwritten.

#2 - 141345

2023-08-01T07:09:12Z

group several gscAllowance issue, with some different impact, but roots from the gscAllowance accounting.

#3 - c4-sponsor

2023-08-02T19:52:28Z

PowVT marked the issue as sponsor confirmed

#4 - c4-sponsor

2023-08-02T19:52:37Z

PowVT marked the issue as disagree with severity

#5 - 0xean

2023-08-10T14:42:37Z

Based on current understanding, I think M is the correct severity here.

#6 - c4-judge

2023-08-10T14:42:51Z

0xean changed the severity to 2 (Med Risk)

#7 - c4-judge

2023-08-11T01:40:23Z

0xean marked issue #288 as primary and marked this issue as a duplicate of 288

#8 - c4-judge

2023-08-11T01:40:32Z

0xean marked the issue as selected for report

Findings Information

🌟 Selected for report: Juntao

Also found by: Jiamin, Juntao, UniversalCrypto, auditsea, circlelooper, crunch, lanrebayode77, vangrim, zaevlad

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
edited-by-warden
duplicate-58

Awards

613.4664 USDC - $613.47

External Links

Lines of code

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/ArcadeTreasury.sol#L303-L323

Vulnerability details

Impact

Tokens may be spent without CORE_VOTING_ROLE approval.

Proof of Concept

ArcadeTreasury transfers or approves them based on invocations from governance. GSC_CORE_VOTING_ROLE may be authorized to spend or approve smaller amounts (gscAllowance) from their own voting contract, all other amounts must be authorized by full community votes.

ADMIN_ROLE can set the gscAllowance for a token by calling setGSCAllowance(...) function:

function setGSCAllowance(address token, uint256 newAllowance) external onlyRole(ADMIN_ROLE) { if (token == address(0)) revert T_ZeroAddress("token"); if (newAllowance == 0) revert T_ZeroAmount(); // enforce cool down period if (uint48(block.timestamp) < lastAllowanceSet[token] + SET_ALLOWANCE_COOL_DOWN) { revert T_CoolDownPeriod(block.timestamp, lastAllowanceSet[token] + SET_ALLOWANCE_COOL_DOWN); } uint256 spendLimit = spendThresholds[token].small; // new limit cannot be more than the small threshold if (newAllowance > spendLimit) { revert T_InvalidAllowance(newAllowance, spendLimit); } // update allowance state lastAllowanceSet[token] = uint48(block.timestamp); gscAllowance[token] = newAllowance; emit GSCAllowanceUpdated(token, newAllowance); }

However, part of gscAllowance may be approved to third parties and the allowance is not reset, thus tokens may be spent without CORE_VOTING_ROLE approval.

Consider the following scenario:

  1. GSC_CORE_VOTING_ROLE approves 100 ether to a third party;
  2. ADMIN_ROLE set the gscAllowance to 80 ether;
  3. Third party pulls 100 ether from treasury, results in 20 ether being spent without CORE_VOTING_ROLE approval.

Please see the tests:

contract ArcadeTreasuryTest is Test { address timelock = address(1); address coreVoting = address(2); address gscCoreVoting = address(3); address other = address(4); ArcadeTreasury treasury; MockToken mockToken; function setUp() public { treasury = new ArcadeTreasury(timelock); vm.startPrank(timelock); treasury.grantRole(treasury.CORE_VOTING_ROLE(), coreVoting); treasury.grantRole(treasury.GSC_CORE_VOTING_ROLE(), gscCoreVoting); mockToken = new MockToken("Mock Token", "MT"); IArcadeTreasury.SpendThreshold memory threshold = IArcadeTreasury.SpendThreshold(100 ether, 200 ether, 300 ether); treasury.setThreshold(address(mockToken), threshold); vm.stopPrank(); uint256 gscAllowance = 100 ether; mockToken.mint(address(treasury), 300 ether); uint256 treasuryBalanceBefore = mockToken.balanceOf(address(treasury)); assertEq(treasuryBalanceBefore, 300 ether); vm.warp(7 days); vm.prank(timelock); treasury.setGSCAllowance(address(mockToken), gscAllowance); } function testDecreaseGscAllowance() public { vm.prank(gscCoreVoting); treasury.gscApprove(address(mockToken), other, gscAllowance); vm.warp(14 days); uint256 gscAllowanceNew = 80 ether; vm.prank(timelock); treasury.setGSCAllowance(address(mockToken), gscAllowanceNew); vm.prank(other); mockToken.transferFrom(address(treasury), other, gscAllowance); uint256 treasuryBalanceAfter = mockToken.balanceOf(address(treasury)); assertTrue(treasuryBalanceAfter < treasuryBalanceBefore - gscAllowanceNew); assertEq(mockToken.balanceOf(address(treasury)), 200 ether); } } contract MockToken is ERC20 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} function mint(address account, uint256 amount) public { _mint(account, amount); } }

Tools Used

Manual Review

Allowance to third parties should be revoked when set the gscAllowance.

Assessed type

Access Control

#0 - 141345

2023-07-29T15:05:37Z

seems wrong understanding of the mechanism, new allowance are separate from previous allowance.

Already allocated allowance should not be affected.

#1 - c4-pre-sort

2023-07-29T15:07:03Z

141345 marked the issue as duplicate of #480

#2 - 141345

2023-07-29T15:07:31Z

#3 - c4-pre-sort

2023-07-29T16:42:00Z

141345 marked the issue as not a duplicate

#4 - c4-pre-sort

2023-07-29T16:42:04Z

141345 marked the issue as primary issue

#5 - c4-pre-sort

2023-08-01T07:07:23Z

141345 marked the issue as duplicate of #58

#6 - c4-judge

2023-08-10T14:42:48Z

0xean changed the severity to 2 (Med Risk)

#7 - c4-judge

2023-08-11T01:40:46Z

0xean marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter