Skip to content

Commit

Permalink
Add a function to verify history input for an open question (changes …
Browse files Browse the repository at this point in the history
…bytecode)
  • Loading branch information
edmundedgar committed Jan 27, 2024
1 parent 96ff973 commit c8f0910
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/contracts/abi/solc-0.8.20/RealityETH-4.0.abi.json

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/contracts/bytecode/RealityETH-4.0.bin

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/contracts/bytecode/RealityETH_ERC20-4.0.bin

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only

pragma solidity 0.8.20;

// These functions were removed added to reality.eth in version 4.
interface IRealityETHHistoryVerification {
function isHistoryOfUnfinalizedQuestionValid(bytes32 question_id, bytes32[] memory history_hashes, address[] memory addrs, uint256[] memory bonds, bytes32[] memory answers) external view returns (bool);
}
37 changes: 36 additions & 1 deletion packages/contracts/development/contracts/RealityETH-4.0.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
pragma solidity ^0.8.20;

import {IRealityETHCore} from "./IRealityETHCore.sol";
import {IRealityETHHistoryVerification} from "./IRealityETHHistoryVerification.sol";
import {BalanceHolder} from "./BalanceHolder.sol";

// solhint-disable-next-line contract-name-camelcase
contract RealityETH_v4_0 is BalanceHolder, IRealityETHCore {
contract RealityETH_v4_0 is BalanceHolder, IRealityETHCore, IRealityETHHistoryVerification {
address private constant NULL_ADDRESS = address(0);

// History hash when no history is created, or history has been cleared
Expand Down Expand Up @@ -664,6 +665,40 @@ contract RealityETH_v4_0 is BalanceHolder, IRealityETHCore {
withdraw();
}

/// @notice Returns true if the supplied history is valid
/// @dev Caller must provide the answer history, in reverse order back to the item they want to check
/// @dev Not necessarily the entire history
/// @dev Useful for freezing an action once a bond is paid for a particular answer, without waiting for resolution
/// @dev Cannot be used after the question is finalized
/// @param question_id The ID of the question
/// @param history_hashes Second-last-to-first, the hash of each history entry. (Final one should be empty).
/// @param addrs Last-to-first, the address of each answerer
/// @param bonds Last-to-first, the bond supplied with each answer
/// @param answers Last-to-first, each answer supplied
function isHistoryOfUnfinalizedQuestionValid(
bytes32 question_id,
bytes32[] memory history_hashes,
address[] memory addrs,
uint256[] memory bonds,
bytes32[] memory answers
) external view stateOpenOrPendingArbitration(question_id) returns (bool) {
bytes32 last_history_hash = questions[question_id].history_hash;

uint256 hist_len = history_hashes.length;
// Check for uneven length entries to make sure we validate all the inputs
if (addrs.length != hist_len || bonds.length != hist_len || answers.length != hist_len) {
return false;
}

for (uint256 i = 0; i < hist_len; i++) {
if (!_isHistoryInputValidForHash(last_history_hash, history_hashes[i], answers[i], bonds[i], addrs[i])) {
return false;
}
last_history_hash = history_hashes[i];
}
return true;
}

/// @notice Returns the questions's content hash, identifying the question content
/// @param question_id The ID of the question
function getContentHash(bytes32 question_id) public view returns (bytes32) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IRealityETHCore_ERC20} from "./IRealityETHCore_ERC20.sol";
import {IRealityETHHistoryVerification} from "./IRealityETHHistoryVerification.sol";
import {BalanceHolder_ERC20} from "./BalanceHolder_ERC20.sol";

// solhint-disable-next-line contract-name-camelcase
contract RealityETH_ERC20_v4_0 is BalanceHolder_ERC20, IRealityETHCore_ERC20 {
contract RealityETH_ERC20_v4_0 is BalanceHolder_ERC20, IRealityETHCore_ERC20, IRealityETHHistoryVerification {
address private constant NULL_ADDRESS = address(0);

// History hash when no history is created, or history has been cleared
Expand Down Expand Up @@ -748,6 +749,40 @@ contract RealityETH_ERC20_v4_0 is BalanceHolder_ERC20, IRealityETHCore_ERC20 {
withdraw();
}

/// @notice Returns true if the supplied history is valid
/// @dev Caller must provide the answer history, in reverse order back to the item they want to check
/// @dev Not necessarily the entire history
/// @dev Useful for freezing an action once a bond is paid for a particular answer, without waiting for resolution
/// @dev Cannot be used after the question is finalized
/// @param question_id The ID of the question
/// @param history_hashes Second-last-to-first, the hash of each history entry. (Final one should be empty).
/// @param addrs Last-to-first, the address of each answerer
/// @param bonds Last-to-first, the bond supplied with each answer
/// @param answers Last-to-first, each answer supplied
function isHistoryOfUnfinalizedQuestionValid(
bytes32 question_id,
bytes32[] memory history_hashes,
address[] memory addrs,
uint256[] memory bonds,
bytes32[] memory answers
) external view stateOpenOrPendingArbitration(question_id) returns (bool) {
bytes32 last_history_hash = questions[question_id].history_hash;

uint256 hist_len = history_hashes.length;
// Check for uneven length entries to make sure we validate all the inputs
if (addrs.length != hist_len || bonds.length != hist_len || answers.length != hist_len) {
return false;
}

for (uint256 i = 0; i < hist_len; i++) {
if (!_isHistoryInputValidForHash(last_history_hash, history_hashes[i], answers[i], bonds[i], addrs[i])) {
return false;
}
last_history_hash = history_hashes[i];
}
return true;
}

/// @notice Returns the questions's content hash, identifying the question content
/// @param question_id The ID of the question
function getContentHash(bytes32 question_id) public view returns (bytes32) {
Expand Down
65 changes: 64 additions & 1 deletion packages/contracts/tests/python/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@

if VERNUM >= 4.0:
IS_COMMIT_REVEAL_SUPPORTED = False
IS_PROVE_HISTORY_SUPPORTED = True
else:
IS_COMMIT_REVEAL_SUPPORTED = True
IS_PROVE_HISTORY_SUPPORTED = False

print("IS_COMMIT_REVEAL_SUPPORTED is "+str(IS_COMMIT_REVEAL_SUPPORTED))

Expand Down Expand Up @@ -2624,13 +2626,74 @@ def test_too_soon_bonds_under_unrevealed_commit(self):

self.assertEqual(self.rc0.functions.balanceOf(k3).call(), claimable)

#@unittest.skipIf(WORKING_ONLY, "Not under construction")
def test_is_history_of_unfinalized_question_valid(self):


if not IS_PROVE_HISTORY_SUPPORTED:
print("Skipping test_is_history_of_unfinalized_question_valid, not supported by this version")
return

k2 = self.web3.eth.accounts[2]
k3 = self.web3.eth.accounts[3]
k4 = self.web3.eth.accounts[4]

if ERC20:
self._issueTokens(k3, 100000, 50000)
self._issueTokens(k4, 100000, 50000)

st = None
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 20, k3)
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 20, 40, k3)
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 40, 80, k4)
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1004, 80, 160, k3)
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1003, 160, 320, k4)
st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 320, 640, k4)

