[Audit Report] Olympus

0xDaveยท2022๋…„ 12์›” 6์ผ
0

Ethereum

๋ชฉ๋ก ๋ณด๊ธฐ
71/112
post-thumbnail

Scope


FilesSLOCCoverage
Kernel Contracts (2)
src/Kernel.sol ฮฃ262100.00%
src/utils/KernelUtils.sol ๐Ÿ–ฅ ฮฃ46-
Module Contracts (6)
src/modules/TRSRY.sol ฮฃ90100.00%
src/modules/MINTR.sol21100.00%
src/modules/RANGE.sol224100.00%
src/modules/PRICE.sol ฮฃ145100.00%
src/modules/VOTES.sol ฮฃ38100.00%
src/modules/INSTR.sol ฮฃ44100.00%
Policy Contracts (7)
src/policies/TreasuryCustodian.sol ฮฃ56100.00%
src/policies/Operator.sol487100.00%
src/policies/BondCallback.sol ฮฃ122100.00%
src/policies/Heart.sol82100.00%
src/policies/PriceConfig.sol41100.00%
src/policies/Governance.sol ฮฃ183100.00%
src/policies/VoterRegistration.sol28100.00%
Interfaces (3)
src/interfaces/IBondCallback.sol11-
src/policies/interfaces/IHeart.sol10-
src/policies/interfaces/IOperator.sol54-
Total (18 files):1944100.00%

๋‚ด๊ฐ€ ์ƒ๊ฐํ–ˆ๋˜ ๊ฒฐํ•จ๋“ค


length == 0 ์œ„์น˜ ์ˆ˜์ •

//INSTR.sol

    function store(Instruction[] calldata instructions_) external permissioned returns (uint256) {
        uint256 length = instructions_.length;
        uint256 instructionsId = ++totalInstructions;

        Instruction[] storage instructions = storedInstructions[instructionsId];

        if (length == 0) revert INSTR_InstructionsCannotBeEmpty();

        for (uint256 i; i < length; ) {
            Instruction calldata instruction = instructions_[i];
            ensureContract(instruction.target);

            // If the instruction deals with a module, make sure the module has a valid keycode (UPPERCASE A-Z ONLY)
            if (
                instruction.action == Actions.InstallModule ||
                instruction.action == Actions.UpgradeModule
            ) {
                Module module = Module(instruction.target);
                ensureValidKeycode(module.KEYCODE());
            } else if (instruction.action == Actions.ChangeExecutor && i != length - 1) {
                // Throw an error if ChangeExecutor exists and is not the last Action in the instruction list.
                // This exists because if ChangeExecutor is not the last item in the list of instructions,
                // the Kernel will not recognize any of the following instructions as valid, since the policy
                // executing the list of instructions no longer has permissions in the Kernel. To avoid this issue
                // and prevent invalid proposals from being saved, we perform this check.
                revert INSTR_InvalidChangeExecutorAction();
            }

            instructions.push(instructions_[i]);
            unchecked {
                ++i;
            }
        }

        emit InstructionsStored(instructionsId);

        return instructionsId;
    }

instructions_์„ ๋ฐ›์•„์„œ ๊ธฐ์กด instructions์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ๊ทธ๋Ÿฐ๋ฐ instructions_์„ ๋ฐ›์„ ๋•Œ ๋“ค์–ด์˜จ ๊ฐ’์˜ length๊ฐ€ 0์ธ์ง€ ์ฒดํฌํ•˜๋Š” ๋ถ€๋ถ„์ด ์ฒ˜์Œ์ด ์•„๋‹ˆ๋ผ ์ค‘๊ฐ„์— ์žˆ๋‹ค. revert๊ฐ€ ๋‚˜์„œ ํฌ๊ฒŒ ์ƒ๊ด€์—†๊ฒ ์ง€๋งŒ, ์ดˆ๋ฐ˜์— ํ™•์ธํ•˜๋Š” ๊ฒŒ ์ข‹์ง€ ์•Š์„๊นŒ ์‹ถ์–ด์„œ ๊ฐ€์ ธ์™€๋ดค๋‹ค.


observationFrequency_์ด 0์ด๋ฉด ํ˜„์žฌ ๊ฐ€๊ฒฉ์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Œ

