veRWA - markus_ether's results

Incentivization Primitive for Real World Assets on Canto

General Information

Platform: Code4rena

Start Date: 07/08/2023

Pot Size: $36,500 USDC

Total HM: 11

Participants: 125

Period: 3 days

Judge: alcueca

Total Solo HM: 4

Id: 274

League: ETH

Canto

Findings Distribution

Researcher Performance

Rank: 31/125

Findings: 2

Award: $110.19

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
3 (High Risk)
partial-50
upgraded by judge
edited-by-warden
duplicate-288

Awards

105.9553 USDC - $105.96

External Links

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L92

Vulnerability details

Impact

Votes cast for newly added gauges are irretrievably lost. The Governance must call the change_gauge_weight method to allow any votes getting counted.

Proof of Concept

While the add_gauge function whitelists a new Gauge, it overlooks one crucial step. The core issue is that the function add_gauge does not set the time_weight of the new gauge, as the original vyper code does.

As a result, the get_weight function prematurely returns, failing to allocate any rewards. By calling change_gauge_weight, the government can finally set the time index enabling new voter to cast their votes.

Here is a POC showcasing the bug:

// prepare vm.startPrank(gov); gc.add_gauge(user1); vm.deal(user1, 100 ether); vm.stopPrank(); // issue vm.startPrank(user1); uint256 v = 10 ether; ve.createLock{value: v}(v); gc.vote_for_gauge_weights(user1, 100); assertEq(gc.get_gauge_weight(user1), 0); // note the user voted, but had no effect

Tools Used

Manual review

There are two potential fixes.

  1. Prevent users from voting on an uninitialized Vault:
function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
+   require(time_weight[_gauge_addr] > 0, "weight must be non-zero");
  1. Instantly initialize a weight of 0 for any new gauge
function add_gauge(address _gauge) external onlyGovernance {
    require(!isValidGauge[_gauge], "Gauge already exists");
    isValidGauge[_gauge] = true;
+   _change_gauge_weight(_gauge, 0);
    emit NewGauge(_gauge);
}

Assessed type

Governance

#0 - c4-pre-sort

2023-08-12T14:46:31Z

141345 marked the issue as duplicate of #169

#1 - c4-pre-sort

2023-08-12T14:52:05Z

141345 marked the issue as duplicate of #288

#2 - c4-judge

2023-08-25T10:27:11Z

alcueca marked the issue as duplicate of #401

#3 - c4-judge

2023-08-25T10:28:11Z

alcueca marked the issue as duplicate of #288

#4 - c4-judge

2023-08-25T10:35:17Z

alcueca marked the issue as partial-50

#5 - c4-judge

2023-08-25T22:45:02Z

alcueca changed the severity to 3 (High Risk)

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L211-L213 https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L276-L277

Vulnerability details

Impact

Governance has the ability to modify gauge weights under emergency circumstances. However, an attacker can counteract this adjustment by recasting their vote for the gauge, hereby restoring the gauge weight.

Proof of Concept

The governance sets the weight of a malicous gauge to zero by calling change_gauge_weight. Now the attacker can reclaim their voting power by setting their voting power to zero. Then they can vote for the vault again, sidestepping the previous government action.

Here's a demonstration highlighting this vulnerability:

function testVoteExploit() public {
    // prepare
    vm.startPrank(gov);
    gc.add_gauge(user1);
    gc.change_gauge_weight(user1, 100);
    vm.deal(user1, 100 ether);
    vm.stopPrank();

    // user votes for gauge
    vm.startPrank(user1);
    uint256 v = 10 ether;
    ve.createLock{value: v}(v);
    gc.vote_for_gauge_weights(user1, 100);
    assertTrue(gc.get_gauge_weight(user1) > 100);
    vm.stopPrank();

    // government sets weights to zero
    vm.startPrank(gov);
    gc.change_gauge_weight(user1, 0);
    assertEq(gc.get_gauge_weight(user1), 0);
    vm.stopPrank();

    // user claim back voting power
    vm.startPrank(user1);
    gc.vote_for_gauge_weights(user1, 0);
    assertEq(gc.vote_user_power(user1), 0);
    
    // user gives gauge weight back
    gc.vote_for_gauge_weights(user1, 100);
    assertTrue(gc.get_gauge_weight(user1) > 0);
    vm.stopPrank();
}

Tools Used

Manual review

To counter this exploit, it's essential to limit the number of times a user can vote in a given period. It's suggested that users are not permitted to vote more than once a week. As a point of comparison, Curve has limited users to vote once every 10 days.

A proposed code modification is:

function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external {
+    require(block.timestamp >= last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often");

In this context, the WEIGHT_VOTE_DELAY should be set to span beyond one week.

Assessed type

Governance

#0 - c4-pre-sort

2023-08-12T16:44:56Z

141345 marked the issue as duplicate of #331

#1 - c4-judge

2023-08-25T10:46:58Z

alcueca marked the issue as not a duplicate

#2 - alcueca

2023-08-25T10:47:35Z

@OpenCoreCH, I don't think this is exactly a duplicate, as it works on already initialized markets, that were disabled by governance.

#3 - OpenCoreCH

2023-08-25T16:54:31Z

Then they can vote for the vault again, sidestepping the previous government action.

Yes that is true if governance only set the weights to 0 (i.e., calls change_gauge_weight like in the PoC). However, if governance calls remove_gauge which sets the weights to zero and removes the gauge from the whitelist (i.e., the intended action when no votes should be possible anymore), this is not the case, as the gauge will not be whitelisted when the user tries to vote again.

#4 - c4-judge

2023-08-25T21:57:02Z

alcueca changed the severity to QA (Quality Assurance)

#5 - c4-judge

2023-08-25T21:57:08Z

alcueca marked the issue as grade-b

#6 - alcueca

2023-08-25T21:57:33Z

Valid QA for the sponsor to document

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