hats-finance / SeeR-PM-0x899bc13919880db76edf4ccd72bdfa5dfa666fb7

1 stars 0 forks source link

MarketFactory’s market creation functions not payable leading to unusable protocol #3

Open hats-bug-reporter[bot] opened 4 hours ago

hats-bug-reporter[bot] commented 4 hours ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0xa12d42f476f20830a264cbc23a927d34e2ef13fd1e0364ee588147dbb15ca3be Severity: medium

Description: Description\ The contract MarketFactory has four functions for market creation, namely: createCategoricalMarket , createMultiCategoricalMarket , createScalarMarket and createMultiScalarMarket . These methods are not payable , which means the caller cannot send native currency when calling them. Although, these methods make use of external functions that are payable and have proper checks in place that will revert if the necessary msg.value is not sent.

Reproduce Scenario

  1. Call createMultiScalarMarket() function

  2. internal function createMarket() is called

  3. internal function createNewMarketParams() is called

  4. internal function askRealityQuestion() is called

  5. external payable function askQuestionWithMinBond() is called

  6. external’s contract _askQuestion() function is called

  7. checks whether the msg.value is greater or equal to question_fee

Attachments

  1. Proof of Concept (PoC)
function createMultiScalarMarket(CreateMarketParams calldata params) external returns (address) {
...
function askRealityQuestion(
    string memory encodedQuestion,
    uint256 templateId,
    uint32 openingTime,
    uint256 minBond
) internal returns (bytes32) {
    bytes32 content_hash = keccak256(abi.encodePacked(templateId, openingTime, encodedQuestion));

    bytes32 question_id = keccak256(
        abi.encodePacked(
            content_hash, arbitrator, questionTimeout, minBond, address(realitio), address(this), uint256(0)
        )
    );

    if (realitio.getTimeout(question_id) != 0) {
        return question_id;
    }

    return realitio.askQuestionWithMinBond(
        templateId, encodedQuestion, arbitrator, questionTimeout, openingTime, 0, minBond
    );
}
...
function askQuestionWithMinBond(uint256 template_id, string memory question, address arbitrator, uint32 timeout, uint32 opening_ts, uint256 nonce, uint256 min_bond) 
    // stateNotCreated is enforced by the internal _askQuestion
public payable returns (bytes32) {

    require(templates[template_id] > 0, "template must exist");

    bytes32 content_hash = keccak256(abi.encodePacked(template_id, opening_ts, question));
    bytes32 question_id = keccak256(abi.encodePacked(content_hash, arbitrator, timeout, min_bond, address(this), msg.sender, nonce));

    // We emit this event here because _askQuestion doesn't need to know the unhashed question.
    // Other events are emitted by _askQuestion.
    emit LogNewQuestion(question_id, msg.sender, template_id, question, content_hash, arbitrator, timeout, opening_ts, nonce, block.timestamp);
    _askQuestion(question_id, content_hash, arbitrator, timeout, opening_ts, min_bond);

    return question_id;
}
...
function _askQuestion(bytes32 question_id, bytes32 content_hash, address arbitrator, uint32 timeout, uint32 opening_ts, uint256 min_bond) 
    stateNotCreated(question_id)
internal {

    // A timeout of 0 makes no sense, and we will use this to check existence
    require(timeout > 0, "timeout must be positive"); 
    require(timeout < 365 days, "timeout must be less than 365 days"); 

    uint256 bounty = msg.value;

    // The arbitrator can set a fee for asking a question. 
    // This is intended as an anti-spam defence.
    // The fee is waived if the arbitrator is asking the question.
    // This allows them to set an impossibly high fee and make users proxy the question through them.
    // This would allow more sophisticated pricing, question whitelisting etc.
    if (arbitrator != NULL_ADDRESS && msg.sender != arbitrator) {
        uint256 question_fee = arbitrator_question_fees[arbitrator];
        require(bounty >= question_fee, "ETH provided must cover question fee"); 
        bounty = bounty - question_fee;
        balanceOf[arbitrator] = balanceOf[arbitrator] + question_fee;
    }
        ...
}
  1. Revised Code

In order to overcome this issue, add the payable keyword to every market creation function within MarketFactory contract.

clesaege commented 4 hours ago

None of those function receive nor send any base assets. Reality has some option to provide some question bounty (I can't see it ever been used) which is not used by Seer either.