//PRICE.sol

    constructor(
        Kernel kernel_,
        AggregatorV2V3Interface ohmEthPriceFeed_,
        AggregatorV2V3Interface reserveEthPriceFeed_,
        uint48 observationFrequency_,
        uint48 movingAverageDuration_
    ) Module(kernel_) {
        /// @dev Moving Average Duration should be divisible by Observation Frequency to get a whole number of observations
        if (movingAverageDuration_ == 0 || movingAverageDuration_ % observationFrequency_ != 0)
            revert Price_InvalidParams();
	//..
    }


    function getCurrentPrice() public view returns (uint256) {
        if (!initialized) revert Price_NotInitialized();

        // Get prices from feeds
        uint256 ohmEthPrice;
        uint256 reserveEthPrice;
        {
            (, int256 ohmEthPriceInt, , uint256 updatedAt, ) = _ohmEthPriceFeed.latestRoundData();
            // Use a multiple of observation frequency to determine what is too old to use.
            // Price feeds will not provide an updated answer if the data doesn't change much.
            // This would be similar to if the feed just stopped updating; therefore, we need a cutoff.
            if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))
                revert Price_BadFeed(address(_ohmEthPriceFeed));
            ohmEthPrice = uint256(ohmEthPriceInt);

            int256 reserveEthPriceInt;
            (, reserveEthPriceInt, , updatedAt, ) = _reserveEthPriceFeed.latestRoundData();
            if (updatedAt < block.timestamp - uint256(observationFrequency))
                revert Price_BadFeed(address(_reserveEthPriceFeed));
            reserveEthPrice = uint256(reserveEthPriceInt);
        }

        // Convert to OHM/RESERVE price
        uint256 currentPrice = (ohmEthPrice * _scaleFactor) / reserveEthPrice;

        return currentPrice;
    }

์ฒ˜์Œ constructor์—์„œ movingAverageDuration_๊ณผ observationFrequency_ ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค. ๊ทธ๋Ÿฐ๋ฐ if๋ฌธ์„ ํ†ตํ•ด movingAverageDuration_ ๊ฐ’๋งŒ 0์ธ์ง€ ํ™•์ธํ•œ๋‹ค. constructor์—์„œ๋Š” ์ƒ๊ด€์ด ์—†์ง€๋งŒ ์ดํ›„ getCurrentPrice()์—์„œ ์•„๋ž˜ if๋ฌธ ๋•Œ๋ฌธ์— ๊ณ„์† revert๊ฐ€ ๋‚  ๊ฒƒ์ด๋‹ค. updatedAt์€ ๋ฌด์กฐ๊ฑด ํ˜„์žฌ block.timestamp ๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ๋ฐ–์— ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))
                revert Price_BadFeed(address(_ohmEthPriceFeed));

๋”ฐ๋ผ์„œ constructor ๋ถ€๋ถ„์— observationFrequency_ ๊ฐ’๋„ 0์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ถ€๋ถ„์ด ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•œ๋‹ค.


์ƒˆ๋กœ ๋ฐฐ์šด ๊ฒƒ

1. type C is V

//Kernel.sol

type Keycode is bytes5;
type Role is bytes32;



//KernelUtils.sol

function toKeycode(bytes5 keycode_) pure returns (Keycode) {
    return Keycode.wrap(keycode_);
}

// solhint-disable-next-line func-visibility
function fromKeycode(Keycode keycode_) pure returns (bytes5) {
    return Keycode.unwrap(keycode_);
}