hashes = st['hash']
addrs = st['addr']
bonds = st['bond']
answers = st['answer']

self.assertTrue(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes, addrs, bonds, answers).call())

bad_hashes = hashes.copy()
bad_hashes[2] = bad_hashes[1]
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, bad_hashes, addrs, bonds, answers).call())

bad_addrs = addrs.copy()
bad_addrs[2] = k2
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes, bad_addrs, bonds, answers).call())

bad_bonds = bonds.copy()
bad_bonds[1] = 987
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes, addrs, bad_bonds, answers).call())

bad_answers = answers.copy()
bad_answers[1] = to_answer_for_contract(987)
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes, addrs, bonds, bad_answers).call())

# Supplying just the end of the list (lowest-bond answers) will fail
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[3:], addrs[3:], bonds[3:], answers[3:]).call())

# Supplying just the start of the list (highest-bond answers) will work
self.assertTrue(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:3], bonds[:3], answers[:3]).call())

# Any unevenness in the length of parameters passed will fail
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:4], addrs[:3], bonds[:3], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:4], bonds[:3], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:3], bonds[:4], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:3], bonds[:3], answers[:4]).call())

self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:2], addrs[:3], bonds[:3], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:2], bonds[:3], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:3], bonds[:2], answers[:3]).call())
self.assertFalse(self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, hashes[:3], addrs[:3], bonds[:3], answers[:2]).call())

self._advance_clock(33)

# Once we finalize this will revert
with self.assertRaises(TransactionFailed):
self.rc0.functions.isHistoryOfUnfinalizedQuestionValid(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact()

if __name__ == '__main__':
main()
Expand Down

0 comments on commit c8f0910

Please sign in to comment.