KernelUtils์— .wrap()๊ณผ .unwrap()์ด ์žˆ๋Š”๋ฐ Kernel์ด๋‚˜ ๋‹ค๋ฅธ ์ปจํŠธ๋ž™ํŠธ๋ฅผ ์•„๋ฌด๋ฆฌ ์ฐพ์•„ ๋ด๋„ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋Š” ๋ถ€๋ถ„์€ ์—†์—ˆ๋‹ค. bytes ํƒ€์ž…์— ์†ํ•ด์žˆ๋Š” ํƒ€์ž…์ธ์ง€ ์ฐพ์•„๋ดค์ง€๋งŒ ๋ณ„ ๋‚ด์šฉ์ด ์—†์—ˆ๋‹ค. ๊ณ„์† ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋’ค์ง€๋‹ค๊ฐ€ ์•„๋ž˜ ๋‚ด์šฉ์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ํƒ€์ž…(C)๊ณผ ๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” ํƒ€์ž…(V)์„ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋‹ค. type C is V์„ ์˜ˆ๋กœ ๋“ค๋ฉด, C.wrap(a)๋ฅผ ํ–ˆ์„ ๋•Œ ์›๋ž˜ Vํƒ€์ž…์ธ a๊ฐ€ Cํƒ€์ž…์œผ๋กœ ๋ณ€ํ•˜๊ณ , C.unwrap(a)๋ฅผ ํ•˜๋ฉด ๋‹ค์‹œ Vํƒ€์ž…์œผ๋กœ ๋Œ์•„์˜จ๋‹ค.


2. EXTCODESIZE Checks

//KernelUtils.sol

function ensureContract(address target_) view {
    uint256 size;
    assembly {
        size := extcodesize(target_)
    }
    if (size == 0) revert TargetNotAContract(target_);
}

target์ด ์ปจํŠธ๋ž™ํŠธ์ธ์ง€ ์•„๋‹Œ์ง€ ํ™•์ธํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์™œ extcodesize๋ฅผ ํ™•์ธํ•ด์„œ 0์ด ์•„๋‹ˆ์–ด์•ผ ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•ด์„œ ์ฐพ์•„๋ดค๋‹ค. Consensys์™€ ethereum stackexchange ๋‹ต๋ณ€์— ๋”ฐ๋ฅด๋ฉด extcodesize๋Š” ํ•ด๋‹น ๊ณ„์ •์˜ ์ฝ”๋“œ ๊ธธ์ด๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค. ์ฆ‰, ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•œ ์ปจํŠธ๋ž™ํŠธ๋ผ๋ฉด ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— 0๋ณด๋‹ค ํฐ ๊ฐ’์„ ๋ฆฌํ„ดํ•  ๊ฒƒ์ด๊ณ  ๋ฉ”ํƒ€๋งˆ์Šคํฌ ๊ฐ™์€ EOA๋ผ๋ฉด ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— 0์„ ๋ฆฌํ„ดํ•  ๊ฒƒ์ด๋‹ค.

ํ•œ ๊ฐ€์ง€ ์ฃผ์˜ํ•  ์ ์€ ์ปจํŠธ๋ž™ํŠธ์˜ constructor์—์„œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ์ปจํŠธ๋ž™ํŠธ๊ฐ€ ๋ฐฐํฌ ๋  ์‹œ์ ์˜ extcodesize๋Š” 0์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ ์„ ์ด์šฉํ•ด์„œ ๊ณต๊ฒฉ๋‹นํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๋ณด์•ˆ์— ์ทจ์•ฝํ•œ ๋ถ€๋ถ„์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.


3. unchecked

    instructions.push(instructions_[i]);
    unchecked {
        ++i;
    }

๊ฐ€๋” ํ•จ์ˆ˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— unchecked๊ฐ€ ์žˆ๋‹ค. ๋ฌด์Šจ ์šฉ๋„์ธ์ง€ ์ฐพ์•„๋ดค๋‹ค. ์›๋ž˜ underflow/overflow๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์†”๋ฆฌ๋””ํ‹ฐ ์ž์ฒด์—์„œ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋Š”๋ฐ ์ด ๋•Œ ๊ฐ€์Šค๊ฐ€ ์†Œ๋ชจ๋œ๋‹ค. underflow/overflow๊ฐ€ ์ผ์–ด๋‚  ์ผ์ด ์—†๋‹ค๋ฉด unchecked๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ€์Šค๋ฅผ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.


๋ ˆํฌํŠธ


[H-01] IN GOVERNANCE.SOL, IT MIGHT BE IMPOSSIBLE TO ACTIVATE A NEW PROPOSAL FOREVER AFTER FAILED TO EXECUTE THE PREVIOUS ACTIVE PROPOSAL.

//GOVERNANCE.sol

function activateProposal(uint256 proposalId_) external {
        ProposalMetadata memory proposal = getProposalMetadata[proposalId_];

        if (msg.sender != proposal.submitter) {
            revert NotAuthorizedToActivateProposal();
        }

        if (block.timestamp > proposal.submissionTimestamp + ACTIVATION_DEADLINE) {
            revert SubmittedProposalHasExpired();
        }

        if (
            (totalEndorsementsForProposal[proposalId_] * 100) <
            VOTES.totalSupply() * ENDORSEMENT_THRESHOLD
        ) {
            revert NotEnoughEndorsementsToActivateProposal();
        }

        if (proposalHasBeenActivated[proposalId_] == true) {
            revert ProposalAlreadyActivated();
        }

        if (block.timestamp < activeProposal.activationTimestamp + GRACE_PERIOD) {
            revert ActiveProposalNotExpired();
        }

        activeProposal = ActivatedProposal(proposalId_, block.timestamp);

        proposalHasBeenActivated[proposalId_] = true;

        emit ProposalActivated(proposalId_, block.timestamp);
    }

proposal์„ active์ƒํƒœ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„  ์—ฌ๋Ÿฌ ์กฐ๊ฑด๋“ค์ด ํ•„์š”ํ•˜๋‹ค. ๊ทธ ์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

		if (
            (totalEndorsementsForProposal[proposalId_] * 100) <
            VOTES.totalSupply() * ENDORSEMENT_THRESHOLD
        ) {
            revert NotEnoughEndorsementsToActivateProposal();
        }

์†”์งํžˆ ๋งํ•˜๋ฉด ๋‚˜๋Š” ํ•ด๋‹น ์กฐ๊ฑด๋“ค์„ ๋ณผ ๋•Œ ๊ทธ ์กฐ๊ฑด๋“ค์ด ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. VOTES.totalSupply() * ENDORSEMENT_THRESHOLD๊ฐ€ ์™œ ์žˆ๋Š”์ง€ ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€ ๋„˜๊ฒผ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋‹ค์‹œ ์ƒ๊ฐํ•ด๋ณด๋‹ˆ ์œ„ ์กฐ๊ฑด๋ฌธ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

		if (
            (totalEndorsementsForProposal[proposalId_] / VOTES.totalSupply()) <
             ENDORSEMENT_THRESHOLD / 100
        ) {
            revert NotEnoughEndorsementsToActivateProposal();
        }

์ฆ‰, ์ „์ฒด ํˆฌํ‘œ ์ˆ˜ ์ค‘์—์„œ ์‚ฌ๋žŒ๋“ค์ด ํ•ด๋‹น ํ”„๋กœํฌ์ ˆ์— ํˆฌํ‘œํ•œ ๋น„์œจ์ด 20%๋ฅผ ๋„˜์ง€ ๋ชปํ•˜๋ฉด active ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋ง์€ ์‚ฌ๋žŒ๋“ค์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํˆฌํ‘œ์ˆ˜๊ฐ€ ์ „์ฒด์˜ 20ํผ์„ผํŠธ๊ฐ€ ์ตœ์†Œํ•œ ์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ๋ง๊ณผ ๊ฐ™๋‹ค.

๋งŒ์•ฝ ํ•˜๋‚˜์˜ ํ”„๋กœํฌ์ ˆ์— 81ํผ์„ผํŠธ์˜ ํˆฌํ‘œ๊ฐ€ ๋ชฐ๋ฆฌ๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

๋ฌธ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ๋ฐœ์ƒํ•œ๋‹ค. ํ•˜๋‚˜์˜ ํ”„๋กœํฌ์ ˆ์— ์ฐฌ์„ฑ 43%, ๋ฐ˜๋Œ€ 37%๋กœ ์ „์ฒด ํˆฌํ‘œ์˜ 81%๊ฐ€ ๋ชฐ๋ ธ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ํ˜„์žฌ ์œ ์ €๋“ค์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํˆฌํ‘œ์ˆ˜๋Š” 19%๋กœ ENDORSEMENT_THRESHOLD / 100๋ฅผ ๋„˜์ง€ ๋ชป ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ activate๋œ ํ”„๋กœํฌ์ ˆ์ด ๊ฐ€๊ฒฐ/๋ถ€๊ฒฐ๋˜๊ธฐ ์ „๊นŒ์ง€ ์ƒˆ๋กœ์šด ํ”„๋กœํฌ์ ˆ์€ active ๋  ์ˆ˜ ์—†๋‹ค. ๋˜ํ•œ executeProposal()์—๋„ ํ•ด๋‹น ์กฐ๊ฑด๋ฌธ์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด์„œ executeํ•  ๋•Œ์—๋„ 20%๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๊ทธ๋ž˜์„œ ํ”„๋กœํฌ์ ˆ์ด active ์ƒํƒœ์— ๊ณ„์† ๋จธ๋ฌผ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

warden์ด ์ œ์•ˆํ•œ ๋Œ€์•ˆ์€ EXECUTION_EXPIRE๋ฅผ constant๋กœ ํ•ด์„œ 2์ฃผ๋กœ ์žก๋Š” ๊ฒƒ์ด๋‹ค. 2์ฃผ๊ฐ€ ์ง€๋‚˜๋ฉด ํˆฌํ‘œ์ž๋“ค์ด ์ž์‹ ์˜ ํˆฌํ‘œ๋ฅผ reclaimํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์„œ ๋‹ค์‹œ ํˆฌํ‘œ๊ถŒ์„ ๊ฐ€์ ธ๊ฐ€๋„๋ก ํ•œ๋‹ค. ๊ฒฐ๊ตญ ํˆฌํ‘œ๊ถŒ์ด ๋ฌถ์ด๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•จ์œผ๋กœ์จ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.


[H-02] ANYONE CAN PASS ANY PROPOSAL ALONE BEFORE FIRST VOTES ARE MINTED

//GOVERNANCE.sol

    function submitProposal(
        Instruction[] calldata instructions_,
        bytes32 title_,
        string memory proposalURI_
    ) external {
        if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)
            revert NotEnoughVotesToPropose();
		//..
        emit ProposalSubmitted(proposalId);
    }

    function activateProposal(uint256 proposalId_) external {
		//...
        if (
            (totalEndorsementsForProposal[proposalId_] * 100) <
            VOTES.totalSupply() * ENDORSEMENT_THRESHOLD
        ) {
            revert NotEnoughEndorsementsToActivateProposal();
        }
        //..
    }

    function executeProposal() external {
        uint256 netVotes = yesVotesForProposal[activeProposal.proposalId] -
            noVotesForProposal[activeProposal.proposalId];
        if (netVotes * 100 < VOTES.totalSupply() * EXECUTION_THRESHOLD) {
            revert NotEnoughVotesToExecute();
        }

        if (block.timestamp < activeProposal.activationTimestamp + EXECUTION_TIMELOCK) {
            revert ExecutionTimelockStillActive();
        }
		//..
    }

ํ”„๋กœํฌ์ ˆ์ด ํ†ต๊ณผ๋˜๊ธฐ๊นŒ์ง€์˜ ์ „์ฒด ์ˆœ์„œ๋Š” submit - activate - execute์ด๋‹ค. ๊ฐ ๋‹จ๊ณ„๋ณ„๋กœ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด์ด ์žˆ๋‹ค. ์ด ๋•Œ ๊ณตํ†ต์ ์œผ๋กœ VOTES.totalSupply()์˜ ๊ฐ€์ค‘์น˜๋ณด๋‹ค ํŠน์ • ๊ฐ’์ด ์ž‘์œผ๋ฉด revert๊ฐ€ ๋‚˜๊ฒŒ ๋˜์–ด์žˆ๋‹ค.

๋งŒ์•ฝ VOTES.totalSupply()๊ฐ€ 0์ด๋ผ๋ฉด?

VOTES๊ฐ€ ๋ฏผํŒ…๋˜๊ธฐ ์ „์ด๋ผ๋ฉด VOTES.totalSupply()๋Š” 0์ด๋‹ค. ์ด ๋•Œ ๊ฐ ํ•จ์ˆ˜์˜ ์กฐ๊ฑด์„ ๋‹ค์‹œ ๋ณด๋ฉด, ๋†€๋ž๊ฒŒ๋„ ๋ชจ๋‘ false๊ฐ€ ๋˜๋ฉด์„œ revert๋ฅผ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ํˆฌํ‘œ๊ถŒ ์—†์ด ํ”„๋กœํฌ์ ˆ์„ submit - activate - execute ๋ชจ๋‘ ์„ฑ๊ณต์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์–˜๊ธฐ๋‹ค. ์ด๋Š” ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ž๊ธˆ ํƒˆ์ทจ๋กœ ์ด์–ด์งˆ ๊ฒƒ์ด๊ณ  ํ”„๋กœ์ ํŠธ์— ์†ํ•ด๋ฅผ ๋ผ์น  ๊ฒƒ์ด๋‹ค.

๋งŒ์•ฝ ์ฝ˜ํ…Œ์ŠคํŠธ ์ค‘์— ์œ„์™€ ๊ฐ™์€ ์ƒ๊ฐ์„ ๋– ์˜ฌ๋ ธ๋‹ค๋ฉด ๋ฐ”๋กœ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜๋Š” warden์ด ์ด์Šˆ์™€ ํ•จ๊ป˜ ์ œ์ถœํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋‹ค. VOTES๊ฐ€ ๋ฏผํŒ…๋˜๊ธฐ ์ „์— Admin๊ณผ Executor ๊ถŒํ•œ์„ ๋„˜๊ธฐ๋Š” ํ”„๋กœํฌ์ ˆ์„ ์˜ฌ๋ฆฌ๊ณ  execute ํ•œ ๋‹ค์Œ ์„ฑ๊ณต์ ์œผ๋กœ ๊ถŒํ•œ ํƒˆ์ทจํ•œ ๊ฒƒ ๊นŒ์ง€ ๋‚˜์™€์žˆ๋‹ค.

    function test_AttackerPassesProposalBeforeMinting() public {
        //์ž์ฒด์ ์œผ๋กœ ๋งŒ๋“  user. vm.deal()์„ ํ†ตํ•ด 100์ด๋” ๋ณด์œ 
        address[] memory users = userCreator.create(1);
        address attacker = users[0];
      
        //atacker ์—ฐ๊ฒฐ
        vm.prank(attacker);
      
        //์ปจํŠธ๋ž™ํŠธ์™€ ํ”„๋กœํฌ์ ˆ ์˜ฌ๋ฆด instruction ์ƒ์„ฑ
        MockMalicious attackerControlledContract = new MockMalicious();
        Instruction[] memory instructions_ = new Instruction[](2);
        
        //struct Instruction(action,target)
        instructions_[0] = Instruction(Actions.ChangeAdmin, address(attackerControlledContract));
        instructions_[1] = Instruction(Actions.ChangeExecutor, address(attackerControlledContract));
      
        vm.prank(attacker);
        governance.submitProposal(instructions_, "proposalName", "This is the proposal URI")
        governance.endorseProposal(1);
        
        vm.prank(attacker);
        governance.activateProposal(1);
        
        vm.warp(block.timestamp + 3 days + 1);
        
        governance.executeProposal();
       
        //๊ถŒํ•œ ํƒˆ์ทจ ์„ฑ๊ณต
        assert(kernel.executor()==address(attackerControlledContract));
        assert(kernel.admin()==address(attackerControlledContract));
    }

์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ ํ•จ์ˆ˜์— VOTES.totalSupply() ์˜ ์ตœ์†Ÿ๊ฐ’์„ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.


[H-03] TRSRY: FRONT-RUNNABLE SETAPPROVALFOR

//TRSRY.sol

    function setApprovalFor(
        address withdrawer_,
        ERC20 token_,
        uint256 amount_
    ) external permissioned {
        withdrawApproval[withdrawer_][token_] = amount_;

        emit ApprovedForWithdrawal(withdrawer_, token_, amount_);
    }

//TreasuryCustodian.sol

    function grantApproval(
        address for_,
        ERC20 token_,
        uint256 amount_
    ) external onlyRole("custodian") {
        TRSRY.setApprovalFor(for_, token_, amount_);
    }

๊ธฐ์กด์— Alice๋Š” 100๋งŒํผ ์ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ custodian์ด Alice์˜ ์ธ์ถœ ํ•œ๋„๋ฅผ 50์œผ๋กœ ์ค„์ด๋Š” ํŠธ๋žœ์žญ์…˜์„ ๋ณด๋‚ธ๋‹ค๊ณ  ๊ฐ€์ • ํ–ˆ์„ ๋•Œ, mempool์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•˜๊ณ  ์žˆ๋˜ Alice๋Š” ๊ฐ€์Šค๋น„๋ฅผ ๋” ์ค˜์„œ 1) ๋จผ์ € 100๋งŒํผ ์ธ์ถœํ•˜๊ณ  2) ํ•œ๋„๊ฐ€ 50์œผ๋กœ ์ค„์–ด๋“ค๋ฉด 3) ๋‹ค์‹œ 50์„ ์ƒˆ๋กœ ์ธ์ถœํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„  approve ํ•˜๋Š” ๊ธˆ์•ก์„ ์ƒˆ๋กœ ํ• ๋‹นํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๊ธฐ์กด ์ธ์ถœ๊ฐ€๋Šฅ ๊ธˆ์•ก์—์„œ ์ฐจ๊ฐํ•˜๋Š” ํ˜•์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค.

withdrawApproval ๊ด€๋ จ ํ•จ์ˆ˜๊ฐ€ ๋‚˜์˜ค๋ฉด ํ•ญ์ƒ mempool์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•˜๊ณ  ์žˆ๋Š” Alice๋ฅผ ๊ธฐ์–ตํ•˜์ž.


[M-05] PROPOSALS OVERWRITE

//Governance.sol

    function submitProposal(
        Instruction[] calldata instructions_,
        bytes32 title_,
        string memory proposalURI_
    ) external {
        if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)
            revert NotEnoughVotesToPropose();

        uint256 proposalId = INSTR.store(instructions_);
        getProposalMetadata[proposalId] = ProposalMetadata(
            title_,
            msg.sender,
            block.timestamp,
            proposalURI_
        );

        emit ProposalSubmitted(proposalId);
    }

INSTR.store(instructions_)์„ ํ†ตํ•ด getProposalMetadata[proposalId]์— ์ƒˆ๋กœ์šด ํ”„๋กœํฌ์ ˆ์„ ํ• ๋‹นํ•  ๋•Œ ์ด๋ฏธ ์žˆ๋Š” ํ”„๋กœํฌ์ ˆ์ธ์ง€ ํ™•์ธํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ์ด์ „ ํ”„๋กœํฌ์ ˆ์ด ๋ˆ„๋ฝ๋˜๊ณ  ์ƒˆ๋กœ์šด ํ”„๋กœํฌ์ ˆ๋กœ ๋Œ€์ฒด๋  ์ˆ˜ ์žˆ๋‹ค. ์ค‘๋ณต ์ฒดํฌํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•œ๋‹ค.


[M-06] AFTER ENDORSING A PROPOSAL, USER CAN TRANSFER VOTES TO ANOTHER USER FOR ENDORSING THE SAME PROPOSAL AGAIN

/// @notice Votes module is the ERC20 token that represents voting power in the network.
/// @dev    This is currently a substitute module that stubs gOHM.
contract OlympusVotes is Module, ERC20 {
  //..

์ฝ”๋ฉ˜ํŠธ์— OlympusVotes๋Š” gOHM์˜ ๋Œ€์ฒด์ œ๋กœ ์‚ฌ์šฉ๋œ๋‹ค๊ณ  ๋‚˜์™€์žˆ๋‹ค. that stubs gOHM์ด๋ผ๋Š” ๋ง์€ ํ˜„์žฌ ํ…Œ์ŠคํŠธ ๋‹จ๊ณ„์—์„œ ์ง์ ‘์ ์œผ๋กœ gOHM์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋‹ˆ gOHM์„ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ž„์‹œ์ ์œผ๋กœ OlympusVotes๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๊ณ , ์ถ”ํ›„์— gOHM์œผ๋กœ ๋ฐ”๊พผ๋‹ค๋Š” ๋œป์ด๋‹ค.

As an additional safety mechanism, when votes are cast, the tokens used to vote on the proposal are locked in the contract until the proposal is no longer active. This exists to deter malicious behavior by ensuring users cannot transfer their voting tokens until after the proposal has been resolved. Once a proposal is no longer active, either by being executed or replaced by a new proposal, users may redeem their votes back from the contract.

ํ”„๋กœ์ ํŠธ ์ธก์˜ ๊ณต์‹๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด Votes ํ† ํฐ์€ ํˆฌํ‘œ ์งํ›„ ์ด๋™ํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ˜„์žฌ ์ด๋”์Šค์บ”์—์„œ gOHM์˜ ์ปจํŠธ๋ž™ํŠธ๋ฅผ ๋ณด๋ฉด transfer์™€ transferFrom ํ•จ์ˆ˜๊ฐ€ ์กด์žฌํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ถ”ํ›„์— gOHM์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ์„ ๋•Œ ํ•ด์ปค๊ฐ€ ํˆฌํ‘œ ์งํ›„ ๋‹ค๋ฅธ ์ง€๊ฐ‘์œผ๋กœ gOHM์„ ์ „์†กํ•ด์„œ ์ค‘๋ณต ํˆฌํ‘œํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ฅผ ์˜ˆ๋ฐฉํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์œ ์ €๋“ค์ด ํˆฌํ‘œํ–ˆ์„ ๋•Œ ํ•ด๋‹น ํ† ํฐ์„ ๊ฑฐ๋ฒ„๋„Œ์Šค์— ์ข…์†๋˜๋„๋ก ํ•ด์„œ ๋‹ค๋ฅธ ์ง€๊ฐ‘์œผ๋กœ ์ „์†กํ•˜์ง€ ๋ชป ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ•ด๋‹น ์ด์Šˆ๊ฐ€ ์ œ์ถœ๋˜๊ณ  ๋‚˜์„œ ํ”„๋กœ์ ํŠธ ์ธก์—์„œ๋Š” staking vault๋ฅผ ํ†ตํ•ด ๋ฐฉ์ง€ํ•  ์˜ˆ์ •์ด๋ผ๊ณ  ํ•œ๋‹ค.


ํ”ผ๋“œ๋ฐฑ


  1. ์กฐ๊ฑด๋ฌธ๊ณผ ์ˆซ์ž๊ฐ€ ๋‚˜์˜จ๋‹ค๋ฉด ์ฃผ์˜ ๊นŠ๊ฒŒ ๋ณผ ๊ฒƒ. ์ดํ•ด๊ฐ€ ์ž˜ ์•ˆ ๋˜๋ฉด ๊ณต์‹์„ ๋ณ€ํ˜•ํ•ด์„œ ์ดํ•ดํ•ด ๋ณผ ๊ฒƒ.
  2. ์ž๊ธˆ์ด ๋น ์ ธ๋‚˜๊ฐ€๋Š” ๊ฒƒ๋งŒ High vulnerability๊ฐ€ ์•„๋‹ˆ๋ผ, ํ”„๋กœ์ ํŠธ๊ฐ€ ์˜๋„๋œ ๋ฐฉํ–ฅ์œผ๋กœ ์šด์˜๋˜์ง€ ์•Š๋Š” ๊ฒƒ๋„ High vulnerability๋‹ค.
  3. ์–ด๋–ค ์ผ์„ ์œ„ํ•ด ํ† ํฐ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด, ํ† ํฐ์ด ๋ฏผํŒ…๋˜๊ธฐ ์ „์˜ ์ƒํ™ฉ์„ ์ƒ๊ฐํ•ด ๋ณผ ๊ฒƒ.
  4. withdrawApproval ํŒจํ„ด - frontRun ๊ธฐ์–ตํ•˜๊ธฐ
  5. ์ฝ”๋ฉ˜ํŠธ์— ์ด์Šˆ์™€ ๊ด€๋ จ๋œ ํžŒํŠธ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.
  6. HM ์ด์Šˆ๋“ค์€ ์ปจํŠธ๋ž™ํŠธ์˜ ๋ฉ”์ธ ๋กœ์ง์—์„œ ์ฃผ๋กœ ๋ฐœ๊ฒฌ๋œ๋‹ค. ์ปจํŠธ๋ž™ํŠธ์˜ ๋ฉ”์ธ์ด ์–ด๋–ค ๋ถ€๋ถ„์ธ์ง€ ๋จผ์ € ํŒŒ์•…ํ•˜์ž.

์ถœ์ฒ˜ ๋ฐ ์ฐธ๊ณ ์ž๋ฃŒ


  1. EXTCODESIZE Checks
  2. How does a contract find out if another address is a contract?
profile
Just BUIDL :)

0๊ฐœ์˜ ๋Œ“๊ธ€