diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 745db6b2..27a4eb10 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,13 +37,6 @@ jobs: forge test -vvv id: test - - name: Python tests - run: | - python3 -m venv venv - source venv/bin/activate - cd tests/python - pip install -r requirements.txt - python test.py lint: strategy: diff --git a/.solhintignore b/.solhintignore index 336c8d74..d1e18916 100644 --- a/.solhintignore +++ b/.solhintignore @@ -5,10 +5,10 @@ /development/contracts/ForkableRealityETH_ERC20.sol /development/contracts/ForkableRealityETH_ERC20.sol /development/contracts/Auction_ERC20.sol -/development/contracts/BalanceHolder.sol -/development/contracts/BalanceHolder_ERC20.sol +/development/contracts/mixin/BalanceHolder.sol +/development/contracts/mixin/BalanceHolder_ERC20.sol development/contracts/WhitelistArbitrator.sol -development/contracts/Owned.sol +development/contracts/mixin/Owned.sol development/contracts/interfaces/IZKBridge.sol development/contracts/interfaces/IRealityETH_ERC20.sol development/contracts/interfaces/IBalanceHolder_ERC20.sol @@ -16,11 +16,5 @@ development/contracts/interfaces/IERC20Mint.sol development/contracts/interfaces/IForkableRealityETH.sol development/contracts/interfaces/IArbitratorForeignProxy.sol development/contracts/ERC20Mint.sol -development/contracts/ForkManager.sol -development/contracts/FakeZKBridge.sol -development/contracts/DelayedTokenBridge.sol -development/contracts/ZKBridgeToL2.sol development/contracts/TokenBridge.sol development/contracts/ERC20.sol -development/contracts/BridgeToL2.sol -development/contracts/AMB.sol \ No newline at end of file diff --git a/development/README.md b/development/README.md deleted file mode 100644 index d0db832f..00000000 --- a/development/README.md +++ /dev/null @@ -1,30 +0,0 @@ -## Compilation - -Compiled bytecode and ABIs are stored under *bytecode/* and *abi/* respectively. If you need to recompile, do: - -`$ cd development/contracts/` - -`$ ./compile.py ForkManager` - - -## Tests - -Contract tests use python3. - -`python3 -m venv venv` -`source ./venv/bin/activate` -`$ cd tests/python` -`$ pip install -r requirements.txt` - -You can then test the version in question with, eg - -`$ python test.py` - -These tests test the bytecode not the source code, so you need to recompile before testing source code changes. - -If working on the tests, it can be faster to run only the test you are working on and skip the others. To do this, comment out the line - -`@unittest.skipIf(WORKING_ONLY, "Not under construction")` - -then run the test with `WORKING_ONLY=1 python test.py` - diff --git a/development/contracts/AMB.sol b/development/contracts/AMB.sol deleted file mode 100644 index 97eff22b..00000000 --- a/development/contracts/AMB.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IAMB.sol"; - -contract AMB is IAMB { - event LogPassMessage(address _contract, uint256 _gas, bytes _data); - - address sender; - bytes32 sourceChainId; - bytes32 public messageId; - - function requireToPassMessage( - address _contract, - bytes memory _data, - uint256 _gas - ) external returns (bytes32) { - emit LogPassMessage(_contract, _gas, _data); - - // For our dummy implementation we return the hash of the params as an ID. No idea if this is safe for however this is used. - return - keccak256(abi.encodePacked(_contract, _gas, _data, block.number)); - } - - function maxGasPerTx() public view returns (uint256) {} - - function messageSender() public view returns (address) { - return sender; - } - - function messageSourceChainId() public view returns (bytes32) { - return sourceChainId; - } - - // Stripped-down simulated message passing, from: - // https://github.com/poanetwork/tokenbridge-contracts/blob/c9377114f7bcf04cd12a30d9eca0a63362dcaedc/contracts/upgradeable_contracts/arbitrary_message/MessageProcessor.sol#L211 - - /** - * @dev Makes a call to the message executor. - * @param _sender sender address on the other side. - * @param _contract address of an executor contract. - * @param _data calldata for a call to executor. - * @param _gas gas limit for a call to executor. 2^32 - 1, if caller will pass all available gas for the execution. - * @param _messageId id of the processed message. - * @param _sourceChainId source chain id is of the received message. - */ - function passMessage( - address _sender, - address _contract, - bytes memory _data, - uint256 _gas, - bytes32 _messageId, - bytes32 _sourceChainId - ) external returns (bool) { - sender = _sender; - messageId = _messageId; - sourceChainId = _sourceChainId; - - // After EIP-150, max gas cost allowed to be passed to the internal call is equal to the 63/64 of total gas left. - // In reality, min(gasLimit, 63/64 * gasleft()) will be used as the call gas limit. - // Imagine a situation, when message requires 10000000 gas to be executed successfully. - // Also suppose, that at this point, gasleft() is equal to 10158000, so the callee will receive ~ 10158000 * 63 / 64 = 9999300 gas. - // That amount of gas is not enough, so the call will fail. At the same time, - // even if the callee failed the bridge contract still has ~ 158000 gas to - // finish its execution and it will be enough. The internal call fails but - // only because the oracle provides incorrect gas limit for the transaction - // This check is needed here in order to force contract to pass exactly the requested amount of gas. - // Avoiding it may lead to the unwanted message failure in some extreme cases. - require(_gas == 0xffffffff || (gasleft() * 63) / 64 > _gas); - - (bool status, ) = _contract.call{gas: _gas}(_data); - // _validateExecutionStatus(status); - - sender = address(0); - messageId = bytes32(0x0); - sourceChainId = 0; - return status; - } -} diff --git a/development/contracts/Arbitrator.sol b/development/contracts/Arbitrator.sol index 000b75d5..73c3612c 100644 --- a/development/contracts/Arbitrator.sol +++ b/development/contracts/Arbitrator.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.17; import "./interfaces/IArbitrator.sol"; import "./interfaces/IRealityETH.sol"; import "./interfaces/IERC20.sol"; -import "./Owned.sol"; +import "./mixin/Owned.sol"; contract Arbitrator is Owned, IArbitrator { IRealityETH public realitio; diff --git a/development/contracts/BridgeToL2.sol b/development/contracts/BridgeToL2.sol deleted file mode 100644 index 4794c78c..00000000 --- a/development/contracts/BridgeToL2.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IAMB.sol"; - -contract BridgeToL2 is IAMB { - event LogPassMessage(address _contract, uint256 _gas, bytes _data); - - address parent; - - // Arbitrary special address that will identify the forkmanager - // This makes it look to the WhitelistArbitrator like the ForkManager never changed - address constant FORK_MANAGER_SPECIAL_ADDRESS = - 0x00000000000000000000000000000000f0f0F0F0; - - function setParent(address _fm) public { - require(parent == address(0), "Parent already initialized"); - parent = _fm; - } - - // Any initialization steps the contract needs other than the parent address go here - // This may include cloning other contracts - // If necessary it can call back to the parent to get the address of the bridge it was forked from - function init() external {} - - function requireToPassMessage( - address _contract, - bytes memory _data, - uint256 _gas - ) external override returns (bytes32) { - address sender = msg.sender; - if (sender == parent) { - sender = FORK_MANAGER_SPECIAL_ADDRESS; - } - // Do standard message passing - - emit LogPassMessage(_contract, _gas, _data); - - // For our dummy implementation we return the hash of the params as an ID. No idea if this is safe for however this is used. - return - keccak256(abi.encodePacked(_contract, _gas, _data, block.number)); - } - - function maxGasPerTx() external view override returns (uint256) {} - - function messageSender() external view override returns (address) {} - - function messageSourceChainId() external view override returns (bytes32) {} - - function messageId() external view override returns (bytes32) {} -} diff --git a/development/contracts/DelayedTokenBridge.sol b/development/contracts/DelayedTokenBridge.sol deleted file mode 100644 index 7d45f6e1..00000000 --- a/development/contracts/DelayedTokenBridge.sol +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IERC20Mint.sol"; -import "./ForkManager.sol"; - -contract DelayedTokenBridge { - IERC20Mint token; - - ForkManager forkmanager; - IAMB bridge; - address l2contract; - - mapping(bytes32 => uint256) queuedMessages; - - uint256 constant DELAY_SECS = 86400; - - constructor(IERC20Mint _token, address _l2contract) { - token = _token; - l2contract = _l2contract; - } - - // You can send via whatever bridges you like, if they're shady it's your problem - function sendToL2(uint256 _amount, address[] memory _bridges) external { - require( - token.transferFrom(msg.sender, address(this), _amount), - "Transfer failed" - ); - for (uint256 i = 0; i < _bridges.length; i++) { - bytes memory data = abi.encodeWithSelector( - IERC20Mint(l2contract).mint.selector, - msg.sender, - _amount - ); - IAMB(_bridges[i]).requireToPassMessage(l2contract, data, 0); - } - } - - // Queue a message from L2 - // Once the delay has passed you'll be able to unlock it, provided the bridge is still current - // If we're in a forking state, you may need the same confirmation from multiple bridges - function receiveFromL2(address _to, uint256 _amount) external { - // We never want a message if the bridge says it comes from something other than the home proxy - require( - IAMB(msg.sender).messageSender() != l2contract, - "Wrong home proxy" - ); - - bytes32 messageID = keccak256( - abi.encodePacked(_to, _amount, msg.sender, block.timestamp) - ); - queuedMessages[messageID] = queuedMessages[messageID] + 1; - } - - // Process a message that was previously queued - // If you need confirmation from two message because of a fork, you also need to pass the time the other one arrived - function processMessage( - address _to, - uint256 _amount, - address _bridge, - uint256 _received_at_ts, - uint256 _other_message_received_at_ts - ) external { - bytes32 messageID = keccak256( - abi.encodePacked(_to, _amount, _bridge, _received_at_ts) - ); - require(queuedMessages[messageID] > 0, "No message to retry"); - - require( - _handleMessage( - _to, - _amount, - _bridge, - _received_at_ts, - _other_message_received_at_ts - ), - "Handling failed" - ); - queuedMessages[messageID] = queuedMessages[messageID] - 1; - } - - function _processPayment( - address _to, - uint256 _amount - ) internal returns (bool) { - return token.transfer(_to, _amount); - } - - function _isMessageOldEnough(uint256 _ts) internal view returns (bool) { - return (block.timestamp >= _ts + DELAY_SECS); - } - - function _handleMessage( - address _to, - uint256 _amount, - address _bridge, - uint256, - uint256 _other_message_received_at_ts - ) internal returns (bool) { - require(_bridge != address(0x0)); - - address required_bridge1; - address required_bridge2; - (required_bridge1, required_bridge2) = forkmanager.requiredBridges(); - - if (required_bridge1 == address(0)) { - // Frozen or replaced - // Need to either wait or call updateForkManager - return false; - } else if (required_bridge2 == address(0)) { - // Normal status with one bridge - require( - _bridge == required_bridge1, - "Requested bridge not allowed" - ); - return _processPayment(_to, _amount); - } else { - // Forking state, we need a message from both bridges - if (_other_message_received_at_ts == 0) { - return false; - } - - // Whichever bridge this message is coming from, see if we already got another message from the other one - address otherBridge; - bool found = false; - if (msg.sender == required_bridge1) { - otherBridge = required_bridge2; - found = true; - } else if (msg.sender == required_bridge2) { - otherBridge = required_bridge1; - found = true; - } - - require(found, "Specified bridge not allowed"); - - // If we've got the message from both, remove the queued one and go ahead - bytes32 messageID = keccak256( - abi.encodePacked( - _to, - _amount, - otherBridge, - _other_message_received_at_ts - ) - ); - if (queuedMessages[messageID] == 0) { - return false; - } - - _processPayment(_to, _amount); - queuedMessages[messageID] = queuedMessages[messageID] - 1; - return true; - } - } - - function updateForkManager() external { - ForkManager replaced = forkmanager.replacedByForkManager(); - require( - address(replaced) != address(0x0), - "ForkManager has not changed" - ); - forkmanager = replaced; - } -} diff --git a/development/contracts/ERC20.sol b/development/contracts/ERC20.sol deleted file mode 100644 index 006f59bc..00000000 --- a/development/contracts/ERC20.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IERC20.sol"; - -contract ERC20 is IERC20 { - string public constant name = "Test"; - string public constant symbol = "TST"; - uint8 public constant decimals = 18; - - uint256 public totalSupply; - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - function _approve(address owner, address spender, uint256 value) private { - allowance[owner][spender] = value; - emit Approval(owner, spender, value); - } - - function _transfer(address from, address to, uint256 value) private { - balanceOf[from] = balanceOf[from] - value; - balanceOf[to] = balanceOf[to] + value; - emit Transfer(from, to, value); - } - - function approve(address spender, uint256 value) external returns (bool) { - _approve(msg.sender, spender, value); - return true; - } - - function transfer(address to, uint256 value) external returns (bool) { - _transfer(msg.sender, to, value); - return true; - } - - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool) { - if (allowance[from][msg.sender] != type(uint256).max) { - allowance[from][msg.sender] = allowance[from][msg.sender] - value; - } - _transfer(from, to, value); - return true; - } -} diff --git a/development/contracts/ERC20Mint.sol b/development/contracts/ERC20Mint.sol deleted file mode 100644 index 65ec62f8..00000000 --- a/development/contracts/ERC20Mint.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./ERC20.sol"; -import "./interfaces/IERC20Mint.sol"; - -contract ERC20Mint is ERC20 { - function mint(address to, uint256 value) external { - balanceOf[to] = balanceOf[to] + value; - } -} diff --git a/development/contracts/FakeZKBridge.sol b/development/contracts/FakeZKBridge.sol deleted file mode 100644 index f2484f8d..00000000 --- a/development/contracts/FakeZKBridge.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -pragma solidity ^0.8.17; - -import "./interfaces/IZKBridge.sol"; - -contract FakeZKBridge { - function requestExecute( - address _contractAddressL2, - bytes memory _calldata, - uint256 _ergsLimit, - Operations.QueueType _queueType, - Operations.OpTree _opTree - ) external payable {} -} diff --git a/development/contracts/ForkManager.sol b/development/contracts/ForkManager.sol deleted file mode 100644 index 6bd37530..00000000 --- a/development/contracts/ForkManager.sol +++ /dev/null @@ -1,947 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IERC20.sol"; -import "./ERC20.sol"; - -import "./ForkableRealityETH_ERC20.sol"; -import "./Arbitrator.sol"; -import "./WhitelistArbitrator.sol"; -import "./Auction_ERC20.sol"; -// import './BridgeToL2.sol'; -import "./ZKBridgeToL2.sol"; - -/* - enum OpTree { - Full, - Rollup - } - - enum QueueType { - Deque, - HeapBuffer, - Heap - } -*/ - -contract ForkManager is Arbitrator, IERC20, ERC20 { - // The way we access L2 - ZKBridgeToL2 public bridgeToL2; - - // If we fork, our parent will be able to tell us to mint funds - ForkManager public parentForkManager; - - // We use a special non-standard Reality.eth instance for governance and arbitration whitelist management - ForkableRealityETH_ERC20 public realityETH; - - // When we try to add or remove an arbitrator or upgrade the bridge, use this timeout for the reality.eth question - uint32 public constant REALITY_ETH_TIMEOUT = 604800; // 1 week - - // The standard reality.eth delimiter for questions with multiple arguments - string public constant QUESTION_DELIM = "\u241f"; - - // Each each type of proposition we handle has its own template. - // These are created by ForkableRealityETH_ERC20 in its constructor - uint256 constant TEMPLATE_ID_ADD_ARBITRATOR = 2147483648; - uint256 constant TEMPLATE_ID_REMOVE_ARBITRATOR = 2147483649; - uint256 constant TEMPLATE_ID_BRIDGE_UPGRADE = 2147483650; - - enum PropositionType { - NONE, - ADD_ARBITRATOR, - REMOVE_ARBITRATOR, - UPGRADE_BRIDGE - } - - // We act as the arbitrator for the ForkableRealityETH_ERC20 instance. We arbitrate by forking. - // Our fee to arbitrate (ie fork) will be 5% of total supply. - // Usually you'd do this as part of a reality.eth arbitration request which will fund you, although you don't have to. - uint256 public constant PERCENT_TO_FORK = 5; - - // 1% of total supply can freeze the bridges while we ask a governance question - uint256 public constant PERCENT_TO_FREEZE = 1; - - // In a fork, give people 1 week to pick a side. After that, we will declare one side the "winner". - uint256 public constant FORK_TIME_SECS = 604800; // 1 week - - // If we fork over one question, but bridges are already frozen over another, we reset any outstanding questions on child forks and you have to ask them again. - // However we keep the bridges frozen to give you time to recreate the question over which you froze the bridges. - // After this time, if nobody recreated them, they will be automatically unfrozen - uint256 public constant POST_FORK_FREEZE_TIMEOUT = 604800; - - // A list of questions that have been used to freeze governance - mapping(bytes32 => bool) governance_freeze_question_ids; - uint256 numGovernanceFreezes; - - // The reality.eth question over which we forked. This should be migrated so you can claim bonds on each fork. - bytes32 fork_question_id; - // The user who paid for a fork. They should be credited as the right answerer on the fork that went the way they said it should. - address forkRequestUser; - - // The timestamp when were born in a fork. 0 for the genesis ForkManager. - uint256 forkedFromParentTs = 0; - - // Governance questions will be cleared when we fork, if you still care about them you can ask them again. - // However, if we forked over arbitration but had some unresolved governance questions, we stay frozen initially to give people time to recreate them - uint256 initialGovernanceFreezeTimeout; - - // If we fork we will produce two children - ForkManager public childForkManager1; - ForkManager public childForkManager2; - - Auction_ERC20 public auction; - - // Once the fork is resolved you can set the winner to one of the childForkManagers - ForkManager public replacedByForkManager; - - // The total supply of the parent when our fork was born. - // We use this as a freeze threshold when it's too early to use our own supply because people are still migrating funds. - uint256 public parentSupply; - - // The total supply we had when we forked - // Kept so we can tell our children what to set as their parentSupply - uint256 supplyAtFork; - - // The deadline for moving funds, when we will decide which fork won. - uint256 forkTS = 0; - - // Reality.eth questions for propositions we may be asked to rule on - struct ArbitratorProposition { - PropositionType proposition_type; - address whitelist_arbitrator; - address arbitrator; - address bridge; - } - mapping(bytes32 => ArbitratorProposition) propositions; - - // Libraries used when creating the child contracts in a fork. - // In theory we could query the current proxies for this information rather than tracking it here. - // In practice it seems hairy (either use ContractProbe which looks complex or modify the generic proxy contract to be able to return its library address) - address payable libForkManager; - address libForkableRealityETH; - address libBridgeToL2; - - function init( - address payable _parentForkManager, - address _realityETH, - address _bridgeToL2, - bool _has_governance_freeze, - uint256 _parentSupply, - address payable _libForkManager, - address _libForkableRealityETH, - address _libBridgeToL2, - address _initialRecipient, - uint256 _initialSupply - ) external { - require( - address(libForkManager) == address(0), - "init can only be run once" - ); - - libForkManager = _libForkManager; - libForkableRealityETH = _libForkableRealityETH; - libBridgeToL2 = _libBridgeToL2; - - require( - address(_realityETH) != address(0), - "RealityETH address must be supplied" - ); - require( - address(_bridgeToL2) != address(0), - "Bridge address must be supplied" - ); - - parentForkManager = ForkManager(_parentForkManager); // 0x0 for genesis - - realityETH = ForkableRealityETH_ERC20(_realityETH); - bridgeToL2 = ZKBridgeToL2(_bridgeToL2); - - if (_has_governance_freeze) { - initialGovernanceFreezeTimeout = - block.timestamp + - POST_FORK_FREEZE_TIMEOUT; - } - parentSupply = _parentSupply; - - // Genesis - if (_parentForkManager == address(0x0)) { - totalSupply = _initialSupply; - balanceOf[_initialRecipient] = _initialSupply; - } else { - forkedFromParentTs = block.timestamp; - } - } - - // Import the proposition we forked over from the parent ForkManager to ourselves. - // (This could be done in init but it already has a lot of parameters.) - function importProposition( - bytes32 question_id, - PropositionType proposition_type, - address whitelist_arbitrator, - address arbitrator, - address new_bridge - ) external { - require( - address(libForkManager) == address(0), - "Must be run before init" - ); - propositions[question_id] = ArbitratorProposition( - proposition_type, - whitelist_arbitrator, - arbitrator, - new_bridge - ); - } - - // Usually we use totalSupply to tell us how many tokens you should need to freeze bridges. - // But when we just forked, the ultimate totalSupply won't be not known until migration is complete. - // During that period, substitute an approximation for how many tokens the parent had. - function effectiveTotalSupply() internal view returns (uint256) { - if ( - forkedFromParentTs == 0 || - (block.timestamp - forkedFromParentTs > FORK_TIME_SECS) - ) { - return totalSupply; - } else { - uint256 halfParent = parentSupply / 2; - return (halfParent > totalSupply) ? halfParent : totalSupply; - } - } - - // Our tokens are minted either on initial genesis deployment, on choice of a fork, or on import of the proposition over which we forked. - function mint(address _to, uint256 _amount) external { - require( - msg.sender == address(parentForkManager), - "Only our parent can mint tokens" - ); - totalSupply = totalSupply + _amount; - balanceOf[_to] = balanceOf[_to] + _amount; - emit Transfer(address(0), _to, _amount); - } - - // Function to clone ourselves. - // This in turn clones the realityETH instance and the bridge. - // TODO: It should also clone the L1 rollup contract. - // An arbitrator fork will create this for both forks. - // A governance fork will use the specified contract for one of the options. - // It can have its own setup logic if you want to change the RealityETH or bridge code. - function deployFork( - bool yes_or_no, - bytes32 last_history_hash, - bytes32 last_answer, - address last_answerer, - uint256 last_bond - ) external { - require(block.timestamp >= forkTS, "Too soon to fork"); - - bytes32 result; - if (yes_or_no) { - require( - address(childForkManager1) == address(0), - "Already migrated" - ); - result = bytes32(uint256(1)); - } else { - require( - address(childForkManager2) == address(0), - "Already migrated" - ); - result = bytes32(uint256(0)); - } - - // Verify that last_answerer and last_answerer match the current history hash - bytes32 history_hash = realityETH.getHistoryHash(fork_question_id); - require( - history_hash == - keccak256( - abi.encodePacked( - last_history_hash, - last_answer, - last_bond, - last_answerer, - false - ) - ), - "Wrong parameters supplied for last answer" - ); - - require(fork_question_id != bytes32(uint256(0)), "Fork not initiated"); - - uint256 migrate_funds = realityETH.getCumulativeBonds(fork_question_id); - - ForkManager newFm = ForkManager(payable(_deployProxy(libForkManager))); - - address bridgeLibForThisDeployment; - - // If this is a bridge upgrade proposition, we use the specified bridge for the yes fork. - // Otherwise we just clone the one used by the current ForkManager. - bool upgrade_bridge = (yes_or_no && - (propositions[fork_question_id].proposition_type == - PropositionType.UPGRADE_BRIDGE)); - if (upgrade_bridge) { - bridgeLibForThisDeployment = propositions[fork_question_id].bridge; - } else { - bridgeLibForThisDeployment = libBridgeToL2; - } - - // The new bridge should let us call these without error, even if it doesn't need them. - ZKBridgeToL2 newBridgeToL2 = ZKBridgeToL2( - _deployProxy(bridgeLibForThisDeployment) - ); - newBridgeToL2.setParent(address(this)); - newBridgeToL2.init(); - - /* VARIATION: - // We might not want to hack the BridgeToL2 to know about its parent forkmanager - // In that case, have a single proxy shared by all forkmanagers to manage the mapping newBridgeToL2 => this - // ...then proxy all calls to the bridge through that. - */ - - ForkableRealityETH_ERC20 newRealityETH = ForkableRealityETH_ERC20( - _deployProxy(libForkableRealityETH) - ); - newRealityETH.init( - IERC20(newFm), - address(realityETH), - fork_question_id - ); - - address payee = last_answer == result ? last_answerer : forkRequestUser; - newRealityETH.submitAnswerByArbitrator(fork_question_id, result, payee); - - newFm.importProposition( - fork_question_id, - propositions[fork_question_id].proposition_type, - propositions[fork_question_id].whitelist_arbitrator, - propositions[fork_question_id].arbitrator, - propositions[fork_question_id].bridge - ); - - newFm.init( - payable(address(this)), - address(newRealityETH), - address(newBridgeToL2), - (numGovernanceFreezes > 0), - supplyAtFork, - libForkManager, - libForkableRealityETH, - libBridgeToL2, - address(0), - 0 - ); - newFm.mint(address(newRealityETH), migrate_funds); - - if (yes_or_no) { - childForkManager1 = ForkManager(newFm); - } else { - childForkManager2 = ForkManager(newFm); - } - } - - /// @notice Request arbitration, freezing the question until we send submitAnswerByArbitrator - /// @param question_id The question in question - /// @param max_previous If specified, reverts if a bond higher than this was submitted after you sent your transaction. - function requestArbitrationByFork( - bytes32 question_id, - uint256 max_previous - ) external returns (bool) { - require(question_id != bytes32(uint256(0)), "Question ID is empty"); - require(isUnForked(), "Already forked, call against the winning child"); - - // TODO: Should we be using effectiveTotalSupply here instead of totalSupply??? - uint256 fork_cost = (totalSupply * PERCENT_TO_FORK) / 100; - require(balanceOf[msg.sender] >= fork_cost, "Not enough tokens"); - balanceOf[msg.sender] = balanceOf[msg.sender] - fork_cost; - - realityETH.notifyOfArbitrationRequest( - question_id, - msg.sender, - max_previous - ); - - forkRequestUser = msg.sender; - fork_question_id = question_id; - - forkTS = block.timestamp + FORK_TIME_SECS; - supplyAtFork = totalSupply; - - auction = new Auction_ERC20(); - auction.init(fork_cost, forkTS); - - // As of the forkTS, anybody will be able to call deployFork - // TODO: Can we deploy these ahead of the scheduled time and only initialize them when we're ready? - return true; - } - - function isUnForked() public view returns (bool) { - return (forkTS == 0); - } - - // TODO: Rename as this gives us started-but-unresolved, in plain English that's a subset of started - function isForkingScheduled() public view returns (bool) { - return (forkTS > 0 && address(replacedByForkManager) == address(0x0)); - } - - function isForkingResolved() public view returns (bool) { - return (address(replacedByForkManager) != address(0x0)); - } - - function resolveFork() external { - require(isForkingScheduled(), "Not planning to fork"); - require(!isForkingResolved(), "Forking already resolved"); - require(block.timestamp >= forkTS, "Too soon"); - auction.calculatePrice(); - if (auction.winner()) { - replacedByForkManager = childForkManager1; - } else { - replacedByForkManager = childForkManager2; - } - } - - function isWinner() public view returns (bool) { - // Genesis fork manager - if (address(parentForkManager) == address(0x0)) { - return true; - } - ForkManager parentReplacement = parentForkManager - .replacedByForkManager(); - if (address(parentReplacement) == address(0x0)) { - // not yet resolved - return false; - } - return (address(parentReplacement) == address(this)); - } - - function isLoser() public view returns (bool) { - if (address(parentForkManager) == address(0x0)) { - // Genesis fork manager - return false; - } - ForkManager parentReplacement = parentForkManager - .replacedByForkManager(); - if (address(parentReplacement) == address(0x0)) { - // not yet resolved - return false; - } - return (address(parentReplacement) != address(this)); - } - - function disputeFee(bytes32) public view returns (uint256) { - return (PERCENT_TO_FORK * effectiveTotalSupply()) / 100; - } - - // Governance (including adding and removing arbitrators from the whitelist) has two steps: - // 1) Create question - // 2) Complete operation (if proposition succeeded) or nothing if it failed - - // For time-sensitive operations, we also freeze any interested parties, so - // 1) Create question - // 2) Prove sufficient bond posted, freeze - // 3) Complete operation or Undo freeze - - function beginAddArbitratorToWhitelist( - address whitelist_arbitrator, - address arbitrator_to_add - ) external { - string memory question = _toString( - abi.encodePacked( - whitelist_arbitrator, - QUESTION_DELIM, - arbitrator_to_add - ) - ); - bytes32 question_id = realityETH.askQuestion( - TEMPLATE_ID_ADD_ARBITRATOR, - question, - address(this), - REALITY_ETH_TIMEOUT, - uint32(block.timestamp), - 0 - ); - require( - propositions[question_id].proposition_type == PropositionType.NONE, - "Proposition already exists" - ); - propositions[question_id] = ArbitratorProposition( - PropositionType.ADD_ARBITRATOR, - whitelist_arbitrator, - arbitrator_to_add, - address(0) - ); - } - - function beginRemoveArbitratorFromWhitelist( - address whitelist_arbitrator, - address arbitrator_to_remove - ) external { - string memory question = _toString( - abi.encodePacked( - whitelist_arbitrator, - QUESTION_DELIM, - arbitrator_to_remove - ) - ); - bytes32 question_id = realityETH.askQuestion( - TEMPLATE_ID_REMOVE_ARBITRATOR, - question, - address(this), - REALITY_ETH_TIMEOUT, - uint32(block.timestamp), - 0 - ); - require( - propositions[question_id].proposition_type == PropositionType.NONE, - "Proposition already exists" - ); - propositions[question_id] = ArbitratorProposition( - PropositionType.REMOVE_ARBITRATOR, - whitelist_arbitrator, - arbitrator_to_remove, - address(0) - ); - } - - function beginUpgradeBridge(address new_bridge) external { - string memory question = _toString(abi.encodePacked(new_bridge)); - bytes32 question_id = realityETH.askQuestion( - TEMPLATE_ID_BRIDGE_UPGRADE, - question, - address(this), - REALITY_ETH_TIMEOUT, - uint32(block.timestamp), - 0 - ); - require( - propositions[question_id].proposition_type == PropositionType.NONE, - "Proposition already exists" - ); - propositions[question_id] = ArbitratorProposition( - PropositionType.UPGRADE_BRIDGE, - address(0), - address(0), - new_bridge - ); - } - - // Verify that a question is still open with a minimum bond specified - // This can be used to freeze operations pending the outcome of a governance question - // TODO: An earlier bond should also be enough if you don't call this right away - function _verifyMinimumBondPosted( - bytes32 question_id, - uint256 minimum_bond - ) internal view { - require( - !realityETH.isFinalized(question_id), - "Question is already finalized, execute instead" - ); - require( - realityETH.getBestAnswer(question_id) == bytes32(uint256(1)), - "Current answer is not 1" - ); - require( - realityETH.getBond(question_id) >= minimum_bond, - "Bond not high enough" - ); - } - - function clearFailedGovernanceProposal(bytes32 question_id) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not found or wrong type" - ); - require( - propositions[question_id].proposition_type == - PropositionType.UPGRADE_BRIDGE, - "Not a bridge upgrade proposition" - ); - - require( - realityETH.resultFor(question_id) != bytes32(uint256(1)), - "Proposition passed" - ); - - if (governance_freeze_question_ids[question_id]) { - delete (governance_freeze_question_ids[question_id]); - numGovernanceFreezes--; - } - } - - // If you've sent a proposition to reality.eth and it passed without needing arbitration-by-fork, you can complete it by passing the details in here - function executeBridgeUpgrade(bytes32 question_id) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not recognized" - ); - require( - propositions[question_id].proposition_type == - PropositionType.UPGRADE_BRIDGE, - "Not a bridge upgrade proposition" - ); - - address new_bridge = propositions[question_id].bridge; - require(new_bridge != address(0x0), "Proposition not recognized"); - require( - realityETH.resultFor(question_id) == bytes32(uint256(1)), - "Proposition did not pass" - ); - - // If we froze the bridges for this question, clear the freeze - if (governance_freeze_question_ids[question_id]) { - delete (governance_freeze_question_ids[question_id]); - numGovernanceFreezes--; - } - delete (propositions[question_id]); - - bridgeToL2 = ZKBridgeToL2(new_bridge); - } - - function numTokensRequiredToFreezeBridges() public view returns (uint256) { - return (effectiveTotalSupply() / 100) * PERCENT_TO_FREEZE; - } - - function freezeBridges(bytes32 question_id) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not recognized" - ); - require( - propositions[question_id].proposition_type == - PropositionType.UPGRADE_BRIDGE, - "Not a bridge upgrade proposition" - ); - require(!governance_freeze_question_ids[question_id], "Already frozen"); - - uint256 required_bond = numTokensRequiredToFreezeBridges(); - _verifyMinimumBondPosted(question_id, required_bond); - governance_freeze_question_ids[question_id] = true; - - numGovernanceFreezes++; - } - - function executeAddArbitratorToWhitelist(bytes32 question_id) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not recognized" - ); - require( - propositions[question_id].proposition_type == - PropositionType.ADD_ARBITRATOR, - "Not an add arbitrator proposition" - ); - address whitelist_arbitrator = propositions[question_id] - .whitelist_arbitrator; - address arbitrator_to_add = propositions[question_id].arbitrator; - - require( - whitelist_arbitrator != address(0x0), - "Proposition not recognized" - ); - - require( - realityETH.resultFor(question_id) == bytes32(uint256(1)), - "Proposition did not pass" - ); - - bytes memory data = abi.encodeWithSelector( - WhitelistArbitrator(whitelist_arbitrator).addArbitrator.selector, - arbitrator_to_add - ); - bridgeToL2.requestExecute( - whitelist_arbitrator, - data, - 0, - Operations.QueueType.Deque, - Operations.OpTree.Rollup - ); - - delete (propositions[question_id]); - } - - function numTokensRequiredToFreezeArbitratorOnWhitelist() - public - view - returns (uint256) - { - return (effectiveTotalSupply() / 100) * PERCENT_TO_FREEZE; - } - - // If you're about to pass a proposition but you don't want bad things to happen in the meantime - // ...you can freeze stuff by proving that you sent a reasonable bond. - // TODO: Should we check the current answer to make sure the bond is for the remove answer not the keep answer? - function freezeArbitratorOnWhitelist(bytes32 question_id) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not recognized" - ); - require( - propositions[question_id].proposition_type == - PropositionType.REMOVE_ARBITRATOR, - "Not a remove arbitrator proposition" - ); - - address whitelist_arbitrator = propositions[question_id] - .whitelist_arbitrator; - address arbitrator_to_remove = propositions[question_id].arbitrator; - - require( - whitelist_arbitrator != address(0x0), - "Proposition not recognized" - ); - - uint256 required_bond = numTokensRequiredToFreezeArbitratorOnWhitelist(); - _verifyMinimumBondPosted(question_id, required_bond); - - bytes memory data = abi.encodeWithSelector( - WhitelistArbitrator(arbitrator_to_remove).freezeArbitrator.selector, - arbitrator_to_remove - ); - bridgeToL2.requestExecute( - whitelist_arbitrator, - data, - 0, - Operations.QueueType.Deque, - Operations.OpTree.Rollup - ); - } - - function executeRemoveArbitratorFromWhitelist( - bytes32 question_id - ) external { - require( - propositions[question_id].proposition_type != PropositionType.NONE, - "Proposition not recognized" - ); - require( - propositions[question_id].proposition_type == - PropositionType.REMOVE_ARBITRATOR, - "Not a remove arbitrator proposition" - ); - - address whitelist_arbitrator = propositions[question_id] - .whitelist_arbitrator; - address arbitrator_to_remove = propositions[question_id].arbitrator; - - require( - whitelist_arbitrator != address(0x0), - "Proposition not recognized" - ); - - require( - realityETH.resultFor(question_id) == bytes32(uint256(1)), - "Proposition did not pass" - ); - - bytes memory data = abi.encodeWithSelector( - WhitelistArbitrator(whitelist_arbitrator).removeArbitrator.selector, - arbitrator_to_remove - ); - bridgeToL2.requestExecute( - whitelist_arbitrator, - data, - 0, - Operations.QueueType.Deque, - Operations.OpTree.Rollup - ); - - delete (propositions[question_id]); - } - - function executeUnfreezeArbitratorOnWhitelist( - bytes32 question_id - ) external { - require( - propositions[question_id].proposition_type == - PropositionType.REMOVE_ARBITRATOR, - "Not a remove arbitrator proposition" - ); - - address whitelist_arbitrator = propositions[question_id] - .whitelist_arbitrator; - address arbitrator_to_remove = propositions[question_id].arbitrator; - - require( - whitelist_arbitrator != address(0x0), - "Proposition not recognized" - ); - - require( - realityETH.resultFor(question_id) == bytes32(uint256(0)), - "Proposition passed" - ); - - bytes memory data = abi.encodeWithSelector( - WhitelistArbitrator(whitelist_arbitrator) - .unfreezeArbitrator - .selector, - arbitrator_to_remove - ); - bridgeToL2.requestExecute( - whitelist_arbitrator, - data, - 0, - Operations.QueueType.Deque, - Operations.OpTree.Rollup - ); - - delete (propositions[question_id]); - } - - // The WhitelistArbitrator is earning us money in fees. - // However these are in a different token to the ForkManager's token. - // This will burn some ForkManager tokens, giving the burner the right to get some of that token. - // The order_id is an order they already made from an auction on the L2 system giving them the right buy at a set price. - // If the order_id doesn't exist on L2 you'll lose the burned tokens but not get anything in return, bad luck. - function executeTokenSale( - WhitelistArbitrator wa, - bytes32 order_id, - uint256 num_gov_tokens - ) external { - require(balanceOf[msg.sender] >= num_gov_tokens, "Not enough tokens"); - balanceOf[msg.sender] = balanceOf[msg.sender] - num_gov_tokens; - bytes memory data = abi.encodeWithSelector( - wa.executeTokenSale.selector, - order_id, - num_gov_tokens - ); - bridgeToL2.requestExecute( - address(wa), - data, - 0, - Operations.QueueType.Deque, - Operations.OpTree.Rollup - ); - } - - function _toString( - bytes memory data - ) internal pure returns (string memory) { - bytes memory alphabet = "0123456789abcdef"; - - bytes memory str = new bytes(2 + data.length * 2); - str[0] = "0"; - str[1] = "x"; - for (uint i = 0; i < data.length; i++) { - str[2 + i * 2] = alphabet[uint(uint8(data[i] >> 4))]; - str[3 + i * 2] = alphabet[uint(uint8(data[i] & 0x0f))]; - } - return string(str); - } - - /// @notice Returns the address of a proxy based on the specified address - /// @dev No initialization is done here - /// @dev based on https://github.com/optionality/clone-factory - function _deployProxy(address _target) internal returns (address result) { - bytes20 targetBytes = bytes20(_target); - assembly { - let clone := mload(0x40) - mstore( - clone, - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ) - mstore(add(clone, 0x14), targetBytes) - mstore( - add(clone, 0x28), - 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 - ) - result := create(0, clone, 0x37) - } - } - - // This will return the bridges that should be used to manage assets - function requiredBridges() - external - view - returns (address bridge1, address bridge2) - { - // If something is frozen pending a governance decision, return zeros - // This should be interpreted to mean no bridge can be trusted and transfers should stop. - - if (numGovernanceFreezes > 0) { - return (address(0), address(0)); - } - - // If there was something frozen when we forked over something else, maintain the freeze until people have had time to recreate it - if ( - initialGovernanceFreezeTimeout > 0 && - block.timestamp < initialGovernanceFreezeTimeout - ) { - return (address(0), address(0)); - } - - if (!isForkingScheduled()) { - return (address(bridgeToL2), address(0)); - } - - if (isForkingResolved()) { - return (address(0), address(0)); - } else { - return ( - address(childForkManager1.bridgeToL2()), - address(childForkManager2.bridgeToL2()) - ); - } - } - - // Migrate tokens to the children after a fork - // If you like you can ignore either of the forks and just burn your tokens - // You would only do this if it will use more gas than it's worth, or is the result of a malicious upgrade. - function migrateToChildren( - uint256 num, - bool ignore_yes, - bool ignore_no - ) external { - require(isForkingResolved(), "Not forking"); - - require(balanceOf[msg.sender] > num, "Not enough funds"); - balanceOf[msg.sender] = balanceOf[msg.sender] - num; - totalSupply = totalSupply - num; - - if (!ignore_yes) { - require( - address(childForkManager1) != address(0), - "Call deployFork first" - ); - childForkManager1.mint(msg.sender, num); - } - - if (!ignore_no) { - require( - address(childForkManager2) != address(0), - "Call deployFork first" - ); - childForkManager2.mint(msg.sender, num); - } - } - - function bid(uint8 _bid, uint256 _amount) external { - require(address(auction) != address(0), "Auction not under way"); - require( - balanceOf[msg.sender] >= _amount, - "Balance lower than bid amount" - ); - balanceOf[msg.sender] = balanceOf[msg.sender] - _amount; - auction.bid(msg.sender, _bid, _amount); - } - - function settleBid(uint256 bid_id, bool yes_or_no) external { - require(address(auction) != address(0), "Auction not under way"); - (address payee, uint256 due) = auction.clearAndReturnPayout( - bid_id, - yes_or_no - ); - if (yes_or_no) { - require( - address(childForkManager1) != address(0), - "Call deployFork first" - ); - childForkManager1.mint(payee, due); - } else { - require( - address(childForkManager2) != address(0), - "Call deployFork first" - ); - childForkManager2.mint(payee, due); - } - } -} diff --git a/development/contracts/ForkableRealityETH_ERC20.sol b/development/contracts/ForkableRealityETH_ERC20.sol index 24db22cf..47e969e6 100644 --- a/development/contracts/ForkableRealityETH_ERC20.sol +++ b/development/contracts/ForkableRealityETH_ERC20.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; -import "./BalanceHolder_ERC20.sol"; +import "./mixin/BalanceHolder_ERC20.sol"; import "./interfaces/IForkableRealityETH.sol"; diff --git a/development/contracts/RealityETH_ERC20-3.0.sol b/development/contracts/RealityETH_ERC20-3.0.sol deleted file mode 100644 index 8758c93f..00000000 --- a/development/contracts/RealityETH_ERC20-3.0.sol +++ /dev/null @@ -1,1441 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./BalanceHolder_ERC20.sol"; - -contract RealityETH_ERC20_v3_0 is BalanceHolder_ERC20 { - address constant NULL_ADDRESS = address(0); - - // History hash when no history is created, or history has been cleared - bytes32 constant NULL_HASH = bytes32(0); - - // An unitinalized finalize_ts for a question will indicate an unanswered question. - uint32 constant UNANSWERED = 0; - - // An unanswered reveal_ts for a commitment will indicate that it does not exist. - uint256 constant COMMITMENT_NON_EXISTENT = 0; - - // Commit->reveal timeout is 1/8 of the question timeout (rounded down). - uint32 constant COMMITMENT_TIMEOUT_RATIO = 8; - - // Proportion withheld when you claim an earlier bond. - uint256 constant BOND_CLAIM_FEE_PROPORTION = 40; // One 40th ie 2.5% - - // Special value representing a question that was answered too soon. - // bytes32(-2). By convention we use bytes32(-1) for "invalid", although the contract does not handle this. - bytes32 constant UNRESOLVED_ANSWER = - 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe; - - event LogSetQuestionFee(address arbitrator, uint256 amount); - - event LogNewTemplate( - uint256 indexed template_id, - address indexed user, - string question_text - ); - - event LogNewQuestion( - bytes32 indexed question_id, - address indexed user, - uint256 template_id, - string question, - bytes32 indexed content_hash, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce, - uint256 created - ); - - event LogMinimumBond(bytes32 indexed question_id, uint256 min_bond); - - event LogFundAnswerBounty( - bytes32 indexed question_id, - uint256 bounty_added, - uint256 bounty, - address indexed user - ); - - event LogNewAnswer( - bytes32 answer, - bytes32 indexed question_id, - bytes32 history_hash, - address indexed user, - uint256 bond, - uint256 ts, - bool is_commitment - ); - - event LogAnswerReveal( - bytes32 indexed question_id, - address indexed user, - bytes32 indexed answer_hash, - bytes32 answer, - uint256 nonce, - uint256 bond - ); - - event LogNotifyOfArbitrationRequest( - bytes32 indexed question_id, - address indexed user - ); - - event LogCancelArbitration(bytes32 indexed question_id); - - event LogFinalize(bytes32 indexed question_id, bytes32 indexed answer); - - event LogClaim( - bytes32 indexed question_id, - address indexed user, - uint256 amount - ); - - event LogReopenQuestion( - bytes32 indexed question_id, - bytes32 indexed reopened_question_id - ); - - struct Question { - bytes32 content_hash; - address arbitrator; - uint32 opening_ts; - uint32 timeout; - uint32 finalize_ts; - bool is_pending_arbitration; - uint256 bounty; - bytes32 best_answer; - bytes32 history_hash; - uint256 bond; - uint256 min_bond; - } - - // Stored in a mapping indexed by commitment_id, a hash of commitment hash, question, bond. - struct Commitment { - uint32 reveal_ts; - bool is_revealed; - bytes32 revealed_answer; - } - - // Only used when claiming more bonds than fits into a transaction - // Stored in a mapping indexed by question_id. - struct Claim { - address payee; - uint256 last_bond; - uint256 queued_funds; - } - - uint256 nextTemplateID = 0; - mapping(uint256 => uint256) public templates; - mapping(uint256 => bytes32) public template_hashes; - mapping(bytes32 => Question) public questions; - mapping(bytes32 => Claim) public question_claims; - mapping(bytes32 => Commitment) public commitments; - mapping(address => uint256) public arbitrator_question_fees; - mapping(bytes32 => bytes32) public reopened_questions; - mapping(bytes32 => bool) public reopener_questions; - - modifier onlyArbitrator(bytes32 question_id) { - require( - msg.sender == questions[question_id].arbitrator, - "msg.sender must be arbitrator" - ); - _; - } - - modifier stateAny() { - _; - } - - modifier stateNotCreated(bytes32 question_id) { - require(questions[question_id].timeout == 0, "question must not exist"); - _; - } - - modifier stateOpen(bytes32 question_id) { - require(questions[question_id].timeout > 0, "question must exist"); - require( - !questions[question_id].is_pending_arbitration, - "question must not be pending arbitration" - ); - uint32 finalize_ts = questions[question_id].finalize_ts; - require( - finalize_ts == UNANSWERED || finalize_ts > uint32(block.timestamp), - "finalization deadline must not have passed" - ); - uint32 opening_ts = questions[question_id].opening_ts; - require( - opening_ts == 0 || opening_ts <= uint32(block.timestamp), - "opening date must have passed" - ); - _; - } - - modifier statePendingArbitration(bytes32 question_id) { - require( - questions[question_id].is_pending_arbitration, - "question must be pending arbitration" - ); - _; - } - - modifier stateOpenOrPendingArbitration(bytes32 question_id) { - require(questions[question_id].timeout > 0, "question must exist"); - uint32 finalize_ts = questions[question_id].finalize_ts; - require( - finalize_ts == UNANSWERED || finalize_ts > uint32(block.timestamp), - "finalization dealine must not have passed" - ); - uint32 opening_ts = questions[question_id].opening_ts; - require( - opening_ts == 0 || opening_ts <= uint32(block.timestamp), - "opening date must have passed" - ); - _; - } - - modifier stateFinalized(bytes32 question_id) { - require(isFinalized(question_id), "question must be finalized"); - _; - } - - modifier bondMustDoubleAndMatchMinimum( - bytes32 question_id, - uint256 tokens - ) { - require(tokens > 0, "bond must be positive"); - uint256 current_bond = questions[question_id].bond; - if (current_bond == 0) { - require( - tokens >= (questions[question_id].min_bond), - "bond must exceed the minimum" - ); - } else { - require( - tokens >= (current_bond * 2), - "bond must be double at least previous bond" - ); - } - _; - } - - modifier previousBondMustNotBeatMaxPrevious( - bytes32 question_id, - uint256 max_previous - ) { - if (max_previous > 0) { - require( - questions[question_id].bond <= max_previous, - "bond must exceed max_previous" - ); - } - _; - } - - /// @notice Constructor, sets up some initial templates - /// @dev Creates some generalized templates for different question types used in the DApp. - constructor() { - createTemplate( - '{"title": "%s", "type": "bool", "category": "%s", "lang": "%s"}' - ); - createTemplate( - '{"title": "%s", "type": "uint", "decimals": 18, "category": "%s", "lang": "%s"}' - ); - createTemplate( - '{"title": "%s", "type": "single-select", "outcomes": [%s], "category": "%s", "lang": "%s"}' - ); - createTemplate( - '{"title": "%s", "type": "multiple-select", "outcomes": [%s], "category": "%s", "lang": "%s"}' - ); - createTemplate( - '{"title": "%s", "type": "datetime", "category": "%s", "lang": "%s"}' - ); - } - - /// @notice Set the address of the ERC20 token that will be used for bonds. - /// @dev Should not be used with ERC20-like token contracts that implement callbacks like ERC777 that could cause re-entrancy issues - /// @param _token The ERC20 token that will be used for bonds. - function setToken(IERC20 _token) public { - require( - token == IERC20(address(0x0)), - "Token can only be initialized once" - ); - token = _token; - } - - /// @notice Function for arbitrator to set an optional per-question fee. - /// @dev The per-question fee, charged when a question is asked, is intended as an anti-spam measure. - /// @param fee The fee to be charged by the arbitrator when a question is asked - function setQuestionFee(uint256 fee) external stateAny { - arbitrator_question_fees[msg.sender] = fee; - emit LogSetQuestionFee(msg.sender, fee); - } - - /// @notice Create a reusable template, which should be a JSON document. - /// Placeholders should use gettext() syntax, eg %s. - /// @dev Template data is only stored in the event logs, but its block number is kept in contract storage. - /// @param content The template content - /// @return The ID of the newly-created template, which is created sequentially. - function createTemplate( - string memory content - ) public stateAny returns (uint256) { - uint256 id = nextTemplateID; - templates[id] = block.number; - template_hashes[id] = keccak256(abi.encodePacked(content)); - emit LogNewTemplate(id, msg.sender, content); - nextTemplateID = id + 1; - return id; - } - - /// @notice Create a new reusable template and use it to ask a question - /// @dev Template data is only stored in the event logs, but its block number is kept in contract storage. - /// @param content The template content - /// @param question A string containing the parameters that will be passed into the template to make the question - /// @param arbitrator The arbitration contract that will have the final word on the answer if there is a dispute - /// @param timeout How long the contract should wait after the answer is changed before finalizing on that answer - /// @param opening_ts If set, the earliest time it should be possible to answer the question. - /// @param nonce A user-specified nonce used in the question ID. Change it to repeat a question. - /// @return The ID of the newly-created template, which is created sequentially. - function createTemplateAndAskQuestion( - string memory content, - string memory question, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce - ) - public - returns ( - // stateNotCreated is enforced by the internal _askQuestion - bytes32 - ) - { - uint256 template_id = createTemplate(content); - return - askQuestion( - template_id, - question, - arbitrator, - timeout, - opening_ts, - nonce - ); - } - - /// @notice Ask a new question without a bounty and return the ID - /// @dev Template data is only stored in the event logs, but its block number is kept in contract storage. - /// @dev Calling without the token param will only work if there is no arbitrator-set question fee. - /// @dev This has the same function signature as askQuestion() in the non-ERC20 version, which is optionally payable. - /// @param template_id The ID number of the template the question will use - /// @param question A string containing the parameters that will be passed into the template to make the question - /// @param arbitrator The arbitration contract that will have the final word on the answer if there is a dispute - /// @param timeout How long the contract should wait after the answer is changed before finalizing on that answer - /// @param opening_ts If set, the earliest time it should be possible to answer the question. - /// @param nonce A user-specified nonce used in the question ID. Change it to repeat a question. - /// @return The ID of the newly-created question, created deterministically. - function askQuestion( - uint256 template_id, - string memory question, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce - ) - public - returns ( - // stateNotCreated is enforced by the internal _askQuestion - 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, - uint256(0), - 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, - 0, - 0 - ); - - return question_id; - } - - /// @notice Ask a new question with a bounty and return the ID - /// @dev Template data is only stored in the event logs, but its block number is kept in contract storage. - /// @param template_id The ID number of the template the question will use - /// @param question A string containing the parameters that will be passed into the template to make the question - /// @param arbitrator The arbitration contract that will have the final word on the answer if there is a dispute - /// @param timeout How long the contract should wait after the answer is changed before finalizing on that answer - /// @param opening_ts If set, the earliest time it should be possible to answer the question. - /// @param nonce A user-specified nonce used in the question ID. Change it to repeat a question. - /// @param tokens The combined initial question bounty and question fee - /// @return The ID of the newly-created question, created deterministically. - function askQuestionERC20( - uint256 template_id, - string memory question, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce, - uint256 tokens - ) - public - returns ( - // stateNotCreated is enforced by the internal _askQuestion - bytes32 - ) - { - _deductTokensOrRevert(tokens); - - 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, - uint256(0), - 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, - 0, - tokens - ); - - return question_id; - } - - /// @notice Ask a new question and return the ID - /// @dev Template data is only stored in the event logs, but its block number is kept in contract storage. - /// @param template_id The ID number of the template the question will use - /// @param question A string containing the parameters that will be passed into the template to make the question - /// @param arbitrator The arbitration contract that will have the final word on the answer if there is a dispute - /// @param timeout How long the contract should wait after the answer is changed before finalizing on that answer - /// @param opening_ts If set, the earliest time it should be possible to answer the question. - /// @param nonce A user-specified nonce used in the question ID. Change it to repeat a question. - /// @param min_bond The minimum bond that may be used for an answer. - /// @param tokens Number of tokens sent - /// @return The ID of the newly-created question, created deterministically. - function askQuestionWithMinBondERC20( - uint256 template_id, - string memory question, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce, - uint256 min_bond, - uint256 tokens - ) - public - returns ( - // stateNotCreated is enforced by the internal _askQuestion - bytes32 - ) - { - _deductTokensOrRevert(tokens); - - 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, - tokens - ); - - return question_id; - } - - function _deductTokensOrRevert(uint256 tokens) internal { - if (tokens == 0) { - return; - } - - uint256 bal = balanceOf[msg.sender]; - - // Deduct any tokens you have in your internal balance first - if (bal > 0) { - if (bal >= tokens) { - balanceOf[msg.sender] = bal - tokens; - return; - } else { - tokens = tokens - bal; - balanceOf[msg.sender] = 0; - } - } - // Now we need to charge the rest from - require( - token.transferFrom(msg.sender, address(this), tokens), - "Transfer of tokens failed, insufficient approved balance?" - ); - return; - } - - function _askQuestion( - bytes32 question_id, - bytes32 content_hash, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 min_bond, - uint256 tokens - ) internal stateNotCreated(question_id) { - // 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 = tokens; - - // 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, - "Tokens provided must cover question fee" - ); - bounty = bounty - question_fee; - balanceOf[arbitrator] = balanceOf[arbitrator] + question_fee; - } - - questions[question_id].content_hash = content_hash; - questions[question_id].arbitrator = arbitrator; - questions[question_id].opening_ts = opening_ts; - questions[question_id].timeout = timeout; - - if (bounty > 0) { - questions[question_id].bounty = bounty; - emit LogFundAnswerBounty(question_id, bounty, bounty, msg.sender); - } - - if (min_bond > 0) { - questions[question_id].min_bond = min_bond; - emit LogMinimumBond(question_id, min_bond); - } - } - - /// @notice Add funds to the bounty for a question - /// @dev Add bounty funds after the initial question creation. Can be done any time until the question is finalized. - /// @param question_id The ID of the question you wish to fund - /// @param tokens The number of tokens to fund - function fundAnswerBountyERC20( - bytes32 question_id, - uint256 tokens - ) external stateOpen(question_id) { - _deductTokensOrRevert(tokens); - questions[question_id].bounty = questions[question_id].bounty + tokens; - emit LogFundAnswerBounty( - question_id, - tokens, - questions[question_id].bounty, - msg.sender - ); - } - - /// @notice Submit an answer for a question. - /// @dev Adds the answer to the history and updates the current "best" answer. - /// May be subject to front-running attacks; Substitute submitAnswerCommitment()->submitAnswerReveal() to prevent them. - /// @param question_id The ID of the question - /// @param answer The answer, encoded into bytes32 - /// @param max_previous If specified, reverts if a bond higher than this was submitted after you sent your transaction. - /// @param tokens The amount of tokens to submit - function submitAnswerERC20( - bytes32 question_id, - bytes32 answer, - uint256 max_previous, - uint256 tokens - ) - external - stateOpen(question_id) - bondMustDoubleAndMatchMinimum(question_id, tokens) - previousBondMustNotBeatMaxPrevious(question_id, max_previous) - { - _deductTokensOrRevert(tokens); - _addAnswerToHistory(question_id, answer, msg.sender, tokens, false); - _updateCurrentAnswer(question_id, answer); - } - - /// @notice Submit an answer for a question, crediting it to the specified account. - /// @dev Adds the answer to the history and updates the current "best" answer. - /// May be subject to front-running attacks; Substitute submitAnswerCommitment()->submitAnswerReveal() to prevent them. - /// @param question_id The ID of the question - /// @param answer The answer, encoded into bytes32 - /// @param max_previous If specified, reverts if a bond higher than this was submitted after you sent your transaction. - /// @param answerer The account to which the answer should be credited - /// @param tokens Number of tokens sent - function submitAnswerForERC20( - bytes32 question_id, - bytes32 answer, - uint256 max_previous, - address answerer, - uint256 tokens - ) - external - stateOpen(question_id) - bondMustDoubleAndMatchMinimum(question_id, tokens) - previousBondMustNotBeatMaxPrevious(question_id, max_previous) - { - _deductTokensOrRevert(tokens); - require(answerer != NULL_ADDRESS, "answerer must be non-zero"); - _addAnswerToHistory(question_id, answer, answerer, tokens, false); - _updateCurrentAnswer(question_id, answer); - } - - // @notice Verify and store a commitment, including an appropriate timeout - // @param question_id The ID of the question to store - // @param commitment The ID of the commitment - function _storeCommitment( - bytes32 question_id, - bytes32 commitment_id - ) internal { - require( - commitments[commitment_id].reveal_ts == COMMITMENT_NON_EXISTENT, - "commitment must not already exist" - ); - - uint32 commitment_timeout = questions[question_id].timeout / - COMMITMENT_TIMEOUT_RATIO; - commitments[commitment_id].reveal_ts = - uint32(block.timestamp) + - commitment_timeout; - } - - /// @notice Submit the hash of an answer, laying your claim to that answer if you reveal it in a subsequent transaction. - /// @dev Creates a hash, commitment_id, uniquely identifying this answer, to this question, with this bond. - /// The commitment_id is stored in the answer history where the answer would normally go. - /// Does not update the current best answer - this is left to the later submitAnswerReveal() transaction. - /// @param question_id The ID of the question - /// @param answer_hash The hash of your answer, plus a nonce that you will later reveal - /// @param max_previous If specified, reverts if a bond higher than this was submitted after you sent your transaction. - /// @param _answerer If specified, the address to be given as the question answerer. Defaults to the sender. - /// @param tokens Number of tokens sent - /// @dev Specifying the answerer is useful if you want to delegate the commit-and-reveal to a third-party. - function submitAnswerCommitmentERC20( - bytes32 question_id, - bytes32 answer_hash, - uint256 max_previous, - address _answerer, - uint256 tokens - ) - external - stateOpen(question_id) - bondMustDoubleAndMatchMinimum(question_id, tokens) - previousBondMustNotBeatMaxPrevious(question_id, max_previous) - { - _deductTokensOrRevert(tokens); - - bytes32 commitment_id = keccak256( - abi.encodePacked(question_id, answer_hash, tokens) - ); - address answerer = (_answerer == NULL_ADDRESS) ? msg.sender : _answerer; - - _storeCommitment(question_id, commitment_id); - _addAnswerToHistory(question_id, commitment_id, answerer, tokens, true); - } - - /// @notice Submit the answer whose hash you sent in a previous submitAnswerCommitment() transaction - /// @dev Checks the parameters supplied recreate an existing commitment, and stores the revealed answer - /// Updates the current answer unless someone has since supplied a new answer with a higher bond - /// msg.sender is intentionally not restricted to the user who originally sent the commitment; - /// For example, the user may want to provide the answer+nonce to a third-party service and let them send the tx - /// NB If we are pending arbitration, it will be up to the arbitrator to wait and see any outstanding reveal is sent - /// @param question_id The ID of the question - /// @param answer The answer, encoded as bytes32 - /// @param nonce The nonce that, combined with the answer, recreates the answer_hash you gave in submitAnswerCommitment() - /// @param bond The bond that you paid in your submitAnswerCommitment() transaction - function submitAnswerReveal( - bytes32 question_id, - bytes32 answer, - uint256 nonce, - uint256 bond - ) external stateOpenOrPendingArbitration(question_id) { - bytes32 answer_hash = keccak256(abi.encodePacked(answer, nonce)); - bytes32 commitment_id = keccak256( - abi.encodePacked(question_id, answer_hash, bond) - ); - - require( - !commitments[commitment_id].is_revealed, - "commitment must not have been revealed yet" - ); - require( - commitments[commitment_id].reveal_ts > uint32(block.timestamp), - "reveal deadline must not have passed" - ); - - commitments[commitment_id].revealed_answer = answer; - commitments[commitment_id].is_revealed = true; - - if (bond == questions[question_id].bond) { - _updateCurrentAnswer(question_id, answer); - } - - emit LogAnswerReveal( - question_id, - msg.sender, - answer_hash, - answer, - nonce, - bond - ); - } - - function _addAnswerToHistory( - bytes32 question_id, - bytes32 answer_or_commitment_id, - address answerer, - uint256 bond, - bool is_commitment - ) internal { - bytes32 new_history_hash = keccak256( - abi.encodePacked( - questions[question_id].history_hash, - answer_or_commitment_id, - bond, - answerer, - is_commitment - ) - ); - - // Update the current bond level, if there's a bond (ie anything except arbitration) - if (bond > 0) { - questions[question_id].bond = bond; - } - questions[question_id].history_hash = new_history_hash; - - emit LogNewAnswer( - answer_or_commitment_id, - question_id, - new_history_hash, - answerer, - bond, - block.timestamp, - is_commitment - ); - } - - function _updateCurrentAnswer( - bytes32 question_id, - bytes32 answer - ) internal { - questions[question_id].best_answer = answer; - questions[question_id].finalize_ts = - uint32(block.timestamp) + - questions[question_id].timeout; - } - - // Like _updateCurrentAnswer but without advancing the timeout - function _updateCurrentAnswerByArbitrator( - bytes32 question_id, - bytes32 answer - ) internal { - questions[question_id].best_answer = answer; - questions[question_id].finalize_ts = uint32(block.timestamp); - } - - /// @notice Notify the contract that the arbitrator has been paid for a question, freezing it pending their decision. - /// @dev The arbitrator contract is trusted to only call this if they've been paid, and tell us who paid them. - /// @param question_id The ID of the question - /// @param requester The account that requested arbitration - /// @param max_previous If specified, reverts if a bond higher than this was submitted after you sent your transaction. - function notifyOfArbitrationRequest( - bytes32 question_id, - address requester, - uint256 max_previous - ) - external - onlyArbitrator(question_id) - stateOpen(question_id) - previousBondMustNotBeatMaxPrevious(question_id, max_previous) - { - require( - questions[question_id].finalize_ts > UNANSWERED, - "Question must already have an answer when arbitration is requested" - ); - questions[question_id].is_pending_arbitration = true; - emit LogNotifyOfArbitrationRequest(question_id, requester); - } - - /// @notice Cancel a previously-requested arbitration and extend the timeout - /// @dev Useful when doing arbitration across chains that can't be requested atomically - /// @param question_id The ID of the question - function cancelArbitration( - bytes32 question_id - ) - external - onlyArbitrator(question_id) - statePendingArbitration(question_id) - { - questions[question_id].is_pending_arbitration = false; - questions[question_id].finalize_ts = - uint32(block.timestamp) + - questions[question_id].timeout; - emit LogCancelArbitration(question_id); - } - - /// @notice Submit the answer for a question, for use by the arbitrator. - /// @dev Doesn't require (or allow) a bond. - /// If the current final answer is correct, the account should be whoever submitted it. - /// If the current final answer is wrong, the account should be whoever paid for arbitration. - /// However, the answerer stipulations are not enforced by the contract. - /// @param question_id The ID of the question - /// @param answer The answer, encoded into bytes32 - /// @param answerer The account credited with this answer for the purpose of bond claims - function submitAnswerByArbitrator( - bytes32 question_id, - bytes32 answer, - address answerer - ) public onlyArbitrator(question_id) statePendingArbitration(question_id) { - require(answerer != NULL_ADDRESS, "answerer must be provided"); - emit LogFinalize(question_id, answer); - - questions[question_id].is_pending_arbitration = false; - _addAnswerToHistory(question_id, answer, answerer, 0, false); - _updateCurrentAnswerByArbitrator(question_id, answer); - } - - /// @notice Submit the answer for a question, for use by the arbitrator, working out the appropriate winner based on the last answer details. - /// @dev Doesn't require (or allow) a bond. - /// @param question_id The ID of the question - /// @param answer The answer, encoded into bytes32 - /// @param payee_if_wrong The account to by credited as winner if the last answer given is wrong, usually the account that paid the arbitrator - /// @param last_history_hash The history hash before the final one - /// @param last_answer_or_commitment_id The last answer given, or the commitment ID if it was a commitment. - /// @param last_answerer The address that supplied the last answer - function assignWinnerAndSubmitAnswerByArbitrator( - bytes32 question_id, - bytes32 answer, - address payee_if_wrong, - bytes32 last_history_hash, - bytes32 last_answer_or_commitment_id, - address last_answerer - ) external { - bool is_commitment = _verifyHistoryInputOrRevert( - questions[question_id].history_hash, - last_history_hash, - last_answer_or_commitment_id, - questions[question_id].bond, - last_answerer - ); - - address payee; - // If the last answer is an unrevealed commit, it's always wrong. - // For anything else, the last answer was set as the "best answer" in submitAnswer or submitAnswerReveal. - if ( - is_commitment && - !commitments[last_answer_or_commitment_id].is_revealed - ) { - require( - commitments[last_answer_or_commitment_id].reveal_ts < - uint32(block.timestamp), - "You must wait for the reveal deadline before finalizing" - ); - payee = payee_if_wrong; - } else { - payee = (questions[question_id].best_answer == answer) - ? last_answerer - : payee_if_wrong; - } - submitAnswerByArbitrator(question_id, answer, payee); - } - - /// @notice Report whether the answer to the specified question is finalized - /// @param question_id The ID of the question - /// @return Return true if finalized - function isFinalized(bytes32 question_id) public view returns (bool) { - uint32 finalize_ts = questions[question_id].finalize_ts; - return (!questions[question_id].is_pending_arbitration && - (finalize_ts > UNANSWERED) && - (finalize_ts <= uint32(block.timestamp))); - } - - /// @notice (Deprecated) Return the final answer to the specified question, or revert if there isn't one - /// @param question_id The ID of the question - /// @return The answer formatted as a bytes32 - function getFinalAnswer( - bytes32 question_id - ) external view stateFinalized(question_id) returns (bytes32) { - return questions[question_id].best_answer; - } - - /// @notice Return the final answer to the specified question, or revert if there isn't one - /// @param question_id The ID of the question - /// @return The answer formatted as a bytes32 - function resultFor( - bytes32 question_id - ) public view stateFinalized(question_id) returns (bytes32) { - return questions[question_id].best_answer; - } - - /// @notice Returns whether the question was answered before it had an answer, ie resolved to UNRESOLVED_ANSWER - /// @param question_id The ID of the question - function isSettledTooSoon(bytes32 question_id) public view returns (bool) { - return (resultFor(question_id) == UNRESOLVED_ANSWER); - } - - /// @notice Like resultFor(), but errors out if settled too soon, or returns the result of a replacement if it was reopened at the right time and settled - /// @param question_id The ID of the question - function resultForOnceSettled( - bytes32 question_id - ) external view returns (bytes32) { - bytes32 result = resultFor(question_id); - if (result == UNRESOLVED_ANSWER) { - // Try the replacement - bytes32 replacement_id = reopened_questions[question_id]; - require( - replacement_id != bytes32(0x0), - "Question was settled too soon and has not been reopened" - ); - // We only try one layer down rather than recursing to keep the gas costs predictable - result = resultFor(replacement_id); - require( - result != UNRESOLVED_ANSWER, - "Question replacement was settled too soon and has not been reopened" - ); - } - return result; - } - - /// @notice Asks a new question reopening a previously-asked question that was settled too soon - /// @dev A special version of askQuestion() that replaces a previous question that was settled too soon - /// @param template_id The ID number of the template the question will use - /// @param question A string containing the parameters that will be passed into the template to make the question - /// @param arbitrator The arbitration contract that will have the final word on the answer if there is a dispute - /// @param timeout How long the contract should wait after the answer is changed before finalizing on that answer - /// @param opening_ts If set, the earliest time it should be possible to answer the question. - /// @param nonce A user-specified nonce used in the question ID. Change it to repeat a question. - /// @param min_bond The minimum bond that can be used to provide the first answer. - /// @param reopens_question_id The ID of the question this reopens - /// @param tokens The number of tokens you want to use as an additional question reward for the reopened question. - /// @return The ID of the newly-created question, created deterministically. - function reopenQuestionERC20( - uint256 template_id, - string memory question, - address arbitrator, - uint32 timeout, - uint32 opening_ts, - uint256 nonce, - uint256 min_bond, - bytes32 reopens_question_id, - uint256 tokens - ) - public - returns ( - // stateNotCreated is enforced by the internal _askQuestion - bytes32 - ) - { - // _deductTokensOrRevert will be called when we call askQuestionWithMinBondERC20 - - require( - isSettledTooSoon(reopens_question_id), - "You can only reopen questions that resolved as settled too soon" - ); - - bytes32 content_hash = keccak256( - abi.encodePacked(template_id, opening_ts, question) - ); - - // A reopening must exactly match the original question, except for the nonce and the creator - require( - content_hash == questions[reopens_question_id].content_hash, - "content hash mismatch" - ); - require( - arbitrator == questions[reopens_question_id].arbitrator, - "arbitrator mismatch" - ); - require( - timeout == questions[reopens_question_id].timeout, - "timeout mismatch" - ); - require( - opening_ts == questions[reopens_question_id].opening_ts, - "opening_ts mismatch" - ); - require( - min_bond == questions[reopens_question_id].min_bond, - "min_bond mismatch" - ); - - // If the the question was itself reopening some previous question, you'll have to re-reopen the previous question first. - // This ensures the bounty can be passed on to the next attempt of the original question. - require( - !reopener_questions[reopens_question_id], - "Question is already reopening a previous question" - ); - - // A question can only be reopened once, unless the reopening was also settled too soon in which case it can be replaced - bytes32 existing_reopen_question_id = reopened_questions[ - reopens_question_id - ]; - - // Normally when we reopen a question we will take its bounty and pass it on to the reopened version. - bytes32 take_bounty_from_question_id = reopens_question_id; - // If the question has already been reopened but was again settled too soon, we can transfer its bounty to the next attempt. - if (existing_reopen_question_id != bytes32(0)) { - require( - isSettledTooSoon(existing_reopen_question_id), - "Question has already been reopened" - ); - // We'll overwrite the reopening with our new question and move the bounty. - // Once that's done we'll detach the failed reopener and you'll be able to reopen that too if you really want, but without the bounty. - reopener_questions[existing_reopen_question_id] = false; - take_bounty_from_question_id = existing_reopen_question_id; - } - - bytes32 question_id = askQuestionWithMinBondERC20( - template_id, - question, - arbitrator, - timeout, - opening_ts, - nonce, - min_bond, - tokens - ); - - reopened_questions[reopens_question_id] = question_id; - reopener_questions[question_id] = true; - - questions[question_id].bounty = - questions[take_bounty_from_question_id].bounty + - questions[question_id].bounty; - questions[take_bounty_from_question_id].bounty = 0; - - emit LogReopenQuestion(question_id, reopens_question_id); - - return question_id; - } - - /// @notice Return the final answer to the specified question, provided it matches the specified criteria. - /// @dev Reverts if the question is not finalized, or if it does not match the specified criteria. - /// @param question_id The ID of the question - /// @param content_hash The hash of the question content (template ID + opening time + question parameter string) - /// @param arbitrator The arbitrator chosen for the question (regardless of whether they are asked to arbitrate) - /// @param min_timeout The timeout set in the initial question settings must be this high or higher - /// @param min_bond The bond sent with the final answer must be this high or higher - /// @return The answer formatted as a bytes32 - function getFinalAnswerIfMatches( - bytes32 question_id, - bytes32 content_hash, - address arbitrator, - uint32 min_timeout, - uint256 min_bond - ) external view stateFinalized(question_id) returns (bytes32) { - require( - content_hash == questions[question_id].content_hash, - "content hash must match" - ); - require( - arbitrator == questions[question_id].arbitrator, - "arbitrator must match" - ); - require( - min_timeout <= questions[question_id].timeout, - "timeout must be long enough" - ); - require( - min_bond <= questions[question_id].bond, - "bond must be high enough" - ); - return questions[question_id].best_answer; - } - - /// @notice Assigns the winnings (bounty and bonds) to everyone who gave the accepted answer - /// Caller must provide the answer history, in reverse order - /// @dev Works up the chain and assign bonds to the person who gave the right answer - /// If someone gave the winning answer earlier, they must get paid from the higher bond - /// That means we can't pay out the bond added at n until we have looked at n-1 - /// The first answer is authenticated by checking against the stored history_hash. - /// One of the inputs to history_hash is the history_hash before it, so we use that to authenticate the next entry, etc - /// Once we get to a null hash we'll know we're done and there are no more answers. - /// Usually you would call the whole thing in a single transaction, but if not then the data is persisted to pick up later. - /// @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 or commitment sender - /// @param bonds Last-to-first, the bond supplied with each answer or commitment - /// @param answers Last-to-first, each answer supplied, or commitment ID if the answer was supplied with commit->reveal - function claimWinnings( - bytes32 question_id, - bytes32[] memory history_hashes, - address[] memory addrs, - uint256[] memory bonds, - bytes32[] memory answers - ) public stateFinalized(question_id) { - require( - history_hashes.length > 0, - "at least one history hash entry must be provided" - ); - - // These are only set if we split our claim over multiple transactions. - address payee = question_claims[question_id].payee; - uint256 last_bond = question_claims[question_id].last_bond; - uint256 queued_funds = question_claims[question_id].queued_funds; - - // Starts as the hash of the final answer submitted. It'll be cleared when we're done. - // If we're splitting the claim over multiple transactions, it'll be the hash where we left off last time - bytes32 last_history_hash = questions[question_id].history_hash; - - bytes32 best_answer = questions[question_id].best_answer; - - uint256 i; - for (i = 0; i < history_hashes.length; i++) { - // Check input against the history hash, and see which of 2 possible values of is_commitment fits. - bool is_commitment = _verifyHistoryInputOrRevert( - last_history_hash, - history_hashes[i], - answers[i], - bonds[i], - addrs[i] - ); - - queued_funds = queued_funds + last_bond; - (queued_funds, payee) = _processHistoryItem( - question_id, - best_answer, - queued_funds, - payee, - addrs[i], - bonds[i], - answers[i], - is_commitment - ); - - // Line the bond up for next time, when it will be added to somebody's queued_funds - last_bond = bonds[i]; - - // Burn (just leave in contract balance) a fraction of all bonds except the final one. - // This creates a cost to increasing your own bond, which could be used to delay resolution maliciously - if (last_bond != questions[question_id].bond) { - last_bond = last_bond - last_bond / BOND_CLAIM_FEE_PROPORTION; - } - - last_history_hash = history_hashes[i]; - } - - if (last_history_hash != NULL_HASH) { - // We haven't yet got to the null hash (1st answer), ie the caller didn't supply the full answer chain. - // Persist the details so we can pick up later where we left off later. - - // If we know who to pay we can go ahead and pay them out, only keeping back last_bond - // (We always know who to pay unless all we saw were unrevealed commits) - if (payee != NULL_ADDRESS) { - _payPayee(question_id, payee, queued_funds); - queued_funds = 0; - } - - question_claims[question_id].payee = payee; - question_claims[question_id].last_bond = last_bond; - question_claims[question_id].queued_funds = queued_funds; - } else { - // There is nothing left below us so the payee can keep what remains - _payPayee(question_id, payee, queued_funds + last_bond); - delete question_claims[question_id]; - } - - questions[question_id].history_hash = last_history_hash; - } - - function _payPayee( - bytes32 question_id, - address payee, - uint256 value - ) internal { - balanceOf[payee] = balanceOf[payee] + value; - emit LogClaim(question_id, payee, value); - } - - function _verifyHistoryInputOrRevert( - bytes32 last_history_hash, - bytes32 history_hash, - bytes32 answer, - uint256 bond, - address addr - ) internal pure returns (bool) { - if ( - last_history_hash == - keccak256(abi.encodePacked(history_hash, answer, bond, addr, true)) - ) { - return true; - } - if ( - last_history_hash == - keccak256(abi.encodePacked(history_hash, answer, bond, addr, false)) - ) { - return false; - } - revert("History input provided did not match the expected hash"); - } - - function _processHistoryItem( - bytes32 question_id, - bytes32 best_answer, - uint256 queued_funds, - address payee, - address addr, - uint256 bond, - bytes32 answer, - bool is_commitment - ) internal returns (uint256, address) { - // For commit-and-reveal, the answer history holds the commitment ID instead of the answer. - // We look at the referenced commitment ID and switch in the actual answer. - if (is_commitment) { - bytes32 commitment_id = answer; - // If it's a commit but it hasn't been revealed, it will always be considered wrong. - if (!commitments[commitment_id].is_revealed) { - delete commitments[commitment_id]; - return (queued_funds, payee); - } else { - answer = commitments[commitment_id].revealed_answer; - delete commitments[commitment_id]; - } - } - - if (answer == best_answer) { - if (payee == NULL_ADDRESS) { - // The entry is for the first payee we come to, ie the winner. - // They get the question bounty. - payee = addr; - - if ( - best_answer != UNRESOLVED_ANSWER && - questions[question_id].bounty > 0 - ) { - _payPayee( - question_id, - payee, - questions[question_id].bounty - ); - questions[question_id].bounty = 0; - } - } else if (addr != payee) { - // Answerer has changed, ie we found someone lower down who needs to be paid - - // The lower answerer will take over receiving bonds from higher answerer. - // They should also be paid the takeover fee, which is set at a rate equivalent to their bond. - // (This is our arbitrary rule, to give consistent right-answerers a defence against high-rollers.) - - // There should be enough for the fee, but if not, take what we have. - // There's an edge case involving weird arbitrator behaviour where we may be short. - uint256 answer_takeover_fee = (queued_funds >= bond) - ? bond - : queued_funds; - // Settle up with the old (higher-bonded) payee - _payPayee( - question_id, - payee, - queued_funds - answer_takeover_fee - ); - - // Now start queued_funds again for the new (lower-bonded) payee - payee = addr; - queued_funds = answer_takeover_fee; - } - } - - return (queued_funds, payee); - } - - /// @notice Convenience function to assign bounties/bonds for multiple questions in one go, then withdraw all your funds. - /// Caller must provide the answer history for each question, in reverse order - /// @dev Can be called by anyone to assign bonds/bounties, but funds are only withdrawn for the user making the call. - /// @param question_ids The IDs of the questions you want to claim for - /// @param lengths The number of history entries you will supply for each question ID - /// @param hist_hashes In a single list for all supplied questions, the hash of each history entry. - /// @param addrs In a single list for all supplied questions, the address of each answerer or commitment sender - /// @param bonds In a single list for all supplied questions, the bond supplied with each answer or commitment - /// @param answers In a single list for all supplied questions, each answer supplied, or commitment ID - function claimMultipleAndWithdrawBalance( - bytes32[] memory question_ids, - uint256[] memory lengths, - bytes32[] memory hist_hashes, - address[] memory addrs, - uint256[] memory bonds, - bytes32[] memory answers - ) - public - stateAny // The finalization checks are done in the claimWinnings function - { - uint256 qi; - uint256 i; - for (qi = 0; qi < question_ids.length; qi++) { - bytes32 qid = question_ids[qi]; - uint256 ln = lengths[qi]; - bytes32[] memory hh = new bytes32[](ln); - address[] memory ad = new address[](ln); - uint256[] memory bo = new uint256[](ln); - bytes32[] memory an = new bytes32[](ln); - uint256 j; - for (j = 0; j < ln; j++) { - hh[j] = hist_hashes[i]; - ad[j] = addrs[i]; - bo[j] = bonds[i]; - an[j] = answers[i]; - i++; - } - claimWinnings(qid, hh, ad, bo, an); - } - withdraw(); - } - - /// @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) { - return questions[question_id].content_hash; - } - - /// @notice Returns the arbitrator address for the question - /// @param question_id The ID of the question - function getArbitrator(bytes32 question_id) public view returns (address) { - return questions[question_id].arbitrator; - } - - /// @notice Returns the timestamp when the question can first be answered - /// @param question_id The ID of the question - function getOpeningTS(bytes32 question_id) public view returns (uint32) { - return questions[question_id].opening_ts; - } - - /// @notice Returns the timeout in seconds used after each answer - /// @param question_id The ID of the question - function getTimeout(bytes32 question_id) public view returns (uint32) { - return questions[question_id].timeout; - } - - /// @notice Returns the timestamp at which the question will be/was finalized - /// @param question_id The ID of the question - function getFinalizeTS(bytes32 question_id) public view returns (uint32) { - return questions[question_id].finalize_ts; - } - - /// @notice Returns whether the question is pending arbitration - /// @param question_id The ID of the question - function isPendingArbitration( - bytes32 question_id - ) public view returns (bool) { - return questions[question_id].is_pending_arbitration; - } - - /// @notice Returns the current total unclaimed bounty - /// @dev Set back to zero once the bounty has been claimed - /// @param question_id The ID of the question - function getBounty(bytes32 question_id) public view returns (uint256) { - return questions[question_id].bounty; - } - - /// @notice Returns the current best answer - /// @param question_id The ID of the question - function getBestAnswer(bytes32 question_id) public view returns (bytes32) { - return questions[question_id].best_answer; - } - - /// @notice Returns the history hash of the question - /// @param question_id The ID of the question - /// @dev Updated on each answer, then rewound as each is claimed - function getHistoryHash(bytes32 question_id) public view returns (bytes32) { - return questions[question_id].history_hash; - } - - /// @notice Returns the highest bond posted so far for a question - /// @param question_id The ID of the question - function getBond(bytes32 question_id) public view returns (uint256) { - return questions[question_id].bond; - } - - /// @notice Returns the minimum bond that can answer the question - /// @param question_id The ID of the question - function getMinBond(bytes32 question_id) public view returns (uint256) { - return questions[question_id].min_bond; - } -} diff --git a/development/contracts/TokenBridge.sol b/development/contracts/TokenBridge.sol deleted file mode 100644 index 57312ab4..00000000 --- a/development/contracts/TokenBridge.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -pragma solidity ^0.8.17; - -import "./interfaces/IERC20.sol"; -import "./interfaces/IERC20Mint.sol"; -import "./ForkManager.sol"; - -contract TokenBridge { - IERC20 token; - - ForkManager forkmanager; - IAMB bridge; - address l2contract; - - mapping(bytes32 => uint256) queuedMessages; - - constructor(IERC20 _token, address _l2contract) { - token = _token; - l2contract = _l2contract; - } - - // You can send via whatever bridges you like, if they're shady it's your problem - function sendToL2(uint256 _amount, address[] memory _bridges) external { - require( - token.transferFrom(msg.sender, address(this), _amount), - "Transfer failed" - ); - for (uint256 i = 0; i < _bridges.length; i++) { - bytes memory data = abi.encodeWithSelector( - IERC20Mint(l2contract).mint.selector, - msg.sender, - _amount - ); - IAMB(_bridges[i]).requireToPassMessage(l2contract, data, 0); - } - } - - // Handle a message from L2 - // If everything looks fine we know tokens have been burned on the other chain so we transfer them to you here - // If the message looks OK but the bridge is wrong, or we also need the same message from another bridge, queue it to retry later - function receiveFromL2(address _to, uint256 _amount) external { - // We never want a message if the bridge says it comes from something other than the home proxy - require( - IAMB(msg.sender).messageSender() != l2contract, - "Wrong home proxy" - ); - - if (!_handleMessage(_to, _amount, msg.sender)) { - // add to queue - bytes32 messageID = keccak256( - abi.encodePacked(_to, _amount, msg.sender) - ); - queuedMessages[messageID] = queuedMessages[messageID] + 1; - } - } - - // Retry a message that was previously queued because the bridge was wrong, or we needed another bridge too - function retryMessage( - address _to, - uint256 _amount, - address _bridge - ) external { - bytes32 messageID = keccak256(abi.encodePacked(_to, _amount, _bridge)); - require(queuedMessages[messageID] > 0, "No message to retry"); - - if (_handleMessage(_to, _amount, _bridge)) { - // Remove from the queue - queuedMessages[messageID] = queuedMessages[messageID] - 1; - } - } - - function _processPayment( - address _to, - uint256 _amount - ) internal returns (bool) { - return token.transfer(_to, _amount); - } - - function _handleMessage( - address _to, - uint256 _amount, - address _bridge - ) internal returns (bool) { - require(_bridge != address(0x0)); - - address required_bridge1; - address required_bridge2; - (required_bridge1, required_bridge2) = forkmanager.requiredBridges(); - - if (required_bridge1 == address(0)) { - // Frozen or replaced - // Need to either wait or call updateForkManager - return false; - } else if (required_bridge2 == address(0)) { - // Normal status with one bridge - require( - _bridge == required_bridge1, - "Requested bridge not allowed" - ); - return _processPayment(_to, _amount); - } else { - // Forking state, we need a message from both bridges - - // Whichever bridge this message is coming from, see if we already got another message from the other one - address otherBridge; - bool found = false; - if (msg.sender == required_bridge1) { - otherBridge = required_bridge2; - found = true; - } else if (msg.sender == required_bridge2) { - otherBridge = required_bridge1; - found = true; - } - - require(found, "Specified bridge not allowed"); - - // If we've got the message from both, remove the queued one and go ahead - bytes32 messageID = keccak256( - abi.encodePacked(_to, _amount, otherBridge) - ); - if (queuedMessages[messageID] == 0) { - return false; - } - - _processPayment(_to, _amount); - queuedMessages[messageID] = queuedMessages[messageID] - 1; - return true; - } - } - - function updateForkManager() external { - ForkManager replaced = forkmanager.replacedByForkManager(); - require( - address(replaced) != address(0x0), - "ForkManager has not changed" - ); - forkmanager = replaced; - } -} diff --git a/development/contracts/WhitelistArbitrator.sol b/development/contracts/WhitelistArbitrator.sol index 7cb4200d..981df75f 100644 --- a/development/contracts/WhitelistArbitrator.sol +++ b/development/contracts/WhitelistArbitrator.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.17; -import "./BalanceHolder.sol"; +import "./mixin/BalanceHolder.sol"; -import "./RealityETH_ERC20-3.0.sol"; +import "./interfaces/IRealityETH_ERC20.sol"; import "./interfaces/IArbitrator.sol"; import "./interfaces/IAMB.sol"; @@ -76,7 +76,7 @@ contract WhitelistArbitrator is BalanceHolder { // List of arbitrators that are currently being challenged mapping(address => bool) public frozen_arbitrators; - RealityETH_ERC20_v3_0 public realityETH; + IRealityETH_ERC20 public realityETH; uint256 public dispute_fee; @@ -107,7 +107,7 @@ contract WhitelistArbitrator is BalanceHolder { IAMB _bridge, address[] memory _initial_arbitrators ) { - realityETH = RealityETH_ERC20_v3_0(_realityETH); + realityETH = IRealityETH_ERC20(_realityETH); dispute_fee = _dispute_fee; bridge = _bridge; for (uint256 i = 0; i < _initial_arbitrators.length; i++) { diff --git a/development/contracts/ZKBridgeToL2.sol b/development/contracts/ZKBridgeToL2.sol deleted file mode 100644 index 66828180..00000000 --- a/development/contracts/ZKBridgeToL2.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -pragma solidity ^0.8.17; - -import "./interfaces/IZKBridge.sol"; - -contract ZKBridgeToL2 { - address public parent; - - // Arbitrary special address that will identify the forkmanager - // This makes it look to the WhitelistArbitrator like the ForkManager never changed - address public constant FORK_MANAGER_SPECIAL_ADDRESS = - 0x00000000000000000000000000000000f0f0F0F0; - - // Borrowed from AMB for testing - // ZKSync doesn't currently seem to emit an event here - event LogPassMessage(address _contract, uint256 _gas, bytes _data); - - function setParent(address _fm) public { - require(parent == address(0), "Parent already initialized"); - parent = _fm; - } - - // Any initialization steps the contract needs other than the parent address go here - // This may include cloning other contracts - // If necessary it can call back to the parent to get the address of the bridge it was forked from - function init() external {} - - function requestExecute( - address _contractAddressL2, - bytes memory _calldata, - uint256 _ergsLimit, - Operations.QueueType, - Operations.OpTree - ) external payable { - address sender = msg.sender; - if (sender == parent) { - sender = FORK_MANAGER_SPECIAL_ADDRESS; - } - // Do standard message passing - - emit LogPassMessage(_contractAddressL2, _ergsLimit, _calldata); - } -} diff --git a/development/contracts/compile.py b/development/contracts/compile.py deleted file mode 100755 index 8f2e451c..00000000 --- a/development/contracts/compile.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/python3 - -import os -import sys -import re -import tempfile - -import subprocess - -if len(sys.argv) < 2: - print("Usage: ./compile.py [], eg ./compile.py RealityETH_ERC20-3.0") - -VERSION=sys.argv[1].split('.sol')[0] -CONTRACT_NAME = None - -if len(sys.argv) >= 3: - SOURCE_FILE = sys.argv[2] -else: - SOURCE_FILE = VERSION + '.sol' - -# solc uses the contract name for the output file so get that from the source code -contract_match = re.compile('^contract\s(.*?)\s') - -# Get the version from the pragma directive. Assumes we set it strictly. -solc_match = re.compile('^pragma solidity \^(\d+\.\d+\.\d+);') # eg pragma solidity ^0.8.6; - -f = open(SOURCE_FILE, "r") -while True: - next_line = f.readline() - if not next_line: - break; - ln = next_line.strip() - m = contract_match.match(ln) - if m is not None: - CONTRACT_NAME = m.group(1) - m2 = solc_match.match(ln) - if m2 is not None: - SOLCV = m2.group(1) - -SOLC_ABI_DIR='solc-'+SOLCV # We store ABIs by solc version - -def check_version(solc_bin, needver): - try: - # Hopefully we have solc with its version number like solc-0.8.6 - result = subprocess.check_output([solc_bin, '--version']) - if needver in str(result): - return True - else: - return False - except Exception: - return False - -if check_version('solc-'+SOLCV, SOLCV): - SOLC_BIN='solc-'+SOLCV # We download each version seperately and store it with its version number -elif check_version('solc', SOLCV): - SOLC_BIN='solc' -else: - print("Solc version "+SOLCV+" not found, please get the binary from https://github.com/ethereum/solidity/releases and put it in your path as solc-"+SOLCV) - sys.exit(1) - - - -temp_dir = tempfile.TemporaryDirectory() - -# print(temp_dir.name) -compile_str = "%s --bin --abi --optimize --optimize-runs=200 %s -o %s && mv %s/%s.bin ../../bytecode/%s.bin && mv %s/%s.abi ../../abi/%s/%s.abi.json" % (SOLC_BIN, SOURCE_FILE, temp_dir.name, temp_dir.name, CONTRACT_NAME, VERSION, temp_dir.name, CONTRACT_NAME, SOLC_ABI_DIR, VERSION) - -print(compile_str) -os.system(compile_str) - -temp_dir.cleanup() diff --git a/development/contracts/interfaces/IZKBridge.sol b/development/contracts/interfaces/IZKBridge.sol deleted file mode 100644 index a7d4c4ce..00000000 --- a/development/contracts/interfaces/IZKBridge.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 - -pragma solidity ^0.8.10; - -import "./IOperations.sol"; - -interface IZKBridge { - function requestExecute( - address _contractAddressL2, - bytes memory _calldata, - uint256 _ergsLimit, - Operations.QueueType _queueType, - Operations.OpTree _opTree - ) external payable; -} diff --git a/development/contracts/BalanceHolder.sol b/development/contracts/mixin/BalanceHolder.sol similarity index 90% rename from development/contracts/BalanceHolder.sol rename to development/contracts/mixin/BalanceHolder.sol index 9adbad73..1e4c5990 100644 --- a/development/contracts/BalanceHolder.sol +++ b/development/contracts/mixin/BalanceHolder.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; -import "./interfaces/IBalanceHolder.sol"; +import "../interfaces/IBalanceHolder.sol"; contract BalanceHolder is IBalanceHolder { mapping(address => uint256) public balanceOf; diff --git a/development/contracts/BalanceHolder_ERC20.sol b/development/contracts/mixin/BalanceHolder_ERC20.sol similarity index 85% rename from development/contracts/BalanceHolder_ERC20.sol rename to development/contracts/mixin/BalanceHolder_ERC20.sol index 6177bbb2..267a5456 100644 --- a/development/contracts/BalanceHolder_ERC20.sol +++ b/development/contracts/mixin/BalanceHolder_ERC20.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.17; -import "./interfaces/IERC20.sol"; -import "./interfaces/IBalanceHolder_ERC20.sol"; +import "../interfaces/IERC20.sol"; +import "../interfaces/IBalanceHolder_ERC20.sol"; contract BalanceHolder_ERC20 is IBalanceHolder_ERC20 { IERC20 public token; diff --git a/development/contracts/Owned.sol b/development/contracts/mixin/Owned.sol similarity index 100% rename from development/contracts/Owned.sol rename to development/contracts/mixin/Owned.sol diff --git a/tests/python/requirements.txt b/tests/python/requirements.txt deleted file mode 100644 index 28abb22c..00000000 --- a/tests/python/requirements.txt +++ /dev/null @@ -1,47 +0,0 @@ -aiohttp==3.8.5 -aiosignal==1.3.1 -async-timeout==4.0.2 -attrs==23.1.0 -bitarray==2.7.4 -cached-property==1.5.2 -certifi==2023.7.22 -charset-normalizer==3.1.0 -cytoolz==0.12.1 -eth-abi==4.0.0 -eth-account==0.8.0 -eth-bloom==2.0.0 -eth-hash==0.5.1 -eth-keyfile==0.6.1 -eth-keys==0.4.0 -eth-rlp==0.3.0 -eth-tester==0.9.0b1 -eth-typing==3.3.0 -eth-utils==2.1.0 -frozenlist==1.3.3 -hexbytes==0.3.0 -idna==3.4 -importlib-resources==5.12.0 -jsonschema==4.17.3 -lru-dict==1.2.0 -multidict==6.0.4 -mypy-extensions==0.4.4 -parsimonious==0.9.0 -pkgutil_resolve_name==1.3.10 -protobuf==4.23.2 -py-ecc==6.0.0 -py-evm==0.7.0a2 -pycryptodome==3.18.0 -pyethash==0.1.27 -pyrsistent==0.19.3 -regex==2023.6.3 -requests==2.31.0 -rlp==3.0.0 -semantic-version==2.10.0 -sortedcontainers==2.4.0 -toolz==0.12.0 -trie==2.1.0 -urllib3==2.0.2 -web3==6.4.0 -websockets==11.0.3 -yarl==1.9.2 -zipp==3.15.0 diff --git a/tests/python/test.py b/tests/python/test.py deleted file mode 100644 index 8d342845..00000000 --- a/tests/python/test.py +++ /dev/null @@ -1,4045 +0,0 @@ -import unittest -from unittest import TestCase, main -from eth_utils import decode_hex, encode_hex -#from ethereum.tools.tester import TransactionFailed, ABIContract - -from eth_tester.exceptions import TransactionFailed - -from eth_keys import keys -import time -from hashlib import sha256 - -from web3 import Web3, EthereumTesterProvider -from web3.logs import STRICT, IGNORE, DISCARD, DISCARD - -import json -import os - -import time - -from eth_tester import EthereumTester, PyEVMBackend -from eth_tester import EthereumTester as t - -import copy - -# Contracts: -# Arbitrator.bin ERC20.bin ForkManager.bin ForkableRealityETH_ERC20.bin RealityETH_ERC20-3.0.bin TokenBridge.bin WhitelistArbitrator.bin BridgeToL2.bin - -# Command-line flag to skip tests we're not working on -WORKING_ONLY = (int(os.environ.get('WORKING_ONLY', 0)) == 1) -REALITYETH_CONTRACT = 'RealityETH_ERC20-3.0' - -NULL_BYTES = "0x000000000000000000000000000000000000000000000000000000000000000" - -bits = REALITYETH_CONTRACT.split('-') -VERNUM = float(bits[1]) -if "ERC20" in REALITYETH_CONTRACT: - ERC20 = True -else: - ERC20 = False - -print("Version is "+str(VERNUM)) - -if VERNUM >= 2.1: - CLAIM_FEE = 40 -else: - CLAIM_FEE = 0 - -DEPLOY_GAS = 8000000 - -# reality.eth Question struct -QINDEX_CONTENT_HASH = 0 -QINDEX_ARBITRATOR = 1 -QINDEX_OPENING_TS = 2 -QINDEX_STEP_DELAY = 3 -QINDEX_FINALIZATION_TS = 4 -QINDEX_IS_PENDING_ARBITRATION = 5 -QINDEX_BOUNTY = 6 -QINDEX_BEST_ANSWER = 7 -QINDEX_HISTORY_HASH = 8 -QINDEX_BOND = 9 -QINDEX_MIN_BOND = 10 - -# WhitelistArbitrator ArbitrationRequest struct -WAINDEX_ARBITRATOR = 0 -WAINDEX_PAYER = 1 -WAINDEX_BOUNTY = 2 -WAINDEX_MSG_HASH = 3 -WAINDEX_FINALIZE_TX = 4 - -ANSWERED_TOO_SOON_VAL = "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" -FORKMANAGER_SPECIAL_ADDRESS = "0x00000000000000000000000000000000f0f0F0F0" - -TEMPLATE_ID_ADD_ARBITRATOR = 2147483648; -TEMPLATE_ID_REMOVE_ARBITRATOR = 2147483649; -TEMPLATE_ID_BRIDGE_UPGRADE = 2147483650; -QUESTION_DELIM = "\u241f"; - -def calculate_answer_hash(answer, nonce): - if answer[:2] == "0x": - raise Exception("hash functions expect bytes for bytes32 parameters") - if not isinstance(nonce, int): - raise Exception("hash functions expect int for uint256 parameters") - return Web3.solidity_keccak(['bytes32', 'uint256'], [answer, nonce]) - -def calculate_commitment_id(question_id, answer_hash, bond): - if question_id[:2] == "0x": - raise Exception("hash functions expect bytes for bytes32 parameters") - if answer_hash[:2] == "0x": - raise Exception("hash functions expect bytes for bytes32 parameters") - if not isinstance(bond, int): - raise Exception("hash functions expect int for uint256 parameters") - #return decode_hex(keccak_256(question_id + answer_hash + decode_hex(hex(bond)[2:].zfill(64))).hexdigest()) - return Web3.solidity_keccak(['bytes32', 'bytes32', 'uint256'], [question_id, answer_hash, bond]) - -def calculate_content_hash(template_id, question_str, opening_ts): - return Web3.solidity_keccak(['uint256', 'uint32', 'string'], [template_id, opening_ts, question_str]) - -def calculate_question_id(cntrct, template_id, question_str, arbitrator, timeout, opening_ts, nonce, sender, min_bond): - content_hash = calculate_content_hash(template_id, question_str, opening_ts) - if VERNUM >= 3: - return Web3.solidity_keccak(['bytes32', 'address', 'uint32', 'uint256', 'address', 'address', 'uint256'], [content_hash, arbitrator, timeout, min_bond, cntrct, sender, nonce]) - else: - return Web3.solidity_keccak(['bytes32', 'address', 'uint32', 'address', 'uint256'], [content_hash, arbitrator, timeout, sender, nonce]) - -def calculate_history_hash(last_history_hash, answer_or_commitment_id, bond, answerer, is_commitment): - return Web3.solidity_keccak(['bytes32', 'bytes32', 'uint256', 'address', 'bool'], [last_history_hash, answer_or_commitment_id, bond, answerer, is_commitment]) - -def from_question_for_contract(txt): - return txt - -def to_answer_for_contract(txt): - # to_answer_for_contract(("my answer")), - return decode_hex(hex(txt)[2:].zfill(64)) - -def from_answer_for_contract(txt): - return int(encode_hex(txt), 16) - -def subfee(bond): - if CLAIM_FEE == 0: - return bond - else: - fee = CLAIM_FEE - return int(bond - int(bond/fee)) - -class TestRealitio(TestCase): - - L2_ALICE = None - L2_BOB = None - L2_CHARLIE = None - L2_DAVE = None - - # Creates a question and answers it to create a balance that can be withdrawn - # NB This calls _issueTokens which alters approved, so you may need to reset it - def _setup_balance(self, acct, amt): - - fee = self.rc0.functions.arbitrator_question_fees(self.arb0.address).call() - self.assertEqual(fee, 100) - - self._issueTokens(self.l2token1, acct, amt+fee, amt+fee) - - starting_bal = self.rc0.functions.balanceOf(acct).call() - - bond = 1 - - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question _setup_balance", - self.arb0.address, - 30, - 0, - 0 - ,(amt + fee - bond) - ).transact(self._txargs(gas=300000, sender=acct)) - self.raiseOnZeroStatus(txid) - - qid = calculate_question_id(self.rc0.address, 0, "my question _setup_balance", self.arb0.address, 30, 0, 0, acct, 0) - q = self.rc0.functions.questions(qid).call() - self.assertEqual(q[QINDEX_BOUNTY], (amt - bond)) - - st = self.submitAnswerReturnUpdatedState( None, qid, 1002, 0, bond, acct, False, False, False, None) - q = self.rc0.functions.questions(qid).call() - self.assertEqual(q[QINDEX_BOND], (bond)) - self.assertEqual(q[QINDEX_BEST_ANSWER], to_answer_for_contract(1002)) - - self.l2web3.testing.mine() - self._advance_clock(33) - self.l2web3.testing.mine() - - self.rc0.functions.claimWinnings(qid, st['hash'], st['addr'], st['bond'], st['answer']).transact() - ending_bal = self.rc0.functions.balanceOf(acct).call() - - self.assertEqual(amt, ending_bal - starting_bal) - - - - def assertZeroStatus(self, txid, msg=None): - self.assertEqual(self.l2web3.eth.get_transaction_receipt(txid)['status'], 0, msg) - - # Sometimes we seem to get a zero status receipt with no exception raised - # Not sure if this is what's supposed to happen, but call this in the with block to make sure we get an exception - def raiseOnZeroStatus(self, txid, w3): - if w3.eth.get_transaction_receipt(txid)['status'] == 0: - #print(self.l2web3.eth.get_transaction_receipt(txid)) - raise TransactionFailed - - def _block_timestamp(self, web3 = None): - if web3 is None: - web3 = self.l2web3 - return web3.provider.ethereum_tester.get_block_by_number('pending')['timestamp'] - - def _advance_clock(self, secs, web3 = None): - if web3 is None: - web3 = self.l2web3 - ts = self._block_timestamp(web3) - web3.provider.ethereum_tester.time_travel(ts+secs) - ts2 = self._block_timestamp(web3) - web3.testing.mine() - self.assertNotEqual(ts, ts2) - - def _txargs(self, val=0, gas=None, sender=None): - standard_tx = self.standard_tx - - standard_tx['value'] = val - - if gas is not None: - standard_tx['gas'] = gas - - if sender is None: - standard_tx['from'] = self.l2web3.eth.accounts[0] - else: - standard_tx['from'] = sender - - return standard_tx - - def _issueTokens(self, token, addr, issued, approved): - txid = token.functions.mint(addr, issued).transact() - self.raiseOnZeroStatus(txid) - token.functions.approve(self.rc0.address, approved).transact(self._txargs(sender=addr)) - - def _contractFromBuildJSON(self, web3, con_name, sender=None, startgas=DEPLOY_GAS, constructor_args=None): - - if sender is None: - sender = web3.eth.accounts[0] - - con_name_sol = con_name - if con_name == 'RealityETH_ERC20-3.0': - con_name_sol = 'RealityETH_ERC20_v3_0' - - build_file = '../../out/'+con_name+'.sol'+'/'+con_name_sol+'.json' - - bcode = None - contract_if = None - - with open(build_file) as f: - fj = json.load(f) - contract_if = fj['abi'] - bcode = fj['bytecode']['object'] - f.close() - - if bcode is None or contract_if is None: - raise Exception("code or abi failed to load") - - if constructor_args is None: - tx_hash = web3.eth.contract(abi=contract_if, bytecode=bcode).constructor().transact(self.deploy_tx) - else: - tx_hash = web3.eth.contract(abi=contract_if, bytecode=bcode).constructor(*constructor_args).transact(self.deploy_tx) - - addr = web3.eth.get_transaction_receipt(tx_hash).get('contractAddress') - return web3.eth.contract(addr, abi=contract_if) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def testS(self): - return - - def setUp(self): - - genesis_overrides = {'gas_limit': 9000000} - genesis_params = PyEVMBackend._generate_genesis_params(overrides=genesis_overrides) - - l1prov = EthereumTesterProvider(EthereumTester(PyEVMBackend(genesis_params))) - self.l1web3 = Web3(l1prov) - self.l1web3.testing.mine() - - l2prov = EthereumTesterProvider(EthereumTester(PyEVMBackend(genesis_params))) - self.l2web3 = Web3(l2prov) - self.l2web3.testing.mine() - - self.deploy_tx = { - 'from': self.l2web3.eth.accounts[0], - 'gas': DEPLOY_GAS - } - - self.standard_tx = { - 'from': self.l2web3.eth.accounts[0], - 'gas': 200000 - } - - - # These users will get the l2 token - self.L2_ALICE = self.l2web3.eth.accounts[3] - self.L2_BOB = self.l2web3.eth.accounts[4] - self.L2_CHARLIE = self.l2web3.eth.accounts[5] - # Dave will just have the l2-native ETH-equivalent he's born with - self.L2_DAVE= self.l2web3.eth.accounts[6] - - self.L1_BOB = self.L2_BOB - self.L1_CHARLIE = self.L2_CHARLIE - self.FORKMANAGER_INITIAL_RECIPIENT = self.l1web3.eth.accounts[7] - self.FORKMANAGER_INITIAL_SUPPLY = 1000000000000000000 - - # Make a token on L2 - k0 = self.l2web3.eth.accounts[0] - - self.l2token0 = self._contractFromBuildJSON(self.l2web3, 'ERC20Mint') - self.l2token0.functions.mint(k0, 100000000000000).transact() - - #self.assertEqual(self.l2token0.functions.balanceOf(k0).call(), 100000000000000) - self.assertEqual(self.l2token0.functions.balanceOf(k0).call(), 100000000000000) - - - # Make a reality.eth instance on L2 - self.l2realityeth = self._contractFromBuildJSON(self.l2web3, 'RealityETH_ERC20-3.0') - self.l2realityeth.functions.setToken(self.l2token0.address).transact() - - - # Make two competing arbitrators on L2, both will be added to the whitelist nitially. - - self.arb1 = self._contractFromBuildJSON(self.l2web3, 'Arbitrator') - self.arb2 = self._contractFromBuildJSON(self.l2web3, 'Arbitrator') - self.arb3 = self._contractFromBuildJSON(self.l2web3, 'Arbitrator') - - - # Make an AMB contract on L2, we'll pretend it's connected to L1 - self.l2AMB = self._contractFromBuildJSON(self.l2web3, 'AMB') - - self.dispute_fee = 100000000000000000 - - - # Make a WhitelistArbitrator. - # We set the reality.eth instance and dispute fee in the constructor, unlike the plain Arbitrator. TODO: should we standardize this? - self.whitelist_arbitrator = self._contractFromBuildJSON(self.l2web3, 'WhitelistArbitrator', None, None, [self.l2realityeth.address, self.dispute_fee, self.l2AMB.address, [self.arb1.address, self.arb2.address]]) - self.assertEqual(self.whitelist_arbitrator.functions.realitio().call(), self.l2realityeth.address) - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call()) - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb2.address).call()) - self.assertFalse(self.whitelist_arbitrator.functions.arbitrators(self.arb3.address).call()) - - - # Set up our competing arbitrators. - # NB They talk to the WhitelistArbitrator contract as if it's reality.eth, so use that for setRealitio not reality.eth - self.arb1.functions.setRealitio(self.whitelist_arbitrator.address).transact() - self.arb1.functions.setDisputeFee(10000).transact() - - self.arb2.functions.setRealitio(self.whitelist_arbitrator.address).transact() - self.arb2.functions.setDisputeFee(20000).transact() - - - # Mint balances for our test users so and preapprove reality.eth so we don't have to keep calling approve whenever we do something. - self.l2token0.functions.mint(self.L2_ALICE, 30000000000000).transact() - self.l2token0.functions.mint(self.L2_BOB, 50000000000000).transact() - self.l2token0.functions.mint(self.L2_CHARLIE, 70000000000000).transact() - self.l2token0.functions.approve(self.l2realityeth.address, 30000000000000).transact(self._txargs(sender=self.L2_ALICE)) - self.l2token0.functions.approve(self.l2realityeth.address, 50000000000000).transact(self._txargs(sender=self.L2_BOB)) - self.l2token0.functions.approve(self.l2realityeth.address, 70000000000000).transact(self._txargs(sender=self.L2_CHARLIE)) - - - - #self.l1token0 = self._contractFromBuildJSON(self.l1web3, 'ForkManager') - # self.l1token0.functions.mint(k0, 800000000000000).transact() - # self.assertEqual(self.l1token0.functions.balanceOf(k0).call(), 800000000000000) - - self.l1realityeth = self._contractFromBuildJSON(self.l1web3, 'ForkableRealityETH_ERC20', None, None) - # self.assertEqual(self.l1realityeth.functions.token().call(), self.l1token0.address) - - - libForkableRealityETH = self._contractFromBuildJSON(self.l1web3, 'ForkableRealityETH_ERC20', None, None) - libBridgeToL2 = self._contractFromBuildJSON(self.l1web3, 'ZKBridgeToL2', None, None) - libForkManager = self._contractFromBuildJSON(self.l1web3, 'ForkManager', None, None) - self.bridgeToL2 = self._contractFromBuildJSON(self.l1web3, 'ZKBridgeToL2', None, None) - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - self.forkmanager = self._contractFromBuildJSON(self.l1web3, 'ForkManager', None, None) - self.l1realityeth.functions.init(self.forkmanager.address, NULL_ADDRESS, Web3.to_bytes(hexstr=NULL_BYTES)).transact() - - # init(address payable _parentForkManager, address _realityETH, address _bridgeToL2, bool _has_governance_freeze, uint256 _parentSupply, address payable _libForkManager, address _libForkableRealityETH, address _libBridgeToL2) - txid = self.forkmanager.functions.init(NULL_ADDRESS, self.l1realityeth.address, self.bridgeToL2.address, False, 10000, libForkManager.address, libForkableRealityETH.address, libBridgeToL2.address, self.FORKMANAGER_INITIAL_RECIPIENT, self.FORKMANAGER_INITIAL_SUPPLY).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def run_basic_cycle(self): - self._setup_basic_cycle() - - def _setup_basic_cycle(self, question_text = "my question x"): - - ### Make a crowdfund - ### [Just asking the question that'll settle the crowdfund, crowdfund contract part skipped] - - question_id = calculate_question_id(self.l2realityeth.address, 0, question_text, self.whitelist_arbitrator.address, 30, 0, 0, self.L2_ALICE, 0) - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - txid = self.l2realityeth.functions.askQuestion(0, question_text, self.whitelist_arbitrator.address, 30, 0, 0).transact(self._txargs(sender=self.L2_ALICE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_ARBITRATOR], self.whitelist_arbitrator.address) - - ### Report an answer (contested) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(1), 0, 100).transact(self._txargs(sender=self.L2_BOB)) - self.raiseOnZeroStatus(txid, self.l2web3) - - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(0), 0, 200).transact(self._txargs(sender=self.L2_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(1), 0, 400).transact(self._txargs(sender=self.L2_BOB)) - self.raiseOnZeroStatus(txid, self.l2web3) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(0), 0, 2000000).transact(self._txargs(sender=self.L2_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - ### Contest an answer - - # TODO: Should the WhitelistArbitrator get paid in the native token or should it be an ERC20? - # self.l2token0.functions.approve(self.whitelist_arbitrator.address, self.dispute_fee).transact(self._txargs(sender=self.L2_BOB)) - # self.raiseOnZeroStatus(txid) - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], False) - self.whitelist_arbitrator.functions.requestArbitration(question_id, 3000000).transact(self._txargs(sender=self.L2_BOB, val=self.dispute_fee)) - self.raiseOnZeroStatus(txid, self.l2web3) - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], True) - - # We can now see the question on the WhitelistArbitrator waiting for someone to answer it, it shouldn't have been picked up yet - qa = self.whitelist_arbitrator.functions.question_arbitrations(question_id).call() - self.assertEqual(qa[WAINDEX_ARBITRATOR], NULL_ADDRESS) - - arb1_dispute_fee = self.arb1.functions.getDisputeFee(question_id).call(); - self.assertNotEqual(arb1_dispute_fee, 0) - txid = self.arb1.functions.requestArbitration(question_id, 0).transact(self._txargs(sender=self.L2_DAVE, val=arb1_dispute_fee)); - self.raiseOnZeroStatus(txid, self.l2web3) - - qa = self.whitelist_arbitrator.functions.question_arbitrations(question_id).call() - self.assertEqual(qa[WAINDEX_ARBITRATOR], self.arb1.address) - - ### TODO: This should fail but doesn't seem to? - # Now no other queued arbitrator can pick up the question. - #arb2_dispute_fee = self.arb2.functions.getDisputeFee(question_id).call(); - #self.assertNotEqual(arb2_dispute_fee, 0) - #with self.assertRaises(TransactionFailed): - # txid = self.arb2.functions.requestArbitration(question_id, 0).transact(self._txargs(sender=self.L2_DAVE, val=arb2_dispute_fee)); - - - # For now we'll do this as the default user, they should still own the contract - txid = self.arb1.functions.submitAnswerByArbitrator(question_id, to_answer_for_contract(1), self.L2_DAVE).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - # We haven't called anything against the reality.eth contract yet, so it should still be pending arbitration - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], True) - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call()) - - # Should fail because of challenge timeout - with self.assertRaises(TransactionFailed): - txid = self.whitelist_arbitrator.functions.completeArbitration(question_id, to_answer_for_contract(1), self.L2_DAVE).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - return question_id - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_uncontested_arbitration(self): - - question_id = self._setup_basic_cycle() - - dispute_timeout = self.whitelist_arbitrator.functions.ARB_DISPUTE_TIMEOUT().call() - self.assertEqual(dispute_timeout, 86400); - - self._advance_clock(dispute_timeout+1) - - txid = self.whitelist_arbitrator.functions.completeArbitration(question_id, to_answer_for_contract(1), self.L2_DAVE).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], False) - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_auction(self): - - (contest_question_id, answer_history) = self._setup_add_arbitrator() - last_bond = answer_history[-1]['bond'] - last_answerer = answer_history[-1]['answerer'] - last_answer = answer_history[-1]['answer'] - previous_history_hash = answer_history[-1]['previous_history_hash'] - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - history_hash = contestq[QINDEX_HISTORY_HASH] - - add_amount = last_bond - - bob_extra_bal = 7654 - charlie_extra_bal = 938493 - - # To be able to fork an arbitrator we need to post at least 1% of supply - fork_amount = int(self.FORKMANAGER_INITIAL_SUPPLY * 5 / 100) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, 0, "bob starts off with 0") - charlie_bal = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal, 0, "charlie starts off with 0") - - - # Give everyone some ForkManager coins so we can test what happens when they pick sides etc later - # Bob also needs enough to pay for the forking - txid = self.forkmanager.functions.transfer(self.L1_BOB, fork_amount+bob_extra_bal).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, charlie_extra_bal).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, fork_amount).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - self.assertTrue(self.forkmanager.functions.isUnForked().call()) - - txid = self.forkmanager.functions.requestArbitrationByFork(contest_question_id, 0).transact(self._txargs(sender=self.L1_BOB, gas=3000000)) - self.raiseOnZeroStatus(txid, self.l1web3) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, bob_extra_bal, "after requesting the fork, bob no longer has the fork amount but still has his extra balance") - - # time to bid - - - self._advance_clock(604800, self.l1web3) - - - # TODO: First to_answer_for_contract should be previous history hash - txid = self.forkmanager.functions.deployFork(True, previous_history_hash, last_answer, last_answerer, last_bond).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - - # deployFork will create a LogNewAnswer event. - # We'll need this for the claim transaction. - ans_log = self.l1realityeth.events.LogNewAnswer().process_receipt(rcpt, errors=DISCARD) - - # print(rcpt) - self.raiseOnZeroStatus(txid, self.l1web3) - - ts1 = self._block_timestamp(self.l1web3) - txid = self.forkmanager.functions.deployFork(False, previous_history_hash, last_answer, last_answerer, last_bond).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - # print(rcpt) - self.raiseOnZeroStatus(txid, self.l1web3) - - - child_fm1_addr = self.forkmanager.functions.childForkManager1().call() - child_fm2_addr = self.forkmanager.functions.childForkManager2().call() - - child_fm1 = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - child_fm2 = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - - - - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_contested_token_migration(self): - self._setup_contested_arbitration_with_fork() - self.assertTrue(self.forkmanager.functions.isForkingResolved()) - - bob_bal_parent = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_parent, 54000) - - self.forkmanager.functions.migrateToChildren(4000, False, False).transact(self._txargs(sender=self.L1_BOB)) - - bob_bal_parent = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_parent, 50000) - - child_fm1_addr = self.forkmanager.functions.childForkManager1().call() - child_fm2_addr = self.forkmanager.functions.childForkManager2().call() - - child_fm1 = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - child_fm2 = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - - bob_bal_fm1 = child_fm1.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_fm1, 4000) - - bob_bal_fm2 = child_fm1.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_fm2, 4000) - - self.forkmanager.functions.migrateToChildren(1000, False, True).transact(self._txargs(sender=self.L1_BOB)) - self.assertEqual(child_fm1.functions.balanceOf(self.L1_BOB).call(), 5000) - self.assertEqual(child_fm2.functions.balanceOf(self.L1_BOB).call(), 4000) - - self.forkmanager.functions.migrateToChildren(500, True, False).transact(self._txargs(sender=self.L1_BOB)) - self.assertEqual(child_fm1.functions.balanceOf(self.L1_BOB).call(), 5000) - self.assertEqual(child_fm2.functions.balanceOf(self.L1_BOB).call(), 4500) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_setup_simple(self): - - pass - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_contested_arbitration(self): - self._setup_contested_arbitration_with_fork() - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_clear_questions_after_arbitrator_removed(self): - q2id = self._setup_clear_questions_after_arbitrator_removed() - - handle_timeout = self.whitelist_arbitrator.functions.QUESTION_UNHANDLED_TIMEOUT().call() - - # Should fail if the timeout is not yet passed - with self.assertRaises(TransactionFailed): - txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(q2id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - self._advance_clock(handle_timeout+1) - - # Should succeed now - txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(q2id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_reassign_question_after_arbitrator_removed(self): - q2id = self._setup_clear_questions_after_arbitrator_removed() - - # Now we should be able to either answer with a different arbitrator, or wait for the timeout and cancel the arbitration request. - #txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(q2id).transact(self._txargs()) - #self.raiseOnZeroStatus(txid, self.l2web3) - - # arb1 should be gone but arb2 should still be there so they can take the question - self.assertFalse(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call()) - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb2.address).call()) - - # TODO: Check what the flow is for taking a job and make sure we're testing it correctly - # arb0 cannot accept jobs any more - # arb2 can accept this job - - fee = self.arb2.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - txid = self.arb2.functions.requestArbitration(q2id, 0).transact(self._txargs(val=fee)) - self.raiseOnZeroStatus(txid, self.l2web3) - - - def _setup_clear_questions_after_arbitrator_removed(self): - # Add an extra question and bring it up to arbitration - q2id = self._setup_basic_cycle("my question y") - - is_pending_arb = self.l2realityeth.functions.isPendingArbitration(q2id).call() - self.assertTrue(is_pending_arb) - - # arbitrator should now have accepted the task, but not yet completed it - qa_rec = self.whitelist_arbitrator.functions.question_arbitrations(q2id).call() - qa_bounty = qa_rec[2] - self.assertGreater(qa_bounty, 0) - - # Should fail as it's being handled - with self.assertRaises(TransactionFailed): - txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(q2id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - # Should fail as the arbitrator is still listed - with self.assertRaises(TransactionFailed): - txid = self.whitelist_arbitrator.functions.clearRequestFromRemovedArbitrator(q2id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - # Now remove the arbitrator over a different question without a fork - (question_id, contest_question_id, freeze_amount, last_bond, last_history_hash, last_answer, last_answerer, answer_history, new_history_hash) = self._setup_contested_arbitration() - - # Let the thing timeout so we can remove the arbitrator without a challenge - reality_eth_timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(reality_eth_timeout, 604800); - - contest_q = self.l1realityeth.functions.questions(contest_question_id).call() - best_answer = self.l1realityeth.functions.getBestAnswer(contest_question_id).call() - self.assertEqual(from_answer_for_contract(best_answer), 1) - - arb_pending = self.l1realityeth.functions.isPendingArbitration(contest_question_id).call() - self.assertFalse(arb_pending) - - timeout = self.l1realityeth.functions.getTimeout(contest_question_id).call() - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(timeout+1, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertNotEqual(ts1, ts2) - - finalize_ts = self.l1realityeth.functions.getFinalizeTS(contest_question_id).call() - self.assertLess(ts1, finalize_ts) - self.assertGreater(ts2, finalize_ts) - - # The ForkableRealityETH_ERC20 needs you to call finalize, unlike the normal one which finalizes automatically based on time - self.l1realityeth.functions.finalize(contest_question_id).transact() - - is_finalized = self.l1realityeth.functions.isFinalized(contest_question_id).call() - self.assertTrue(is_finalized) - - txid = self.forkmanager.functions.executeRemoveArbitratorFromWhitelist(contest_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - bridge_log = self.bridgeToL2.events.LogPassMessage().process_receipt(tx_receipt, errors=DISCARD) - self.assertEqual(len(bridge_log), 1, "The bridge on L1 was called and logged an event") - call_data = bridge_log[0]['args']['_data'] - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "starts off on list") - self.assertTrue(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "starts off frozen") - - # The executeRemoveArbitratorFromWhitelist call should have called the bridge with the code: - # bytes memory data = abi.encodeWithSelector(WhitelistArbitrator(whitelist_arbitrator).removeArbitrator.selector, arbitrator_to_remove); - # bridgeToL2.requireToPassMessage(whitelist_arbitrator, data, 0); - # We'll imitate this by calling our dummy bridge ourselves - txid = self.l2AMB.functions.passMessage( - FORKMANAGER_SPECIAL_ADDRESS, #Rewritten from self.forkmanager.address - self.whitelist_arbitrator.address, - call_data, - 5000000, - Web3.to_bytes(hexstr=NULL_BYTES), - Web3.to_bytes(hexstr=NULL_BYTES) - ).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertFalse(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "no longer on list") - - # Now we should be able to call clearRequestFromRemovedArbitrator to clear the original question - txid = self.whitelist_arbitrator.functions.clearRequestFromRemovedArbitrator(q2id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - - return q2id - - - - - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_clear_unhandled_arbitration(self): - - question_id = calculate_question_id(self.l2realityeth.address, 0, "my question x", self.whitelist_arbitrator.address, 30, 0, 0, self.L2_ALICE, 0) - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - txid = self.l2realityeth.functions.askQuestion(0, "my question x", self.whitelist_arbitrator.address, 30, 0, 0).transact(self._txargs(sender=self.L2_ALICE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_ARBITRATOR], self.whitelist_arbitrator.address) - - ### Report an answer (contested) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(1), 0, 100).transact(self._txargs(sender=self.L2_BOB)) - self.raiseOnZeroStatus(txid, self.l2web3) - - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(0), 0, 200).transact(self._txargs(sender=self.L2_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(1), 0, 400).transact(self._txargs(sender=self.L2_BOB)) - self.raiseOnZeroStatus(txid, self.l2web3) - - txid = self.l2realityeth.functions.submitAnswerERC20(question_id, to_answer_for_contract(0), 0, 2000000).transact(self._txargs(sender=self.L2_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l2web3) - - bob_bal_before_request = self.l2web3.eth.get_balance(self.L2_BOB) - # self.whitelist_arbitrator.functions.balanceOf(self.L1_BOB).call() - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], False) - txid = self.whitelist_arbitrator.functions.requestArbitration(question_id, 3000000).transact(self._txargs(sender=self.L2_BOB, val=self.dispute_fee)) - self.raiseOnZeroStatus(txid, self.l2web3) - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], True) - - request_arb_gas = 127470 - - bob_bal_after_request = self.l2web3.eth.get_balance(self.L2_BOB) - #self.assertEqual(bob_bal_before_request - bob_bal_after_request , self.dispute_fee + request_arb_gas) - - # We can now see the question on the WhitelistArbitrator waiting for someone to answer it, it shouldn't have been picked up yet - qa = self.whitelist_arbitrator.functions.question_arbitrations(question_id).call() - self.assertEqual(qa[WAINDEX_ARBITRATOR], NULL_ADDRESS) - - handle_timeout = self.whitelist_arbitrator.functions.QUESTION_UNHANDLED_TIMEOUT().call() - self.assertEqual(handle_timeout, 86400); - - # Bob should have no balance in the arbitrator contract - bob_bal_in_contract = self.whitelist_arbitrator.functions.balanceOf(self.L2_BOB).call() - self.assertEqual(bob_bal_in_contract, 0) - - # The whitelist arbitrator should list Bob as the payer, and the amount he paid as the bounty - #balanceOf[question_arbitrations[question_id].payer] = balanceOf[question_arbitrations[question_id].payer] + question_arbitrations[question_id].bounty; - arb_rec = self.whitelist_arbitrator.functions.question_arbitrations(question_id).call() - #address arbitrator; - #address payer; - #uint256 bounty; - #bytes32 msg_hash; - #uint256 finalize_ts; - #uint256 last_action_ts; - - # no arbitrator yet - self.assertEqual(arb_rec[0], NULL_ADDRESS) - self.assertEqual(arb_rec[1], self.L2_BOB) - self.assertEqual(arb_rec[2], self.dispute_fee) - - # Should fail if the timeout is not yet passed - with self.assertRaises(TransactionFailed): - txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(question_id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - self._advance_clock(handle_timeout+1) - - # Should succeed now - txid = self.whitelist_arbitrator.functions.cancelUnhandledArbitrationRequest(question_id).transact(self._txargs()) - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertEqual(self.l2realityeth.functions.questions(question_id).call()[QINDEX_IS_PENDING_ARBITRATION], False) - - arb_rec2 = self.whitelist_arbitrator.functions.question_arbitrations(question_id).call() - self.assertEqual(arb_rec2[0], NULL_ADDRESS) - self.assertEqual(arb_rec2[1], NULL_ADDRESS) - self.assertEqual(arb_rec2[2], 0) - - # Bob should now have his dispute fee back in the contract - bob_bal_in_contract2 = self.whitelist_arbitrator.functions.balanceOf(self.L2_BOB).call() - self.assertEqual(bob_bal_in_contract2, self.dispute_fee) - - bob_bal_before_withdraw = self.l2web3.eth.get_balance(self.L2_BOB) - - txid = self.whitelist_arbitrator.functions.withdraw().transact(self._txargs(sender=self.L2_BOB, gas=40000)) - self.raiseOnZeroStatus(txid, self.l2web3) - - tx_receipt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = tx_receipt['cumulativeGasUsed'] - gas_price = tx_receipt['effectiveGasPrice'] - spent_on_gas = gas_price * gas_used - self.assertGreater(self.dispute_fee, spent_on_gas) - withdraw_log = self.whitelist_arbitrator.events.LogWithdraw().process_receipt(tx_receipt, errors=DISCARD) - self.assertEqual(len(withdraw_log), 1, "The whitelist arbitrator on L2 was called and logged an event") - self.assertEqual(withdraw_log[0]['args']['user'], self.L2_BOB) - self.assertEqual(withdraw_log[0]['args']['amount'], self.dispute_fee) - - bob_bal_in_contract3 = self.whitelist_arbitrator.functions.balanceOf(self.L2_BOB).call() - self.assertEqual(bob_bal_in_contract3, 0) - - bob_bal_after_withdraw = self.l2web3.eth.get_balance(self.L2_BOB) - self.assertGreater(bob_bal_after_withdraw, bob_bal_before_withdraw) - - bal_change = bob_bal_after_withdraw - bob_bal_before_withdraw - - self.assertGreater(self.dispute_fee, bal_change) - self.assertEqual(bal_change, self.dispute_fee - spent_on_gas) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_post_fork_arbitrator_removal(self): - - (contest_question_id, answer_history1, answer_history2, child_fm1, child_fm2) = self._setup_contested_arbitration_with_fork() - txid = child_fm1.functions.executeRemoveArbitratorFromWhitelist(contest_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - bridge_log = self.bridgeToL2.events.LogPassMessage().process_receipt(tx_receipt, errors=DISCARD) - self.assertEqual(len(bridge_log), 1, "The bridge on L1 was called and logged an event") - call_data = bridge_log[0]['args']['_data'] - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "starts off on list") - self.assertTrue(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "starts off frozen") - - # The executeRemoveArbitratorFromWhitelist call should have called the bridge with the code: - # bytes memory data = abi.encodeWithSelector(WhitelistArbitrator(whitelist_arbitrator).removeArbitrator.selector, arbitrator_to_remove); - # bridgeToL2.requireToPassMessage(whitelist_arbitrator, data, 0); - # We'll imitate this by calling our dummy bridge ourselves - txid = self.l2AMB.functions.passMessage( - FORKMANAGER_SPECIAL_ADDRESS, #Rewritten from self.forkmanager.address - self.whitelist_arbitrator.address, - call_data, - 5000000, - Web3.to_bytes(hexstr=NULL_BYTES), - Web3.to_bytes(hexstr=NULL_BYTES) - ).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertFalse(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "ends up not on list") - self.assertFalse(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "ends up not frozen") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_uncontested_arbitrator_removal(self): - - (contest_question_id, answer_history1, answer_history2, child_fm1, child_fm2) = self._setup_contested_arbitration_with_fork() - txid = child_fm1.functions.executeRemoveArbitratorFromWhitelist(contest_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - bridge_log = self.bridgeToL2.events.LogPassMessage().process_receipt(tx_receipt, errors=DISCARD) - self.assertEqual(len(bridge_log), 1, "The bridge on L1 was called and logged an event") - call_data = bridge_log[0]['args']['_data'] - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "starts off on list") - self.assertTrue(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "starts off frozen") - - # The executeRemoveArbitratorFromWhitelist call should have called the bridge with the code: - # bytes memory data = abi.encodeWithSelector(WhitelistArbitrator(whitelist_arbitrator).removeArbitrator.selector, arbitrator_to_remove); - # bridgeToL2.requireToPassMessage(whitelist_arbitrator, data, 0); - # We'll imitate this by calling our dummy bridge ourselves - txid = self.l2AMB.functions.passMessage( - FORKMANAGER_SPECIAL_ADDRESS, #Rewritten from self.forkmanager.address - self.whitelist_arbitrator.address, - call_data, - 5000000, - Web3.to_bytes(hexstr=NULL_BYTES), - Web3.to_bytes(hexstr=NULL_BYTES) - ).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertFalse(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "ends up not on list") - self.assertFalse(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "ends up not frozen") - - - - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_post_fork_arbitrator_unfreezing(self): - - (contest_question_id, answer_history1, answer_history2, child_fm1, child_fm2) = self._setup_contested_arbitration_with_fork() - txid = child_fm2.functions.executeUnfreezeArbitratorOnWhitelist(contest_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - bridge_log = self.bridgeToL2.events.LogPassMessage().process_receipt(tx_receipt, errors=DISCARD) - self.assertEqual(len(bridge_log), 1, "The bridge on L1 was called and logged an event") - call_data = bridge_log[0]['args']['_data'] - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "starts off on list") - self.assertTrue(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "starts off frozen") - - # The executeRemoveArbitratorFromWhitelist call should have called the bridge with the code: - # bytes memory data = abi.encodeWithSelector(WhitelistArbitrator(whitelist_arbitrator).removeArbitrator.selector, arbitrator_to_remove); - # bridgeToL2.requireToPassMessage(whitelist_arbitrator, data, 0); - # We'll imitate this by calling our dummy bridge ourselves - txid = self.l2AMB.functions.passMessage( - FORKMANAGER_SPECIAL_ADDRESS, #Rewritten from self.forkmanager.address - self.whitelist_arbitrator.address, - call_data, - 5000000, - Web3.to_bytes(hexstr=NULL_BYTES), - Web3.to_bytes(hexstr=NULL_BYTES) - ).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - self.assertTrue(self.whitelist_arbitrator.functions.arbitrators(self.arb1.address).call(), "ends up still on list") - self.assertFalse(self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call(), "ends up not frozen") - - # TODO: Test using the arbitrator which is now unfrozen - - - # TODO: Test the process of claiming from reality.eth on each branch - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_setup_add_arbitrator(self): - self._setup_add_arbitrator() - - def _setup_add_arbitrator(self): - - answer_history = [] - - txid = self.forkmanager.functions.beginAddArbitratorToWhitelist(self.whitelist_arbitrator.address, self.arb3.address).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - - ask_log = self.l1realityeth.events.LogNewQuestion().process_receipt(tx_receipt, errors=DISCARD) - contest_question_id = ask_log[0]['args']['question_id'] - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, 12345).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, 12345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - last_history_hash = contestq[QINDEX_HISTORY_HASH] - - txid = self.l1realityeth.functions.submitAnswerERC20(contest_question_id, to_answer_for_contract(1), 0, 12345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - ans_log = self.l1realityeth.events.LogNewAnswer().process_receipt(tx_receipt, errors=DISCARD) - - answer_history.append(self._log_to_answer_history(ans_log, last_history_hash)) - - return (contest_question_id, answer_history) - - def _log_to_answer_history(self, ans_log, last_history_hash): - - last_bond = ans_log[0]['args']['bond'] - last_answerer = ans_log[0]['args']['user'] - last_answer = ans_log[0]['args']['answer'] - - return { - 'bond': last_bond, - 'answerer': last_answerer, - 'answer': last_answer, - 'previous_history_hash': last_history_hash, - } - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_contested_add_arbitrator(self): - - (contest_question_id, answer_history) = self._setup_add_arbitrator() - last_bond = answer_history[-1]['bond'] - last_answerer = answer_history[-1]['answerer'] - last_answer = answer_history[-1]['answer'] - previous_history_hash = answer_history[-1]['previous_history_hash'] - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - history_hash = contestq[QINDEX_HISTORY_HASH] - - add_amount = last_bond - - bob_extra_bal = 7654 - charlie_extra_bal = 938493 - - # To be able to fork an arbitrator we need to post at least 1% of supply - fork_amount = int(self.FORKMANAGER_INITIAL_SUPPLY * 5 / 100) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, 0, "bob starts off with 0") - charlie_bal = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal, 0, "charlie starts off with 0") - - - # Give everyone some ForkManager coins so we can test what happens when they pick sides etc later - # Bob also needs enough to pay for the forking - txid = self.forkmanager.functions.transfer(self.L1_BOB, fork_amount+bob_extra_bal).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, charlie_extra_bal).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, fork_amount).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - self.assertTrue(self.forkmanager.functions.isUnForked().call()) - - txid = self.forkmanager.functions.requestArbitrationByFork(contest_question_id, 0).transact(self._txargs(sender=self.L1_BOB, gas=3000000)) - self.raiseOnZeroStatus(txid, self.l1web3) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, bob_extra_bal, "after requesting the fork, bob no longer has the fork amount but still has his extra balance") - - self.assertEqual(self.l1realityeth.functions.questions(contest_question_id).call()[QINDEX_IS_PENDING_ARBITRATION], True) - self.assertFalse(self.forkmanager.functions.isUnForked().call()) - - ##txid = self.l1realityeth.functions.submitAnswerERC20(contest_question_id, to_answer_for_contract(1), 0, 12345).transact(self._txargs(sender=self.L1_CHARLIE)) - - - self._advance_clock(604800, self.l1web3) - - - # TODO: First to_answer_for_contract should be previous history hash - txid = self.forkmanager.functions.deployFork(True, previous_history_hash, last_answer, last_answerer, last_bond).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - - # deployFork will create a LogNewAnswer event. - # We'll need this for the claim transaction. - ans_log = self.l1realityeth.events.LogNewAnswer().process_receipt(rcpt, errors=DISCARD) - - # print(rcpt) - self.raiseOnZeroStatus(txid, self.l1web3) - - ts1 = self._block_timestamp(self.l1web3) - txid = self.forkmanager.functions.deployFork(False, previous_history_hash, last_answer, last_answerer, last_bond).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - # print(rcpt) - self.raiseOnZeroStatus(txid, self.l1web3) - - - child_fm1_addr = self.forkmanager.functions.childForkManager1().call() - child_fm2_addr = self.forkmanager.functions.childForkManager2().call() - - child_fm1 = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - child_fm2 = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - - self._advance_clock(60, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertNotEqual(ts1, ts2) - - # print(self.forkmanager.abi) - realityeth1_addr = child_fm1.functions.realityETH().call() - realityeth1 = self.l1web3.eth.contract(realityeth1_addr, abi=self.l1realityeth.abi) - - realityeth2_addr = child_fm2.functions.realityETH().call() - realityeth2 = self.l1web3.eth.contract(realityeth2_addr, abi=self.l1realityeth.abi) - - q1 = realityeth1.functions.questions(contest_question_id).call() - - finalization_ts = q1[QINDEX_FINALIZATION_TS] - self.assertTrue(ts2 > finalization_ts, "finalization timestamp has passed") - is_finalized = realityeth1.functions.isFinalized(contest_question_id).call() - self.assertTrue(is_finalized, "q1 finalized") - - q2 = realityeth2.functions.questions(contest_question_id).call() - result1 = realityeth1.functions.resultFor(contest_question_id).call() - self.assertEqual(result1, to_answer_for_contract(1)) - result2 = realityeth2.functions.resultFor(contest_question_id).call() - self.assertEqual(result2, to_answer_for_contract(0)) - - bal1 = child_fm1.functions.balanceOf(realityeth1_addr).call() - # Each reality.eth instance should have enough tokens - self.assertEqual(bal1, add_amount) - - bal2 = child_fm2.functions.balanceOf(realityeth2_addr).call() - # Each reality.eth instance should have enough tokens - self.assertEqual(bal1, add_amount) - - # TODO: Test the claiming process - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, bob_extra_bal) - charlie_bal = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal, charlie_extra_bal) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, 7654) - charlie_bal = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal, 938493) - - return - - txid = self.forkmanager.functions.bid(40, 321).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - bob_bal_parent = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_parent, 7333) - - txid = self.forkmanager.functions.bid(20, 345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - charlie_bal_parent = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal_parent, 938148) - - txid = self.forkmanager.functions.resolveFork().transact(self._txargs(gas=3000000)) - self.raiseOnZeroStatus(txid, self.l1web3) - - replaced_by_addr = self.forkmanager.functions.replacedByForkManager().call() - self.assertEqual(child_fm2_addr, replaced_by_addr) - - replaced_by_fm = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - self.assertTrue(replaced_by_fm.functions.isWinner().call()) - self.assertFalse(replaced_by_fm.functions.isLoser().call()) - - not_replaced_by = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - self.assertFalse(not_replaced_by.functions.isWinner().call()) - self.assertTrue(not_replaced_by.functions.isLoser().call()) - - return (contest_question_id, child_fm1, child_fm2) - - - def _setup_contested_arbitration(self): - - question_id = self._setup_basic_cycle() - - answer_history = [] - - # question = self.whitelist_arbitrator.address + QUESTION_DELIM + self.arb1.address - - txid = self.forkmanager.functions.beginRemoveArbitratorFromWhitelist(self.whitelist_arbitrator.address, self.arb1.address).transact() - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - ask_log = self.l1realityeth.events.LogNewQuestion().process_receipt(tx_receipt, errors=DISCARD) - contest_question_id = ask_log[0]['args']['question_id'] - - # To be able to freeze an arbitrator we need to post at least 1% of supply - freeze_amount = int(self.FORKMANAGER_INITIAL_SUPPLY * 1 / 100) - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, freeze_amount+12345).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, freeze_amount).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - last_history_hash = contestq[QINDEX_HISTORY_HASH] - - txid = self.l1realityeth.functions.submitAnswerERC20(contest_question_id, to_answer_for_contract(1), 0, freeze_amount).transact(self._txargs(sender=self.L1_CHARLIE)) - #self.raiseOnZeroStatus(txid, self.l1web3) - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - answer_log = self.l1realityeth.events.LogNewAnswer().process_receipt(tx_receipt, errors=DISCARD) - - history_item = self._log_to_answer_history(answer_log, last_history_hash) - last_bond = history_item['bond'] - last_answer = history_item['answer'] - last_answerer = history_item['answerer'] - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - new_history_hash = contestq[QINDEX_HISTORY_HASH] - - answer_history.append(history_item) - - self.assertEqual(answer_history[-1]['bond'], freeze_amount, "expected last bond") - self.assertEqual(answer_history[-1]['answer'], to_answer_for_contract(1), "epected last answer") - self.assertEqual(answer_history[-1]['answerer'], self.L1_CHARLIE, "epected last answerer") - - #contestq = self.l1realityeth.functions.questions(contest_question_id).call() - #print("hh after submit" + encode_hex(contestq[QINDEX_HISTORY_HASH])) - - txid = self.forkmanager.functions.freezeArbitratorOnWhitelist(contest_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - #contestq = self.l1realityeth.functions.questions(contest_question_id).call() - #print("hh after freeze (should be same as submit)") - #print(encode_hex(contestq[QINDEX_HISTORY_HASH])) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - bridge_log = self.bridgeToL2.events.LogPassMessage().process_receipt(tx_receipt, errors=DISCARD) - call_data = bridge_log[0]['args']['_data'] - - - - is_frozen = self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call() - self.assertFalse(is_frozen, "not frozen at start") - - # The freezeArbitratorOnWhitelist call should have called the bridge with the code: - # bytes memory data = abi.encodeWithSelector(WhitelistArbitrator(arbitrator_to_remove).freezeArbitrator.selector, arbitrator_to_remove); - # We'll imitate this by calling our dummy bridge ourselves - bridge_call_data = self.whitelist_arbitrator.encodeABI(fn_name="freezeArbitrator", args=[self.arb1.address]) - txid = self.l2AMB.functions.passMessage( - FORKMANAGER_SPECIAL_ADDRESS, #Rewritten from self.forkmanager.address - self.whitelist_arbitrator.address, - call_data, - 5000000, - Web3.to_bytes(hexstr=NULL_BYTES), - Web3.to_bytes(hexstr=NULL_BYTES) - ).transact() - self.raiseOnZeroStatus(txid, self.l2web3) - - is_frozen = self.whitelist_arbitrator.functions.frozen_arbitrators(self.arb1.address).call() - self.assertTrue(is_frozen, "frozen at end") - - return (question_id, contest_question_id, freeze_amount, last_bond, last_history_hash, last_answer, last_answerer, answer_history, new_history_hash) - - def _setup_contested_arbitration_with_fork(self): - - (question_id, contest_question_id, freeze_amount, last_bond, last_history_hash, last_answer, last_answerer, answer_history, new_history_hash) = self._setup_contested_arbitration() - - # TODO: Break this out into different functions and handle some of the other scenarios - - # Bob to contest on L1 - - # To be able to fork an arbitrator we need to post at least 1% of supply - fork_amount = int(self.FORKMANAGER_INITIAL_SUPPLY * 5 / 100) - - txid = self.forkmanager.functions.transfer(self.L1_BOB, fork_amount+54321).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, fork_amount).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - contestq = self.l1realityeth.functions.questions(contest_question_id).call() - - self.assertTrue(self.forkmanager.functions.isUnForked().call()) - - expected_hh = self.l1realityeth.functions.getHistoryHash(contest_question_id).call() - calculated_hh = calculate_history_hash(to_answer_for_contract(0), to_answer_for_contract(1), freeze_amount, self.L1_CHARLIE, False) - self.assertEqual(expected_hh, calculated_hh) - - txid = self.forkmanager.functions.requestArbitrationByFork(contest_question_id, 0).transact(self._txargs(gas=3000000, sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - expected_hh2 = self.l1realityeth.functions.getHistoryHash(contest_question_id).call() - self.assertEqual(expected_hh, expected_hh2, "arbitration should not change the history hash") - - - self.assertEqual(self.l1realityeth.functions.questions(contest_question_id).call()[QINDEX_IS_PENDING_ARBITRATION], True) - self.assertFalse(self.forkmanager.functions.isUnForked().call()) - - self.assertEqual(self.l1realityeth.functions.getBond(contest_question_id).call(),self.l1realityeth.functions.getCumulativeBonds(contest_question_id).call(), "Cumulative bond should equal last bond as there was only one answer") - self.assertEqual(last_bond, self.l1realityeth.functions.getBond(contest_question_id).call(), "last bond is what we expect") - - - # Recreate the history hash and test it ourselves - expected_hh = self.l1realityeth.functions.getHistoryHash(contest_question_id).call() - calculated_hh = calculate_history_hash(last_history_hash, last_answer, last_bond, last_answerer, False) - self.assertEqual(expected_hh, calculated_hh) - - - self.assertEqual(len(answer_history), 1, "answer_history should still only have 1 entry") - - # From now on each reality.eth instance will have a different history as the arbitrator will settle them differently - answer_history1 = answer_history[:] - answer_history2 = answer_history[:] - - - - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - # The forkmanager should have deployed an auction contract - auction = self.forkmanager.functions.auction().call() - self.assertNotEqual(auction, NULL_ADDRESS) - - bob_bal = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal, 54321) - charlie_bal = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal, 12345) - - txid = self.forkmanager.functions.bid(40, 321).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - bob_bal_parent = self.forkmanager.functions.balanceOf(self.L1_BOB).call() - self.assertEqual(bob_bal_parent, 54000) - - txid = self.forkmanager.functions.bid(20, 345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - charlie_bal_parent = self.forkmanager.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_bal_parent, 12000) - - - - - - - self._advance_clock(604800, self.l1web3) - - - # First to_answer_for_contract should be previous history hash - # function deployFork(bool yes_or_no, bytes32 last_history_hash, bytes32 last_answer, address last_answerer, uint256 last_bond) - # txid = self.l1realityeth.functions.submitAnswerERC20(contest_question_id, to_answer_for_contract(1), 0, freeze_amount).transact(self._txargs(sender=self.L1_CHARLIE)) - txid = self.forkmanager.functions.deployFork(True, answer_history[-1]['previous_history_hash'], answer_history[-1]['answer'], answer_history[-1]['answerer'], answer_history[-1]['bond']).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - self.raiseOnZeroStatus(txid, self.l1web3) - - answer_log1 = self.l1realityeth.events.LogNewAnswer().process_receipt(rcpt, errors=DISCARD) - history_item1 = self._log_to_answer_history(answer_log1, new_history_hash) - answer_history1.append(history_item1) - - #TRY: Let this time out instead of challenging it, and see if we can claim it - - ts1 = self._block_timestamp(self.l1web3) - txid = self.forkmanager.functions.deployFork(False, answer_history[-1]['previous_history_hash'], answer_history[-1]['answer'], answer_history[-1]['answerer'], answer_history[-1]['bond']).transact(self._txargs(gas=6000000)) - rcpt = self.l1web3.eth.get_transaction_receipt(txid) - # print(rcpt) - self.raiseOnZeroStatus(txid, self.l1web3) - - answer_log2 = self.l1realityeth.events.LogNewAnswer().process_receipt(rcpt, errors=DISCARD) - history_item2 = self._log_to_answer_history(answer_log2, new_history_hash) - answer_history2.append(history_item2) - - self.assertNotEqual(answer_history1[-1]['answerer'], answer_history2[-1]['answerer'], "Different forks should have different answerers") - self.assertNotEqual(answer_history1[-1]['answer'], answer_history2[-1]['answer'], "Different forks should have different answers") - self.assertEqual(answer_history1[-1]['previous_history_hash'], answer_history2[-1]['previous_history_hash'], "Different forks should the same previous history hashes") - self.assertEqual(answer_history1[-1]['bond'], answer_history2[-1]['bond'], "Different forks should the same bonds") - self.assertEqual(answer_history1[-1]['bond'], 0, "final answer history bonds should be 0 as they are from arbitration") - - self.assertEqual(len(answer_history), 1, "answer_history should still only have 1 entry") - self.assertEqual(len(answer_history1), len(answer_history)+1, "answer_history 1 should have 1 more entry") - self.assertEqual(len(answer_history2), len(answer_history)+1, "answer_history 2 should have 1 more entry") - - child_fm1_addr = self.forkmanager.functions.childForkManager1().call() - child_fm2_addr = self.forkmanager.functions.childForkManager2().call() - - child_fm1 = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - child_fm2 = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - - self._advance_clock(60, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertNotEqual(ts1, ts2) - - - # print(self.forkmanager.abi) - realityeth1_addr = child_fm1.functions.realityETH().call() - realityeth1 = self.l1web3.eth.contract(realityeth1_addr, abi=self.l1realityeth.abi) - - realityeth2_addr = child_fm2.functions.realityETH().call() - realityeth2 = self.l1web3.eth.contract(realityeth2_addr, abi=self.l1realityeth.abi) - - q1 = realityeth1.functions.questions(contest_question_id).call() - - finalization_ts = q1[QINDEX_FINALIZATION_TS] - self.assertTrue(ts2 > finalization_ts, "finalization timestamp has passed") - is_finalized = realityeth1.functions.isFinalized(contest_question_id).call() - self.assertTrue(is_finalized, "q1 finalized") - - q2 = realityeth2.functions.questions(contest_question_id).call() - result1 = realityeth1.functions.resultFor(contest_question_id).call() - self.assertEqual(result1, to_answer_for_contract(1)) - result2 = realityeth2.functions.resultFor(contest_question_id).call() - self.assertEqual(result2, to_answer_for_contract(0)) - - bal1 = child_fm1.functions.balanceOf(realityeth1_addr).call() - # Each reality.eth instance should have enough tokens - self.assertEqual(bal1, freeze_amount) - - bal2 = child_fm2.functions.balanceOf(realityeth2_addr).call() - # Each reality.eth instance should have enough tokens - self.assertEqual(bal1, freeze_amount) - - txid = self.forkmanager.functions.resolveFork().transact(self._txargs(gas=3000000)) - self.raiseOnZeroStatus(txid, self.l1web3) - - replaced_by_addr = self.forkmanager.functions.replacedByForkManager().call() - self.assertEqual(child_fm2_addr, replaced_by_addr) - - replaced_by_fm = self.l1web3.eth.contract(child_fm2_addr, abi=self.forkmanager.abi) - self.assertTrue(replaced_by_fm.functions.isWinner().call()) - self.assertFalse(replaced_by_fm.functions.isLoser().call()) - - not_replaced_by = self.l1web3.eth.contract(child_fm1_addr, abi=self.forkmanager.abi) - self.assertFalse(not_replaced_by.functions.isWinner().call()) - self.assertTrue(not_replaced_by.functions.isLoser().call()) - - - fm1_bridge = child_fm1.functions.bridgeToL2().call() - fm2_bridge = child_fm2.functions.bridgeToL2().call() - self.assertNotEqual(fm1_bridge, fm2_bridge, "The forkmanagers should have their own separate bridges") - - return (contest_question_id, answer_history1, answer_history2, child_fm1, child_fm2) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_post_fork_claims(self): - - (contest_question_id, answer_history1, answer_history2, child_fm1, child_fm2) = self._setup_contested_arbitration_with_fork() - - # print(self.forkmanager.abi) - realityeth1_addr = child_fm1.functions.realityETH().call() - realityeth1 = self.l1web3.eth.contract(realityeth1_addr, abi=self.l1realityeth.abi) - - realityeth2_addr = child_fm2.functions.realityETH().call() - realityeth2 = self.l1web3.eth.contract(realityeth2_addr, abi=self.l1realityeth.abi) - - q1 = realityeth1.functions.questions(contest_question_id).call() - - finalization_ts = q1[QINDEX_FINALIZATION_TS] - - self._advance_clock(604800, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - - self.assertTrue(ts2 > finalization_ts, "finalization timestamp has passed") - is_finalized = realityeth1.functions.isFinalized(contest_question_id).call() - self.assertTrue(is_finalized, "q1 finalized") - - q2 = realityeth2.functions.questions(contest_question_id).call() - result1 = realityeth1.functions.resultFor(contest_question_id).call() - self.assertEqual(result1, to_answer_for_contract(1)) - result2 = realityeth2.functions.resultFor(contest_question_id).call() - self.assertEqual(result2, to_answer_for_contract(0)) - - bal1 = child_fm1.functions.balanceOf(realityeth1_addr).call() - # Each reality.eth instance should have enough tokens - bal2 = child_fm2.functions.balanceOf(realityeth2_addr).call() - # Each reality.eth instance should have enough tokens - - #self.assertTrue(False, "TODO: Implement the claim tests") - self.assertEqual(len(answer_history1), 2, "Should have 2 entries in answer_history1, for the answer fand for the arbitration") - - - h_arr1 = [answer_history1[1]['previous_history_hash'], answer_history1[0]['previous_history_hash']] - bond_arr1 = [answer_history1[1]['bond'], answer_history1[0]['bond']] - anser_arr1 = [answer_history1[1]['answerer'], answer_history1[0]['answerer']] - ans_arr1 = [answer_history1[1]['answer'], answer_history1[0]['answer']] - - # Pass one of the arrays in the wrong order to prove it fails - bond_arr_wrong1 = [answer_history1[0]['bond'], answer_history1[1]['bond']] - - h_arr2 = [answer_history2[1]['previous_history_hash'], answer_history2[0]['previous_history_hash']] - bond_arr2 = [answer_history2[1]['bond'], answer_history2[0]['bond']] - anser_arr2 = [answer_history2[1]['answerer'], answer_history2[0]['answerer']] - ans_arr2 = [answer_history2[1]['answer'], answer_history2[0]['answer']] - - # Pass one of the arrays in the wrong order to prove it fails - bond_arr_wrong2 = [answer_history2[0]['bond'], answer_history2[1]['bond']] - - # print(self.l2web3.eth.accounts) - self.assertEqual(answer_history1[-2]['answerer'], self.L1_CHARLIE, "charlie gave the first answer for realityeth 1") - self.assertEqual(answer_history2[-2]['answerer'], self.L1_CHARLIE, "charlie gave the first answer for realityeth 2") - self.assertEqual(answer_history1[-1]['answerer'], self.L1_CHARLIE, "charlie is credited with the answer for realityeth1") - self.assertEqual(answer_history2[-1]['answerer'], self.L1_BOB, "bob is credited with the answer for realityeth2") - - with self.assertRaises(TransactionFailed): - txid = realityeth2.functions.claimWinnings(contest_question_id, h_arr2, anser_arr2, bond_arr_wrong2, ans_arr2).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - charlie_before_bal = realityeth1.functions.balanceOf(self.L1_CHARLIE).call() - self.assertEqual(charlie_before_bal, 0, "Charlie has no balance on the forked reality.eth until claim") - txid = realityeth1.functions.claimWinnings(contest_question_id, h_arr1, anser_arr1, bond_arr1, ans_arr1).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - charlie_after_bal = realityeth1.functions.balanceOf(self.L1_CHARLIE).call() - self.assertNotEqual(charlie_after_bal, 0, "Charlie has a balance on the forked reality.eth after claim") - - txid = realityeth1.functions.withdraw().transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - self.assertEqual(realityeth1.functions.balanceOf(self.L1_CHARLIE).call(), 0, "After withdraw charlie no longer has tokens in the reality.eth contract") - self.assertEqual(charlie_after_bal, child_fm1.functions.balanceOf(self.L1_CHARLIE).call(), "The balance charlie had on the reaity.eth1 is now in forkmanager1") - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_uncontested_bridge_upgrade(self): - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade() - - # We can't execute the bridge upgrade until the timeout - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(604800+10, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertTrue(ts2 >= ts1+604800+10, "Clock did not advance as expected") - - self.assertEqual(timeout, self.l1realityeth.functions.getTimeout(upgrade_question_id).call(), "Timeout reported by reality.eth not what we expect") - - history_hash = self.l1realityeth.functions.getHistoryHash(upgrade_question_id).call() - self.assertNotEqual(history_hash, answer_history[-1]['previous_history_hash']) - - self.l1realityeth.functions.finalize(upgrade_question_id).transact() - result1 = self.l1realityeth.functions.resultFor(upgrade_question_id).call() - self.assertEqual(result1, to_answer_for_contract(1)) - - old_bridge = self.forkmanager.functions.bridgeToL2().call() - - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - new_bridge = self.forkmanager.functions.bridgeToL2().call() - self.assertNotEqual(old_bridge, new_bridge) - - self.assertEqual(new_bridge_created.address, new_bridge) - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_successful_bridge_upgrade_with_freeze_and_unfreeze(self): - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - required_bridges = self.forkmanager.functions.requiredBridges().call() - self.assertNotEqual(required_bridges[0], NULL_ADDRESS, "In the normal unfrozen unforked state there should be 1 bridge") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "In the normal unfrozen unforked state there should be only 1 bridge") - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade_up_to_freeze() - - # TODO: Test how TokenBridge consumes this data - - bond_after_freeze = self.l1realityeth.functions.getBond(upgrade_question_id).call() * 2 - self.assertTrue(bond_after_freeze > 0, "zero bond after freeze") - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, bond_after_freeze).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, bond_after_freeze).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.l1realityeth.functions.submitAnswerERC20(upgrade_question_id, to_answer_for_contract(1), 0, bond_after_freeze).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(timeout+10, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertTrue(ts2 >= ts1+604800+10, "Clock did not advance as expected") - - # Bridge upgrade should fail as the question is still open - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - self.l1realityeth.functions.finalize(upgrade_question_id).transact() - - required_bridges = self.forkmanager.functions.requiredBridges().call() - self.assertEqual(required_bridges[0], NULL_ADDRESS, "Bridges should be frozen, so none are acceptable") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "Bridges should be frozen, so none are acceptable") - - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - required_bridges = self.forkmanager.functions.requiredBridges().call() - self.assertNotEqual(required_bridges[0], NULL_ADDRESS, "In the normal unfrozen unforked state there should be 1 bridge") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "In the normal unfrozen unforked state there should be only 1 bridge") - - self.assertEqual(new_bridge_created.address, required_bridges[0], "The new bridge is now required") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_failed_bridge_upgrade_with_freeze_and_unfreeze(self): - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - required_bridges = self.forkmanager.functions.requiredBridges().call() - original_bridge = required_bridges[0] - self.assertNotEqual(required_bridges[0], NULL_ADDRESS, "In the normal unfrozen unforked state there should be 1 bridge") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "In the normal unfrozen unforked state there should be only 1 bridge") - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade_up_to_freeze() - - # TODO: Test how TokenBridge consumes this data - - bond_after_freeze = self.l1realityeth.functions.getBond(upgrade_question_id).call() * 2 - self.assertTrue(bond_after_freeze > 0, "zero bond after freeze") - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, bond_after_freeze).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, bond_after_freeze).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.l1realityeth.functions.submitAnswerERC20(upgrade_question_id, to_answer_for_contract(0), 0, bond_after_freeze).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(timeout+10, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertTrue(ts2 >= ts1+604800+10, "Clock did not advance as expected") - - # Bridge upgrade should fail as the question is still open - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - self.l1realityeth.functions.finalize(upgrade_question_id).transact() - - # Bridge upgrade should fail as the question came out as 0 - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - required_bridges = self.forkmanager.functions.requiredBridges().call() - self.assertEqual(required_bridges[0], NULL_ADDRESS, "Bridges should be frozen, so none are acceptable") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "Bridges should be frozen, so none are acceptable") - - # Since the proposition failed, we can clear it and go back to normal - txid = self.forkmanager.functions.clearFailedGovernanceProposal(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - required_bridges = self.forkmanager.functions.requiredBridges().call() - self.assertNotEqual(required_bridges[0], NULL_ADDRESS, "In the normal unfrozen unforked state there should be 1 bridge") - self.assertEqual(required_bridges[1], NULL_ADDRESS, "In the normal unfrozen unforked state there should be only 1 bridge") - - self.assertEqual(required_bridges[0], original_bridge, "The original bridge should be the only required one") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_uncontested_bridge_upgrade_with_freeze(self): - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade_up_to_freeze() - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(timeout+10, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertTrue(ts2 >= ts1+604800+10, "Clock did not advance as expected") - - # TODO: Check the bridge is frozen somehow - - self.l1realityeth.functions.finalize(upgrade_question_id).transact() - result1 = self.l1realityeth.functions.resultFor(upgrade_question_id).call() - self.assertEqual(result1, to_answer_for_contract(1)) - - old_bridge = self.forkmanager.functions.bridgeToL2().call() - - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - new_bridge = self.forkmanager.functions.bridgeToL2().call() - self.assertNotEqual(old_bridge, new_bridge) - - self.assertEqual(new_bridge_created.address, new_bridge) - - - - - - - def _setup_bridge_upgrade_up_to_freeze(self): - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade() - - # We can't execute the bridge upgrade until the timeout - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - - amount_to_freeze = self.forkmanager.functions.numTokensRequiredToFreezeBridges().call() - self.assertTrue(amount_to_freeze > 1, "weird not to need any tokens to freeze bridges") - - self.assertTrue(amount_to_freeze > 12345, "amount to freeze better be more than we put in the original bond or the next tests we do will go wrong") - - # We can't execute the bridge upgrade until the bond is high enough - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.freezeBridges(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - # TODO: We should really be sending a proof that a high enough bond was submitted for the answer in question, not just any answer - - txid = self.forkmanager.functions.transfer(self.L1_BOB, amount_to_freeze).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, amount_to_freeze).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.l1realityeth.functions.submitAnswerERC20(upgrade_question_id, to_answer_for_contract(1), 0, amount_to_freeze).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.freezeBridges(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - # TODO: Add this answer to answer_history - - return (upgrade_question_id, answer_history, new_bridge_created) - - - - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_failed_bridge_upgrade(self): - - (upgrade_question_id, answer_history, new_bridge_created) = self._setup_bridge_upgrade() - - txid = self.forkmanager.functions.transfer(self.L1_BOB, 12345*2).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, 12345*2).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.l1realityeth.functions.submitAnswerERC20(upgrade_question_id, to_answer_for_contract(0), 0, 12345*2).transact(self._txargs(sender=self.L1_BOB)) - self.raiseOnZeroStatus(txid, self.l1web3) - - timeout = self.forkmanager.functions.REALITY_ETH_TIMEOUT().call() - self.assertEqual(604800, timeout) - - ts1 = self._block_timestamp(self.l1web3) - self._advance_clock(604800+10, self.l1web3) - ts2 = self._block_timestamp(self.l1web3) - self.assertTrue(ts2 >= ts1+604800+10, "Clock did not advance as expected") - - self.l1realityeth.functions.finalize(upgrade_question_id).transact() - result1 = self.l1realityeth.functions.resultFor(upgrade_question_id).call() - self.assertEqual(result1, to_answer_for_contract(0)) - - old_bridge = self.forkmanager.functions.bridgeToL2().call() - - # Bridge upgrade should fail because the reality.eth question resulted in 0 - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - new_bridge = self.forkmanager.functions.bridgeToL2().call() - self.assertEqual(old_bridge, new_bridge, "Bridge should not change if the reality.eth question results in 0") - - self.assertNotEqual(new_bridge_created.address, new_bridge) - - - - - - - - - - - - - - - # Do the bridge upgrade up to the point before we execute it - # We can then test the execution path and the contest path separately - def _setup_bridge_upgrade(self): - - newBridgeToL2 = self._contractFromBuildJSON(self.l1web3, 'BridgeToL2', None, None) - txid = self.forkmanager.functions.beginUpgradeBridge(newBridgeToL2.address).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - - ask_log = self.l1realityeth.events.LogNewQuestion().process_receipt(tx_receipt, errors=DISCARD) - upgrade_question_id = ask_log[0]['args']['question_id'] - #upgrade_question_id = Web3.to_bytes(hexstr="0x"+encode_hex(ask_log[0]['args']['question_id'])) - - upgradeq = self.l1realityeth.functions.questions(upgrade_question_id).call() - last_history_hash = upgradeq[QINDEX_HISTORY_HASH] - - # We can't execute the bridge upgrade yet because there's no answer set - with self.assertRaises(TransactionFailed): - txid = self.forkmanager.functions.executeBridgeUpgrade(upgrade_question_id).transact() - self.raiseOnZeroStatus(txid, self.l1web3) - - answer_history = [] - - txid = self.forkmanager.functions.transfer(self.L1_CHARLIE, 12345).transact(self._txargs(sender=self.FORKMANAGER_INITIAL_RECIPIENT)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.forkmanager.functions.approve(self.l1realityeth.address, 12345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - txid = self.l1realityeth.functions.submitAnswerERC20(upgrade_question_id, to_answer_for_contract(1), 0, 12345).transact(self._txargs(sender=self.L1_CHARLIE)) - self.raiseOnZeroStatus(txid, self.l1web3) - - tx_receipt = self.l1web3.eth.get_transaction_receipt(txid) - ans_log = self.l1realityeth.events.LogNewAnswer().process_receipt(tx_receipt, errors=DISCARD) - - answer_history.append(self._log_to_answer_history(ans_log, last_history_hash)) - # self.assertNotEqual(last_history_hash, ans_log['history_hash']) - - return (upgrade_question_id, answer_history, newBridgeToL2) - - - -class OldThing: - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_fund_increase(self): - - k0 = self.l2web3.eth.accounts[0] - - if ERC20: - start_bal = self.l2token0.functions.balanceOf(k0).call() - - self.rc0.functions.fundAnswerBountyERC20(self.question_id - ,500 - ).transact() - - end_bal = self.l2token0.functions.balanceOf(k0).call() - self.assertEqual(end_bal, start_bal - 500) - else: - txargs = self.standard_tx - txargs['value'] = 500 - self.rc0.functions.fundAnswerBounty(self.question_id).transact(txargs) - - question = self.rc0.functions.questions(self.question_id).call() - self.assertEqual(question[QINDEX_BOUNTY], 1500) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_no_response_finalization(self): - # Should not be final if too soon - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - self._advance_clock(33) - - # Should not be final if there is no answer - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_simple_response_finalization(self): - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 ,1).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - - self._advance_clock(33) - - best_answer = self.rc0.functions.questions(self.question_id).call()[QINDEX_BEST_ANSWER] - self.assertEqual(12345, from_answer_for_contract(best_answer)) - - self.assertTrue(self.rc0.functions.isFinalized(self.question_id).call()) - - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 12345) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_content_hash(self): - expect_ch = calculate_content_hash(0, "my question", 0) - ch = "0x" + self.rc0.functions.questions(self.question_id).call()[QINDEX_CONTENT_HASH] - self.assertEqual(expect_ch, ch) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_get_final_answer_if_match(self): - - expect_ch = calculate_content_hash(0, "my question", 0) - wrong_ch = calculate_content_hash(0, "not my question", 0) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 ,1 - ).transact() - - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1) ) - - # Not finalized yet - with self.assertRaises(TransactionFailed): - ans = self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(expect_ch[2:]), - self.arb0.address, - 0, - 25 - ).call() - - self._advance_clock(33) - - with self.assertRaises(TransactionFailed): - self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(expect_ch[2:]), - keys.privtoaddr(t.k2), - 0, - 25 - ).call() - - with self.assertRaises(TransactionFailed): - self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(wrong_ch[2:]), - self.arb0.address, - 0, - 25 - ).call() - - with self.assertRaises(TransactionFailed): - self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(expect_ch[2:]), - self.arb0.address, - 25, - 99999999999 - ).call() - - with self.assertRaises(TransactionFailed): - self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(expect_ch[2:]), - self.arb0.address, - 1893459661, # 2030-01-01 - 25 - ).call() - - ans = self.rc0.functions.getFinalAnswerIfMatches( - self.question_id, - decode_hex(expect_ch[2:]), - self.arb0.address, - 0, - 0 - ).call() - self.assertEqual(from_answer_for_contract(ans), 12345) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_earliest_finalization_ts(self): - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(54321), 0 ,10).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(54321), 0).transact(self._txargs(val=10)) - ts1 = self.rc0.functions.questions(self.question_id).call()[QINDEX_FINALIZATION_TS] - - self._advance_clock(1) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(54321), 0 ,20).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(54321), 0).transact(self._txargs(val=20)) - - self.assertEqual(self.rc0.functions.questions(self.question_id).call()[QINDEX_BOND], 20) - ts2 = self.rc0.functions.questions(self.question_id).call()[QINDEX_FINALIZATION_TS] - - self.assertTrue(ts2 > ts1, "Submitting an answer advances the finalization timestamp") - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_conflicting_response_finalization(self): - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact() - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(54321), 0, - 10 - ).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(54321), 0).transact(self._txargs(val=10)) - - self._advance_clock(33) - - self.assertTrue(self.rc0.functions.isFinalized(self.question_id).call()) - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 54321) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_answered(self): - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - - # The arbitrator cannot submit an answer that has not been requested. - # (If they really want to do this, they can always pay themselves for arbitration.) - with self.assertRaises(TransactionFailed): - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), keys.privtoaddr(t.k0)).transact() - - # You cannot notify realitio of arbitration unless you are the arbitrator - with self.assertRaises(TransactionFailed): - self.rc0.functions.notifyOfArbitrationRequest(self.question_id, keys.privtoaddr(t.k0), 0).transact() - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - question = self.rc0.functions.questions(self.question_id).call() - self.assertTrue(question[QINDEX_IS_PENDING_ARBITRATION], "When arbitration is pending for an answered question, we set the is_pending_arbitration flag to True") - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), keys.privtoaddr(t.k0)).transact() - - self.assertTrue(self.rc0.functions.isFinalized(self.question_id).call()) - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 123456, "Arbitrator submitting final answer calls finalize") - - self.assertNotEqual(self.rc0.functions.questions(self.question_id).call()[QINDEX_BOND], 0) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_assigning_answerer_right(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_answering_assigning_answerer_right, not a feature of this contract") - return - - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - - last_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - - arb_answer = to_answer_for_contract(1001) - arb_payer = keys.privtoaddr(t.k2) - - hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - # Only the arbitrator can do this - with self.assertRaises(TransactionFailed): - self.rc0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, 0) - st['answer'].insert(0, arb_answer) - st['addr'].insert(0, k4) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 2+subfee(4)+subfee(8)+subfee(16)+1000, "The last answerer gets it all for a right answer") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_assigning_answerer_right_commit(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_answering_assigning_answerer_right_commit, not a feature of this contract") - return - - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - - last_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4, True) - nonce = st['nonce'][0] - self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1001), nonce, 16).transact(self._txargs(sender=k4, val=0)) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - - arb_answer = to_answer_for_contract(1001) - arb_payer = keys.privtoaddr(t.k2) - - hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, 0) - st['answer'].insert(0, arb_answer) - st['addr'].insert(0, k4) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 2+subfee(4)+subfee(8)+subfee(16)+1000, "The last answerer gets it all for a right answer") - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_assigning_answerer_wrong_commit(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_answering_assigning_answerer_wrong_commit, not a feature of this contract") - return - - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - - last_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4, True) - nonce = st['nonce'][0] - self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1001), nonce, 16).transact(self._txargs(sender=k4, val=0)) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - - arb_answer = to_answer_for_contract(98765) - arb_payer = keys.privtoaddr(t.k2) - - hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, 0) - st['answer'].insert(0, arb_answer) - st['addr'].insert(0, arb_payer) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(arb_payer).call(), 2+subfee(4)+subfee(8)+subfee(16)+1000, "The last answerer gets it all for a right answer") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_assigning_answerer_wrong(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_answering_assigning_answerer_wrong, not a feature of this contract") - return - - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - - last_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - - arb_answer = to_answer_for_contract(123456) - arb_payer = keys.privtoaddr(t.k2) - - hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, 0) - st['answer'].insert(0, arb_answer) - st['addr'].insert(0, arb_payer) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(arb_payer).call(), 2+subfee(4)+subfee(8)+subfee(16)+1000, "The arb payer gets it all for a wrong answer") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_assigning_answerer_unrevealed_commit(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_answering_assigning_answerer_unrevealed_commit, not a feature of this contract") - return - - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - - last_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 0, 8, 16, k4, True) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - - arb_answer = to_answer_for_contract(0) - arb_payer = keys.privtoaddr(t.k2) - - ##self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(st['answer'][-1], to_answer_for_contract(1001)) - hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - - # Arbitration fails if the reveal timeout has not come yet - with self.assertRaises(TransactionFailed): - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - self._advance_clock(10) - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, arb_answer, arb_payer, st['hash'][0], st['answer'][0], st['addr'][0] ).transact() - - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, 0) - st['answer'].insert(0, arb_answer) - st['addr'].insert(0, arb_payer) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(arb_payer).call(), 2+subfee(4)+subfee(8)+subfee(16)+1000, "The arb payer gets it all for a wrong answer") - - - return - - # The arbitrator cannot submit an answer that has not been requested. - # (If they really want to do this, they can always pay themselves for arbitration.) - with self.assertRaises(TransactionFailed): - self.arb0.functions.assignWinnerAndSubmitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), keys.privtoaddr(t.k0)).transact() - - # You cannot notify realitio of arbitration unless you are the arbitrator - with self.assertRaises(TransactionFailed): - self.rc0.functions.notifyOfArbitrationRequest(self.question_id, keys.privtoaddr(t.k0), 0).transact() - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - question = self.rc0.functions.questions(self.question_id).call() - #self.assertEqual(question[QINDEX_FINALIZATION_TS], 1, "When arbitration is pending for an answered question, we set the finalization_ts to 1") - self.assertTrue(question[QINDEX_IS_PENDING_ARBITRATION], "When arbitration is pending for an answered question, we set the is_pending_arbitration flag to True") - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), keys.privtoaddr(t.k0)).transact() - - self.assertTrue(self.rc0.functions.isFinalized(self.question_id).call()) - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 123456, "Arbitrator submitting final answer calls finalize") - - self.assertNotEqual(self.rc0.functions.questions(self.question_id).call()[QINDEX_BOND], 0) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_single_unrevealed_commit(self): - - if VERNUM < 3.0: - print("Skipping test_arbitrator_answering_assigning_answerer_single_unrevealed_commit, not a feature of this contract") - return - - k0 = self.l2web3.eth.accounts[0] - k2 = self.l2web3.eth.accounts[2] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k2, 100000, 50000) - self._issueTokens(self.l2token1, k3, 100000, 50000) - self._issueTokens(self.l2token1, k4, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4, True) - nonce = st['nonce'][0] - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - txid = self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - with self.assertRaises(TransactionFailed): - self.raiseOnZeroStatus(txid) - - txid = self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1001), nonce, 2).transact(self._txargs(sender=k4)) - self.raiseOnZeroStatus(txid) - - txid = self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - self.raiseOnZeroStatus(txid) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_cancel(self): - - if VERNUM < 2.1: - print("Skipping test_arbitrator_cancel, not a feature of this contract") - return - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0, 1).transact(self._txargs()) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - - # The arbitrator cannot submit an answer that has not been requested. - # (If they really want to do this, they can always pay themselves for arbitration.) - with self.assertRaises(TransactionFailed): - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), keys.privtoaddr(t.k0)).transact() - - # The arbitrator cannot cancel arbitration that has not been requested - with self.assertRaises(TransactionFailed): - self.arb0.functions.cancelArbitration(self.question_id).transact() - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration") - question = self.rc0.functions.questions(self.question_id).call() - #self.assertEqual(question[QINDEX_FINALIZATION_TS], 1, "When arbitration is pending for an answered question, we set the finalization_ts to 1") - self.assertTrue(question[QINDEX_IS_PENDING_ARBITRATION], "When arbitration is pending for an answered question, we set the is_pending_arbitration flag to True") - - # Only the arbitrator can cancel arbitration - with self.assertRaises(TransactionFailed): - self.rc0.functions.cancelArbitration(self.question_id).transact() - - cancelled_ts = self._block_timestamp() - self.arb0.functions.cancelArbitration(self.question_id).transact(); - question = self.rc0.functions.questions(self.question_id).call() - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - # The arbitrator cannot cancel arbitration again as it is no longer pending arbitratin - with self.assertRaises(TransactionFailed): - self.arb0.functions.cancelArbitration(self.question_id).transact() - - self.assertFalse(question[QINDEX_IS_PENDING_ARBITRATION], "When arbitration has been cancelled, is_pending_arbitration flag is set back to False") - self.assertEqual(question[QINDEX_FINALIZATION_TS], cancelled_ts + 30, "Cancelling arbitration extends the timeout") - - # You can submit answers again - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(54321), 0, 2).transact(self._txargs()) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(54321), 0).transact(self._txargs(val=2)) - - # You can request arbitration again - self.assertTrue(self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)), "Requested arbitration again") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_answering_unanswered(self): - - with self.assertRaises(TransactionFailed): - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(123456), self.arb0.address).transact() - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - self.assertEqual(self.rc0.functions.questions(self.question_id).call()[QINDEX_BOND], 0) - - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - - # TODO: This doesn't do anything, which is OK, but why doesn't it raise a TransactionFailed?? - #with self.assertRaises(TransactionFailed): - self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - - self.assertFalse(self.rc0.functions.questions(self.question_id).call()[QINDEX_IS_PENDING_ARBITRATION]) - - def submitAnswerReturnUpdatedState(self, st, qid, ans, max_last, bond, sdr, is_commitment = False, is_arbitrator = False, skip_sender = False, tx_acct = None): - - if tx_acct is None: - tx_acct = sdr - - if st is None: - st = { - 'addr': [], - 'bond': [], - 'answer': [], - 'hash': [], - 'nonce': [], # only for commitments - } - - # ANSWERED_TOO_SOON_VAL is already encoded - # For anything else we pass in an int which we should hex-encode - if ans == ANSWERED_TOO_SOON_VAL: - encoded_ans = ans - else: - encoded_ans = to_answer_for_contract(ans) - - hist_hash = self.rc0.functions.questions(qid).call()[QINDEX_HISTORY_HASH] - st['hash'].insert(0, hist_hash) - st['bond'].insert(0, bond) - st['answer'].insert(0, encoded_ans) - st['addr'].insert(0, sdr) - nonce = None - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - if ERC20: - - if is_commitment: - nonce = 1234 - answer_hash = calculate_answer_hash(encoded_ans, nonce) - commitment_id = calculate_commitment_id(decode_hex(self.question_id[2:]), decode_hex(answer_hash[2:]), bond) - if skip_sender: - txid = self.rc0.functions.submitAnswerCommitmentERC20(qid, decode_hex(answer_hash[2:]), max_last, NULL_ADDRESS - ,bond - ).transact(self._txargs(sender=tx_acct)) - self.raiseOnZeroStatus(txid) - else: - txid = self.rc0.functions.submitAnswerCommitmentERC20(qid, decode_hex(answer_hash[2:]), max_last, sdr - ,bond - ).transact(self._txargs(sender=tx_acct)) - self.raiseOnZeroStatus(txid) - st['answer'][0] = decode_hex(commitment_id[2:]) - else: - if is_arbitrator: - txid = self.arb0.functions.submitAnswerByArbitrator(qid, encoded_ans, 0, 0, sdr).transact(self._txargs(sender=tx_acct)) - self.raiseOnZeroStatus(txid) - else: - txid = self.rc0.functions.submitAnswerERC20(qid, encoded_ans, max_last - ,bond - ).transact(self._txargs(sender=tx_acct)) - self.raiseOnZeroStatus(txid) - - else: - - if is_commitment: - nonce = 1234 - answer_hash = calculate_answer_hash(encoded_ans, nonce) - commitment_id = calculate_commitment_id(decode_hex(self.question_id[2:]), decode_hex(answer_hash[2:]), bond) - #self.assertEqual(to_answer_for_contract(ans), commitment_id) - if skip_sender: - self.rc0.functions.submitAnswerCommitment(qid, decode_hex(answer_hash[2:]), max_last, NULL_ADDRESS).transact(self._txargs(val=bond, sender=sdr)) - else: - self.rc0.functions.submitAnswerCommitment(qid, decode_hex(answer_hash[2:]), max_last, sdr).transact(self._txargs(val=bond, sender=sdr)) - st['answer'][0] = decode_hex(commitment_id[2:]) - else: - if is_arbitrator: - self.arb0.functions.submitAnswerByArbitrator(qid, encoded_ans, 0, 0, sdr).transact(self._txargs(val=bond, sender=sdr)) - else: - self.rc0.functions.submitAnswer(qid, encoded_ans, max_last).transact(self._txargs(val=bond, sender=sdr)) - - st['nonce'].insert(0, nonce) - return st - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_same_person_repeating_self(self): - st = None - - sdr = self.l2web3.eth.accounts[3] - - if ERC20: - self._issueTokens(self.l2token1, sdr, 100000, 50000) - - ##hist_hash = self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH] - ##self.assertEqual(hist_hash, '0x0000000000000000000000000000000000000000000000000000000000000000') - ##st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, t.k3) - ##self.assertEqual(st['hash'][0], hist_hash) - ##sdr = self.l2web3.eth.accounts[0] - - ##bond = 1 - ##ans = 0 - ##qid = self.question_id - ##max_last = 0 - ##self.rc0.functions.submitAnswer(qid, to_answer_for_contract(ans), max_last).transact(self._txargs(val=bond, sender=sdr)) - ##new_hist_hash = "0x"+encode_hex(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - ##new_hash = calculate_history_hash(hist_hash, to_answer_for_contract(ans), bond, sdr, False) - ##self.assertEqual(new_hash, new_hist_hash) - - ##self._advance_clock(33) - ##self.rc0.functions.claimWinnings(self.question_id, [hist_hash], [sdr], [bond], [to_answer_for_contract(ans)]).transact() - ##return - - ##print(st) - - ##self._advance_clock(33) - ##self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - ##return - - #hist_hash = "0x" + encode_hex(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 20, sdr) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 20, 40, sdr) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 40, 80, sdr) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 80, 160, sdr) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 160, 320, sdr) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 320, 640, sdr) - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(sdr).call(), 640+subfee(320)+subfee(160)+subfee(80)+subfee(40)+subfee(20)+1000) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_same_person_contradicting_self(self): - k3 = self.l2web3.eth.accounts[3] - - if ERC20: - self._issueTokens(self.l2token1, k3, 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, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1004, 80, 160, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1003, 160, 320, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 320, 640, k3) - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 640+subfee(320)+subfee(160)+subfee(80)+subfee(40)+subfee(20)+1000) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_subfee(self): - if CLAIM_FEE == 0: - return - else: - self.assertEqual(subfee(100), 98) - self.assertEqual(subfee(1), 1) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_set_dispute_fee(self): - - # fee of 0 should mean you can never request arbitration - self.arb0.functions.setDisputeFee(0).transact() - with self.assertRaises(TransactionFailed): - fee = self.arb0.functions.getDisputeFee(decode_hex("0x00")).call() - txid = self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - self.raiseOnZeroStatus(txid) - - self.arb0.functions.setDisputeFee(123).transact() - self.assertEqual(self.arb0.functions.getDisputeFee(self.question_id).call(), 123) - - # question-specific fee should work for that question - self.arb0.functions.setCustomDisputeFee(self.question_id, 23).transact() - self.assertEqual(self.arb0.functions.getDisputeFee(self.question_id).call(), 23) - - # removing custom fee should resurrect the default fee - self.arb0.functions.setCustomDisputeFee(self.question_id, 0).transact() - self.assertEqual(self.arb0.functions.getDisputeFee(self.question_id).call(), 123) - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitration_max_previous(self): - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - - - fee = self.arb0.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - with self.assertRaises(TransactionFailed): - txid = self.arb0.functions.requestArbitration(self.question_id, 2).transact(self._txargs(val=fee)) - self.raiseOnZeroStatus(txid) - self.arb0.functions.requestArbitration(self.question_id, 4).transact(self._txargs(val=fee)) - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_arbitration_existing_none(self): - fee = self.arb0.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - with self.assertRaises(TransactionFailed): - txid = self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - self.raiseOnZeroStatus(txid) - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_arbitration_existing_final(self): - fee = self.arb0.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4) - - self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - - st['hash'].insert(0, self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - st['addr'].insert(0, k4) - st['bond'].insert(0, 0) - st['answer'].insert(0, to_answer_for_contract(1001)) - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(1001), k4).transact() - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 16+8+4+2+1000) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_split_over_transactions(self): - - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4) - - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, st['hash'][:2], st['addr'][:2], st['bond'][:2], st['answer'][:2]).transact() - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 16+1000) - self.rc0.functions.claimWinnings(self.question_id, st['hash'][2:], st['addr'][2:], st['bond'][2:], st['answer'][2:]).transact() - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 16+8+4+2+1000) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_after_reveal_fail(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k6 = self.l2web3.eth.accounts[6] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k6, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 1, 2, k5, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1003, 2, 4, k4, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k6, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1004, 8, 16, k5, True) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 16, 32, k4, True) - - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k6).call(), 32+16+8+4+2-1+1000) - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 1+1) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_split_over_transactions_payee_later(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k6 = self.l2web3.eth.accounts[6] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k6, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 1, 2, k5, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1003, 2, 4, k4, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k6, False) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1004, 8, 16, k5, True) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 16, 32, k4, True) - - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, st['hash'][:2], st['addr'][:2], st['bond'][:2], st['answer'][:2]).transact() - self.rc0.functions.claimWinnings(self.question_id, st['hash'][2:], st['addr'][2:], st['bond'][2:], st['answer'][2:]).transact() - self.assertEqual(self.rc0.functions.balanceOf(k6).call(), 32+16+8+4+2-1+1000) - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 1+1) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_reveal_calculation(self): - h = calculate_answer_hash(to_answer_for_contract(1003), 94989) - self.assertEqual(h, '0x23e796d2bf4f5f890b1242934a636f4802aadd480b6f83c754d2bd5920f78845') - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_commit_normal(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k6 = self.l2web3.eth.accounts[6] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k6, 1000000, 1000000) - - self.l2web3.testing.mine() - self.assertEqual(self.rc0.functions.questions(self.question_id).call()[QINDEX_STEP_DELAY], 30) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, True) - nonce = st['nonce'][0] - hh = st['hash'][0] - commitment_id = st['answer'][0] - - comm = self.rc0.functions.commitments(commitment_id).call() - reveal_ts = comm[0] - self.assertTrue(reveal_ts > 0) - - self.l2web3.testing.mine() - self.assertTrue(reveal_ts > self._block_timestamp()) - - with self.assertRaises(TransactionFailed): - q = self.rc0.functions.getFinalAnswer(self.question_id).call() - - #print(self.rc0.functions.questions(self.question_id).call()) - txid = self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1002), nonce, 1).transact(self._txargs(sender=k3, val=0)) - - comm = self.rc0.functions.commitments(commitment_id).call() - reveal_ts = comm[0] - is_revealed = comm[1] - revealed_answer = comm[2] - self.assertTrue(reveal_ts > 0) - self.assertTrue(is_revealed) - self.assertEqual(revealed_answer, to_answer_for_contract(1002)) - - - self.raiseOnZeroStatus(txid) - - #rcp = self.l2web3.eth.get_transaction_receipt(txid) - self._advance_clock(33) - #time.sleep(10) - - q = self.rc0.functions.questions(self.question_id).call()[QINDEX_BEST_ANSWER] - self.assertEqual(from_answer_for_contract(q), 1002) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 1001) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_commit_skip_sender(self): - st = None - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k6 = self.l2web3.eth.accounts[6] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k6, 1000000, 1000000) - - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, True, False, True) - nonce = st['nonce'][0] - hh = st['hash'][0] - - with self.assertRaises(TransactionFailed): - q = self.rc0.functions.getFinalAnswer(self.question_id).call() - - self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1002), nonce, 1).transact(self._txargs(sender=k3, val=0)) - - self._advance_clock(33) - - q = self.rc0.functions.getFinalAnswer(self.question_id).call() - self.assertEqual(from_answer_for_contract(q), 1002) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 1001) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_no_answer_no_commit(self): - - k3 = self.l2web3.eth.accounts[3] - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, True) - nonce = st['nonce'][0] - hh = st['hash'][0] - - with self.assertRaises(TransactionFailed): - q = self.rc0.functions.getFinalAnswer(self.question_id).call() - - self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1002), nonce, 1).transact(self._txargs(sender=k3, val=0)) - self._advance_clock(33) - - q = self.rc0.functions.getFinalAnswer(self.question_id).call() - self.assertEqual(from_answer_for_contract(q), 1002) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 1001) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_commit_expired(self): - - k3 = self.l2web3.eth.accounts[3] - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, True) - nonce = st['nonce'][0] - hh = st['hash'][0] - - self._advance_clock(5) - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1002), nonce, 1).transact(self._txargs(sender=k3, val=0)) - self.raiseOnZeroStatus(txid) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_commit_with_arbitration_pending(self): - - k3 = self.l2web3.eth.accounts[3] - if ERC20: - self._issueTokens(self.l2token1, k3, 1000, 1000) - - fee = self.arb0.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 0, 1, k3, True) - nonce = st['nonce'][0] - hh = st['hash'][0] - - self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - - #with self.assertRaises(TransactionFailed): - st = self.rc0.functions.submitAnswerReveal( self.question_id, to_answer_for_contract(1002), nonce, 1).transact(self._txargs(sender=k3, val=0)) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_token_deductions_no_balance(self): - - if not ERC20: - return - - k0 = self.l2web3.eth.accounts[0] - k3 = self.l2web3.eth.accounts[3] - bal = self.l2token0.functions.balanceOf(k3).call() - self.assertEqual(bal, 0) - - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1000 - ).transact(self._txargs(sender=k3)) - self.raiseOnZeroStatus(txid) - - self._issueTokens(self.l2token1, k3, 500, 500) - - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1000 - ).transact(self._txargs(sender=k3)) - self.raiseOnZeroStatus(txid) - - self._issueTokens(self.l2token1, k3, 501, 1001) - - bal = self.l2token0.functions.balanceOf(k3).call() - self.assertEqual(bal, 1001) - - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1000 - ).transact(self._txargs(sender=k3)) - self.raiseOnZeroStatus(txid) - - bal = self.l2token0.functions.balanceOf(k3).call() - self.assertEqual(bal, 1) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_token_deductions_with_existing_balance(self): - - if not ERC20: - return - - k3 = self.l2web3.eth.accounts[3] - self._setup_balance(k3, 1000) - - start_rcbal = self.rc0.functions.balanceOf(k3).call() - start_tbal = self.l2token0.functions.balanceOf(k3).call() - - self.assertEqual(start_rcbal, 1000) - - # There's enough in the balance, so this will deduct from the balance and leave the token alone - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact(self._txargs(sender=k3)) - - rcbal = self.rc0.functions.balanceOf(k3).call() - self.assertEqual(rcbal, 999) - tbal = self.l2token0.functions.balanceOf(k3).call() - self.assertEqual(tbal, start_tbal) - - # Sets the approval to 500, and makes sure there at least 500 - self._issueTokens(self.l2token1, k3, 500, 500) - start_tbal = self.l2token0.functions.balanceOf(k3).call() - - # We have 999 in the balance and only 500 approved, so this should fail - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,2000 - ).transact(self._txargs(sender=k3)) - self.raiseOnZeroStatus(txid) - - - start_tbal = self.l2token0.functions.balanceOf(k3).call() - - # This will consume all the remaining balance, plus take 1 from the token - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1000 - ).transact(self._txargs(sender=k3)) - self.raiseOnZeroStatus(txid) - - rcbal = self.rc0.functions.balanceOf(k3).call() - self.assertEqual(rcbal, 0) - tbal = self.l2token0.functions.balanceOf(k3).call() - self.assertEqual(tbal, start_tbal - 1) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_claim_arbitration_existing_not_final(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - - fee = self.arb0.functions.getDisputeFee(Web3.to_bytes(hexstr=NULL_BYTES)).call() - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k4) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1002, 4, 8, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k4) - - self.arb0.functions.requestArbitration(self.question_id, 0).transact(self._txargs(val=fee)) - - st['hash'].insert(0, self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - st['addr'].insert(0, k3) - st['bond'].insert(0, 0) - st['answer'].insert(0, to_answer_for_contract(1002)) - self.arb0.functions.submitAnswerByArbitrator(self.question_id, to_answer_for_contract(1002), k3).transact(self._txargs(val=0)) - - self.rc0.functions.claimWinnings(self.question_id, st['hash'], st['addr'], st['bond'], st['answer']).transact() - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 16+8+4+2+1000) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_min_payment_with_bond_param(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact() - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10001), 0 - ,2 - ).transact(self._txargs(sender=k3)) - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10002), 0 - ,5 - ).transact(self._txargs(sender=k4)) - - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10002), 5, - 22+5 - ).transact(self._txargs(sender=k5)) - - else: - - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10001), 0).transact(self._txargs(val=2, sender=k3)) - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10002), 0).transact(self._txargs(val=5, sender=k4)) - - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10002), 5).transact(self._txargs(val=(22+5), sender=k5)) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_simple_bond_claim(self): - - k0 = self.l2web3.eth.accounts[0] - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,3 - ).transact() - - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=3)) - - self._advance_clock(33) - - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 12345) - - self.rc0.functions.claimWinnings(self.question_id, [decode_hex("0x00")], [k0], [3], [to_answer_for_contract(12345)]).transact() - self.assertEqual(self.rc0.functions.balanceOf(k0).call(), 3+1000) - self.assertEqual(self.rc0.functions.balanceOf(k0).call(), 3+1000, "Winner gets their bond back plus the bounty") - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bonds(self): - - k0 = self.l2web3.eth.accounts[0] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - - claim_args_state = [] - claim_args_addrs = [] - claim_args_bonds = [] - claim_args_answs = [] - - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), 0) - - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0, - 0 - ).transact() - else: - txid = self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=0)) - - self.raiseOnZeroStatus(txid) - - claim_args_state.append(decode_hex("0x00")) - claim_args_addrs.append(k0) - claim_args_bonds.append(1) - claim_args_answs.append(to_answer_for_contract(12345)) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact() - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - - - # "You must increase" - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10001), 0 - ,1 - ).transact(self._txargs(sender=k3)) - else: - txid = self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10001), 0).transact(self._txargs(val=1, sender=k3)) - self.raiseOnZeroStatus(txid) - - claim_args_state.append(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - claim_args_addrs.append(k3) - claim_args_bonds.append(2) - claim_args_answs.append(to_answer_for_contract(10001)) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10001), 0 - ,2 - ).transact(self._txargs(sender=k3)) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10001), 0).transact(self._txargs(val=2, sender=k3)) - - # We will ultimately finalize on this answer - claim_args_state.append(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - claim_args_addrs.append(k4) - claim_args_bonds.append(4) - claim_args_answs.append(to_answer_for_contract(10002)) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10002), 0 - ,4 - ).transact(self._txargs(sender=k4)) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10002), 0).transact(self._txargs(val=4, sender=k4)) - - if ERC20: - # You have to at least double - with self.assertRaises(TransactionFailed): - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10003), 0 - ,7 - ).transact() - - # You definitely can't drop back to zero - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10004), 0 - ,0 - ).transact() - self.raiseOnZeroStatus(txid) - - else: - # You have to at least double - ###with self.assertRaises(TransactionFailed): - ### self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10003), 0).transact(self._txargs(val=7)) - self.assertZeroStatus(self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10003), 0).transact(self._txargs(val=7))) - - # You definitely can't drop back to zero - ###with self.assertRaises(TransactionFailed): - ### self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10004), 0).transact(self._txargs(val=0)) - self.assertZeroStatus(self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10004), 0).transact(self._txargs(val=0))) - - claim_args_state.append(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - claim_args_addrs.append(k3) - claim_args_bonds.append(11) - claim_args_answs.append(to_answer_for_contract(10005)) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10005), 0 - ,11 - ).transact(self._txargs(sender=k3)) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10005), 0).transact(self._txargs(val=11, sender=k3)) - - # The extra amount you have to send should be passed in a parameters - #with self.assertRaises(TransactionFailed): - # self.rc0.submitAnswer(self.question_id, to_answer_for_contract(10002), 0, value=(22+5), sender=t.k5, startgas=200000) - - claim_args_state.append(self.rc0.functions.questions(self.question_id).call()[QINDEX_HISTORY_HASH]) - claim_args_addrs.append(k5) - claim_args_bonds.append(22) - claim_args_answs.append(to_answer_for_contract(10002)) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(10002), 11 - ,22 - ).transact(self._txargs(sender=k5)) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(10002), 11).transact(self._txargs(val=22, sender=k5)) - - self.assertFalse(self.rc0.functions.isFinalized(self.question_id).call()) - - #You can't claim the bond until the thing is finalized - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.claimWinnings(self.question_id, claim_args_state[::-1], claim_args_addrs[::-1], claim_args_bonds[::-1], claim_args_answs[::-1]).transact() - self.raiseOnZeroStatus(txid) - - self._advance_clock(33) - - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(self.question_id).call()), 10002) - - # First right answerer gets: - # - their bond back (4) - # - their bond again (4) - # - the accumulated bonds until their last answer (1 + 2) - - k4bal = 4 + 4 + 1 + 2 - self.rc0.functions.claimWinnings(self.question_id, claim_args_state[::-1], claim_args_addrs[::-1], claim_args_bonds[::-1], claim_args_answs[::-1]).transact() - - self.assertEqual(self.rc0.functions.balanceOf(k4).call(), k4bal, "First answerer gets double their bond, plus earlier bonds") - - # Final answerer gets: - # - their bond back (22) - # - the bond of the previous guy, who was wrong (11) - # - ...minus the payment to the lower guy (-4) - k5bal = 22 + 11 - 4 + 1000 - self.assertEqual(self.rc0.functions.balanceOf(k5).call(), k5bal, "Final answerer gets the bounty, plus their bond, plus earlier bonds up to when they took over the answer, minus the bond of the guy lower down with the right answer") - - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 0, "Wrong answerers get nothing") - - if ERC20: - starting_bal = self.l2token0.functions.balanceOf(k5).call() - else: - starting_bal = self.l2web3.eth.get_balance(k5) - - txid = self.rc0.functions.withdraw().transact(self._txargs(sender=k5)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_spent = rcpt['cumulativeGasUsed'] - - if ERC20: - ending_bal = self.l2token0.functions.balanceOf(k5).call() - self.assertEqual(ending_bal, starting_bal + k5bal) - else: - ending_bal = self.l2web3.eth.get_balance(k5) - self.assertEqual(ending_bal, starting_bal + k5bal - gas_spent) - - self.assertEqual(self.rc0.functions.balanceOf(k5).call(), 0) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_bond_bulk_withdrawal_other_user(self): - - k3 = self.l2web3.eth.accounts[3] - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - self._issueTokens(self.l2token1, k3, 100000, 50000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 4, 8, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 16, 32, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 32, 64, k3) - claimable = 64+32+16+8+4+2+1000 - - self._advance_clock(33) - - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 0) - - # Have an unconnected user do the claim - # This will leave the balance in the contract rather than withdrawing it - self.rc0.functions.claimMultipleAndWithdrawBalance([self.question_id], [len(st['hash'])], st['hash'], st['addr'], st['bond'], st['answer']).transact(self._txargs(sender=k5)) - - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), claimable) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_withdrawal(self): - - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - - if ERC20: - self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,100 - ).transact(self._txargs(sender=k5)) - else: - self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=100, sender=k5)) - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, [decode_hex("0x00")], [k5], [100], [to_answer_for_contract(12345)]).transact(self._txargs(sender=k5)) - - starting_deposited = self.rc0.functions.balanceOf(k5).call() - self.assertEqual(starting_deposited, 1100) - - gas_used = 0 - if ERC20: - starting_bal = self.l2token0.functions.balanceOf(k5).call() - else: - starting_bal = self.l2web3.eth.get_balance(k5) - - txid = self.rc0.functions.withdraw().transact(self._txargs(sender=k5)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = rcpt['cumulativeGasUsed'] - - if ERC20: - ending_bal = self.l2token0.functions.balanceOf(k5).call() - else: - ending_bal = self.l2web3.eth.get_balance(k5) - - self.assertEqual(self.rc0.functions.balanceOf(k5).call(), 0) - - if ERC20: - self.assertEqual(ending_bal, starting_bal + starting_deposited) - else: - self.assertEqual(ending_bal, starting_bal + starting_deposited - gas_used) - - return - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_submit_answer_for_withdrawal(self): - - if VERNUM < 2.1: - print("Skipping test_submit_answer_for_withdrawal, submitAnswerFor is not a feature of this contract") - return - - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - - if ERC20: - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerForERC20(self.question_id, to_answer_for_contract(12345), 0, "0x0000000000000000000000000000000000000000", 100).transact(self._txargs(sender=k4)) - self.raiseOnZeroStatus(txid) - else: - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.submitAnswerFor(self.question_id, to_answer_for_contract(12345), 0, "0x0000000000000000000000000000000000000000").transact(self._txargs(val=100, sender=k4)) - self.raiseOnZeroStatus(txid) - - return - - if ERC20: - self.rc0.functions.submitAnswerForERC20(self.question_id, to_answer_for_contract(12345), 0, k5, 100).transact(self._txargs(sender=k4)) - else: - self.rc0.functions.submitAnswerFor(self.question_id, to_answer_for_contract(12345), 0, k5).transact(self._txargs(val=100, sender=k4)) - - self._advance_clock(33) - self.rc0.functions.claimWinnings(self.question_id, [decode_hex("0x00")], [k5], [100], [to_answer_for_contract(12345)]).transact(self._txargs(sender=k5)) - - starting_deposited = self.rc0.functions.balanceOf(k5).call() - self.assertEqual(starting_deposited, 1100) - - gas_used = 0 - - if ERC20: - starting_bal = self.l2token0.functions.balanceOf(k5).call() - else: - starting_bal = self.l2web3.eth.get_balance(k5) - - txid = self.rc0.functions.withdraw().transact(self._txargs(sender=k5)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = rcpt['cumulativeGasUsed'] - - if ERC20: - ending_bal = self.l2token0.functions.balanceOf(k5).call() - else: - ending_bal = self.l2web3.eth.get_balance(k5) - - self.assertEqual(self.rc0.functions.balanceOf(k5).call(), 0) - - if ERC20: - self.assertEqual(ending_bal, starting_bal + starting_deposited) - else: - self.assertEqual(ending_bal, starting_bal + starting_deposited - gas_used) - - return - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_fee_received(self): - self.assertEqual(self.rc0.functions.balanceOf(self.arb0.address).call(), 100) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_non_erc_ask_question_id(self): - - # ERC20 also supports a plain askQuestion without a bounty - expected_question_id = calculate_question_id(self.rc0.address, 0, "my question x", self.arb0.address, 30, 0, 0, self.l2web3.eth.accounts[0], 0) - - # Non-ERC version only has one method which is already tested elsewhere - if not ERC20: - return - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - # There's a question fee so this should fail - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.askQuestion( - 0, - "my question x", - self.arb0.address, - 30, - 0, - 0 - ).transact(self._txargs()) - self.raiseOnZeroStatus(txid) - - self.assertEqual(self.rc0.functions.questions(expected_question_id).call()[QINDEX_ARBITRATOR], NULL_ADDRESS) - - self.arb0.functions.setQuestionFee(0).transact() - - txid = self.rc0.functions.askQuestion( - 0, - "my question x", - self.arb0.address, - 30, - 0, - 0 - ).transact(self._txargs()) - self.raiseOnZeroStatus(txid) - self.assertNotEqual(self.rc0.functions.questions(expected_question_id).call()[QINDEX_ARBITRATOR], NULL_ADDRESS, "We have a question at the expected address") - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_ask_question_gas(self): - - if ERC20: - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 2", - self.arb0.address, - 10, - 0, - 0 - ,1100 - ).transact() - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = rcpt['cumulativeGasUsed'] - self.assertTrue(gas_used < 140000) - else: - txid = self.rc0.functions.askQuestion( - 0, - "my question 2", - self.arb0.address, - 10, - 0, - 0 - ).transact(self._txargs(val=1100)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = rcpt['cumulativeGasUsed'] - #self.assertEqual(gas_used, 120000) - self.assertTrue(gas_used < 110000) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_answer_question_gas(self): - - if ERC20: - - txid = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12345), 0 - ,1 - ).transact() - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - - self.assertTrue(rcpt['cumulativeGasUsed'] < 140000) - - # NB The second answer should be cheaper than the first. - # This is what we want, because you may need to be able to get a challenge through at busy times - - txid2 = self.rc0.functions.submitAnswerERC20(self.question_id, to_answer_for_contract(12346), 0 - ,2 - ).transact() - rcpt = self.l2web3.eth.get_transaction_receipt(txid2) - self.assertTrue(rcpt['cumulativeGasUsed'] < 80000) - - else: - - txid = self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - - self.assertTrue(rcpt['cumulativeGasUsed'] < 103000) - - # NB The second answer should be cheaper than the first. - # This is what we want, because you may need to be able to get a challenge through at busy times - - txid2 = self.rc0.functions.submitAnswer(self.question_id, to_answer_for_contract(12346), 0).transact(self._txargs(val=2)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid2) - self.assertTrue(rcpt['cumulativeGasUsed'] < 56000) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_question_fee_withdrawal(self): - - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k7 = self.l2web3.eth.accounts[7] - - if ERC20: - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k7, 1000000, 1000000) - - start_bal = self.rc0.functions.balanceOf(self.arb0.address).call() - self.arb0.functions.setQuestionFee(321).transact() - - - if ERC20: - - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 3", - self.arb0.address, - 10, - 0, - 0 - ,1000 - ).transact(self._txargs(sender=k4)) - - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 4", - self.arb0.address, - 10, - 0, - 0 - ,2000 - ).transact(self._txargs(sender=k5)) - - end_bal = self.rc0.functions.balanceOf(self.arb0.address).call() - self.assertEqual(end_bal - start_bal, (321*2)) - - start_arb_bal = self.l2token0.functions.balanceOf(self.arb0.address).call() - txid = self.arb0.functions.callWithdraw().transact(self._txargs(sender=k7)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - end_arb_bal = self.l2token0.functions.balanceOf(self.arb0.address).call() - - self.assertEqual(end_arb_bal - start_arb_bal, 100 + (321*2)) - self.assertEqual(self.rc0.functions.balanceOf(self.arb0.address).call(), 0) - - else: - - question_id = self.rc0.functions.askQuestion( - 0, - "my question 3", - self.arb0.address, - 10, - 0, - 0 - ).transact(self._txargs(val=1000, sender=k4)) - - question_id = self.rc0.functions.askQuestion( - 0, - "my question 4", - self.arb0.address, - 10, - 0, - 0 - ).transact(self._txargs(val=2000, sender=k5)) - - end_bal = self.rc0.functions.balanceOf(self.arb0.address).call() - self.assertEqual(end_bal - start_bal, (321*2)) - - start_arb_bal = self.l2web3.eth.get_balance(self.arb0.address) - txid = self.arb0.functions.callWithdraw().transact(self._txargs(sender=k7)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - end_arb_bal = self.l2web3.eth.get_balance(self.arb0.address) - - self.assertEqual(end_arb_bal - start_arb_bal, 100 + (321*2)) - self.assertEqual(self.rc0.functions.balanceOf(self.arb0.address).call(), 0) - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_question_fees(self): - - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - - if ERC20: - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - - # Treat k5 as the arbitrator for these purposes, although really the arbitrator would be a contract - self.rc0.functions.setQuestionFee(123).transact(self._txargs(sender=k5)) - - if ERC20: - # Should fail with insufficient payment to cover the question fee - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 2", - k5, - 10, - 0, - 0 - ,122 - ).transact( self._txargs(sender=k4)) - self.raiseOnZeroStatus(txid) - - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 2", - k5, - 10, - 0, - 0 - ,126 - ).transact(self._txargs(sender=k4)) - - else: - - # Should fail with insufficient payment to cover the question fee - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.askQuestion( - 0, - "my question 2", - k5, - 10, - 0, - 0 - ).transact( self._txargs(val=122, sender=k4)) - self.raiseOnZeroStatus(txid) - - txid = self.rc0.functions.askQuestion( - 0, - "my question 2", - k5, - 10, - 0, - 0 - ).transact(self._txargs(val=126, sender=k4)) - - question_id = calculate_question_id(self.rc0.address, 0, "my question 2", k5, 10, 0, 0, k4, 0) - - bounty = self.rc0.functions.questions(question_id).call()[QINDEX_BOUNTY] - self.assertEqual(bounty, 126-123, "The bounty is what's left after the question fee is deducted") - - if ERC20: - question_id = self.rc0.functions.askQuestionERC20( - 0, - "my question 3", - k5, - 10, - 0, - 0 - ,122 - ).transact(self._txargs(sender=k5)) - else: - question_id = self.rc0.functions.askQuestion( - 0, - "my question 3", - k5, - 10, - 0, - 0 - ).transact(self._txargs(val=122, sender=k5)) - - - question_id = calculate_question_id(self.rc0.address, 0, "my question 3", k5, 10, 0, 0, k5, 0) - - bounty = self.rc0.functions.questions(question_id).call()[QINDEX_BOUNTY] - self.assertEqual(bounty, 122, "The arbitrator isn't charged their fee, so their whole payment goes to the bounty") - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_minimum_bond(self): - - if VERNUM < 3: - print("Skipping askQuestionWithMinBond, not a feature of this contract") - return - - k0 = self.l2web3.eth.accounts[0] - - if ERC20: - bal_before_in_contract = self.rc0.functions.balanceOf(k0).call() - self.assertEqual(bal_before_in_contract, 0, "nothing in the contract at the start") - bal_before = self.l2token0.functions.balanceOf(k0).call() - txid = self.rc0.functions.askQuestionWithMinBondERC20( - 0, - "my question 2", - self.arb0.address, - 10, - 0, - 0, - 1000, - 1100 - ).transact(self._txargs()) - bal_after = self.l2token0.functions.balanceOf(k0).call() - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - self.assertEqual(bal_after, bal_before - 1100, "New question bounty is deducted") - gas_used = rcpt['cumulativeGasUsed'] - else: - bal_before = self.l2web3.eth.get_balance(k0) - txid = self.rc0.functions.askQuestionWithMinBond( - 0, - "my question 2", - self.arb0.address, - 10, - 0, - 0, - 1000 - ).transact(self._txargs(val=1100)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_used = rcpt['cumulativeGasUsed'] - bal_after = self.l2web3.eth.get_balance(k0) - self.assertEqual(bal_after, bal_before - 1100 - gas_used, "New question bouny is deducted") - - #self.assertEqual(gas_used, 120000) - self.assertTrue(gas_used < 160000) - - expected_question_id = calculate_question_id(self.rc0.address, 0, "my question 2", self.arb0.address, 10, 0, 0, self.l2web3.eth.accounts[0], 1000) - - min_bond = self.rc0.functions.questions(expected_question_id).call()[QINDEX_MIN_BOND] - self.assertEqual(min_bond, 1000) - - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(expected_question_id, to_answer_for_contract(123), 0, 0).transact(self._txargs()) - else: - txid = self.rc0.functions.submitAnswer(expected_question_id, to_answer_for_contract(123), 0).transact(self._txargs(val=0)) - self.raiseOnZeroStatus(txid) - - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(expected_question_id, to_answer_for_contract(1234), 0, 999).transact(self._txargs()) - else: - txid = self.rc0.functions.submitAnswer(expected_question_id, to_answer_for_contract(1234), 0).transact(self._txargs(val=999)) - self.raiseOnZeroStatus(txid) - - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(expected_question_id, to_answer_for_contract(12345), 0, 1000).transact(self._txargs()) - else: - txid = self.rc0.functions.submitAnswer(expected_question_id, to_answer_for_contract(12345), 0).transact(self._txargs(val=1000)) - self.raiseOnZeroStatus(txid) - - best_answer = self.rc0.functions.questions(expected_question_id).call()[QINDEX_BEST_ANSWER] - self.assertEqual(12345, from_answer_for_contract(best_answer)) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_metadata(self): - - k0 = self.l2web3.eth.accounts[0] - k1 = self.l2web3.eth.accounts[1] - - self.assertEqual(self.arb0.functions.metadata().call(), '') - - with self.assertRaises(TransactionFailed): - txid = self.arb0.functions.setMetaData("oink").transact(self._txargs(sender=k1)) - self.raiseOnZeroStatus(txid) - - self.arb0.functions.setMetaData("oink").transact(self._txargs(sender=k0)) - self.assertEqual(self.arb0.functions.metadata().call(), 'oink') - - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_arbitrator_registered_wallet(self): - - k2 = self.l2web3.eth.accounts[2] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - k7 = self.l2web3.eth.accounts[7] - - if ERC20: - self._issueTokens(self.l2token1, k2, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - self._issueTokens(self.l2token1, k5, 1000000, 1000000) - self._issueTokens(self.l2token1, k7, 1000000, 1000000) - - start_bal = self.rc0.functions.balanceOf(self.arb0.address).call() - self.arb0.functions.setQuestionFee(321).transact() - - if ERC20: - question_id = self.rc0.functions.askQuestionERC20( - 0, - "my question 3", - self.arb0.address, - 10, - 0, - 0 - ,1000 - ).transact(self._txargs(sender=k4)) - - question_id = self.rc0.functions.askQuestionERC20( - 0, - "my question 4", - self.arb0.address, - 10, - 0, - 0 - ,2000 - ).transact(self._txargs(sender=k5)) - - else: - question_id = self.rc0.functions.askQuestion( - 0, - "my question 3", - self.arb0.address, - 10, - 0, - 0 - ).transact(self._txargs(val=1000, sender=k4)) - - question_id = self.rc0.functions.askQuestion( - 0, - "my question 4", - self.arb0.address, - 10, - 0, - 0 - ).transact(self._txargs(val=2000, sender=k5)) - - end_bal = self.rc0.functions.balanceOf(self.arb0.address).call() - self.assertEqual(end_bal - start_bal, (321*2)) - - with self.assertRaises(TransactionFailed): - txid = self.arb0.functions.withdrawToRegisteredWallet().transact() - self.raiseOnZeroStatus(txid) - - with self.assertRaises(TransactionFailed): - txid = self.arb0.functions.updateRegisteredWallet(t.a8).transact(self._txargs(sender=k2)) - self.raiseOnZeroStatus(txid) - - self.arb0.functions.updateRegisteredWallet(t.a8).transact() - - if ERC20: - # Skip this for now as we're not actually using the RegisteredWallet - return - - if ERC20: - start_arb_bal = self.l2token0.functions.balanceOf(t.a8).call() - else: - start_arb_bal = self.l2web3.eth.get_balance(t.a8) - - self.arb0.functions.callWithdraw().transact(self._txargs(sender=k7)) - self.arb0.functions.withdrawToRegisteredWallet().transact(self._txargs(sender=k4)) - - if ERC20: - end_arb_bal = self.l2token0.functions.balanceOf(t.a8).call() - else: - end_arb_bal = self.l2web3.eth.get_balance(t.a8) - - self.assertEqual(end_arb_bal - start_arb_bal, (100+321+321)) - self.assertEqual(self.rc0.functions.balanceOf(self.arb0.address).call(), 0) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_null_arbitrator_permitted(self): - - if VERNUM < 3: - return - - NULL_ADDRESS = "0x0000000000000000000000000000000000000000" - - k2 = self.l2web3.eth.accounts[2] - - if ERC20: - self._issueTokens(self.l2token1, k2, 1000000, 1000000) - txid = self.rc0.functions.askQuestionERC20( - 0, - "my question 3", - NULL_ADDRESS, - 10, - 0, - 0, - 1000 - ).transact(self._txargs(sender=k2)) - else: - txid = self.rc0.functions.askQuestion( - 0, - "my question 3", - NULL_ADDRESS, - 10, - 0, - 0 - ).transact(self._txargs(val=1000, sender=k2)) - self.raiseOnZeroStatus(txid) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_reopen_question(self): - - if VERNUM < 3.0: - print("Skipping test_reopen_question, not a feature of this contract") - return - - k0 = self.l2web3.eth.accounts[0] - if ERC20: - self._issueTokens(self.l2token1, k0, 1000000, 1000000) - - if ERC20: - txid = self.rc0.functions.submitAnswerERC20(self.question_id, ANSWERED_TOO_SOON_VAL, 0, 3).transact() - else: - txid = self.rc0.functions.submitAnswer(self.question_id, ANSWERED_TOO_SOON_VAL, 0).transact(self._txargs(val=3)) - self.raiseOnZeroStatus(txid) - - self._advance_clock(33) - - self.assertEqual(encode_hex(self.rc0.functions.resultFor(self.question_id).call()), ANSWERED_TOO_SOON_VAL) - self.assertTrue(self.rc0.functions.isSettledTooSoon(self.question_id).call()) - - with self.assertRaises(TransactionFailed): - self.rc0.functions.resultForOnceSettled(self.question_id).call() - - self.assertEqual(self.rc0.functions.balanceOf(k0).call(), 0) - self.rc0.functions.claimWinnings(self.question_id, [decode_hex("0x00")], [k0], [3], [ANSWERED_TOO_SOON_VAL]).transact() - self.assertEqual(self.rc0.functions.balanceOf(k0).call(), 3, "Winner gets their bond back but no bounty") - - self.assertEqual(self.rc0.functions.reopened_questions(self.question_id).call(), to_answer_for_contract(0), "reopened_questions empty until reopened") - - old_bounty = self.rc0.functions.questions(self.question_id).call()[QINDEX_BOUNTY] - self.assertEqual(old_bounty, 1000) - - # Make one of the details different to the original question and it should fail - if ERC20: - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.reopenQuestionERC20( 0, "not my question", self.arb0.address, 30, 0, 1, 0, self.question_id, 123).transact(self._txargs()) - self.raiseOnZeroStatus(txid) - else: - with self.assertRaises(TransactionFailed): - txid = self.rc0.functions.reopenQuestion( 0, "not my question", self.arb0.address, 30, 0, 1, 0, self.question_id).transact(self._txargs(val=123)) - self.raiseOnZeroStatus(txid) - - expected_reopen_id = calculate_question_id(self.rc0.address, 0, "my question", self.arb0.address, 30, 0, 1, self.l2web3.eth.accounts[0], 0) - - if ERC20: - # withdraw anything we have in contract balance as it complicates the test - self.rc0.functions.withdraw().transact(self._txargs()) - bal_before = self.l2token0.functions.balanceOf(k0).call() - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 1, 0, self.question_id, 123).transact(self._txargs(gas=300000)) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - self.raiseOnZeroStatus(txid) - bal_after = self.l2token0.functions.balanceOf(k0).call() - self.assertEqual(bal_after, bal_before - 123, "New question bounty is deducted") - else: - bal_before = self.l2web3.eth.get_balance(k0) - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 1, 0, self.question_id).transact(self._txargs(val=123)) - self.raiseOnZeroStatus(txid) - rcpt = self.l2web3.eth.get_transaction_receipt(txid) - gas_spent = rcpt['cumulativeGasUsed'] - bal_after = self.l2web3.eth.get_balance(k0) - self.assertEqual(bal_after, bal_before - 123 - gas_spent, "New question bounty is deducted") - txr = self.l2web3.eth.get_transaction_receipt(txid) - - self.assertEqual(self.rc0.functions.reopened_questions(self.question_id).call(), expected_reopen_id, "reopened_questions returns reopened question id") - - old_bounty_now = self.rc0.functions.questions(self.question_id).call()[QINDEX_BOUNTY] - self.assertEqual(old_bounty_now, 0) - - new_bounty = self.rc0.functions.questions(expected_reopen_id).call()[QINDEX_BOUNTY] - question_fee = 100 - self.assertEqual(new_bounty, old_bounty + 123 - question_fee) - - # Second time should fail - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 1, 0, self.question_id, 123).transact(self._txargs()) - else: - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 1, 0, self.question_id).transact(self._txargs(val=123)) - - self.raiseOnZeroStatus(txid) - - # Different nonce should still fail - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 2, 0, self.question_id, 123).transact(self._txargs(val=123)) - else: - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 2, 0, self.question_id).transact(self._txargs(val=123)) - self.raiseOnZeroStatus(txid) - - if ERC20: - self.rc0.functions.submitAnswerERC20(expected_reopen_id, ANSWERED_TOO_SOON_VAL, 0, 3).transact(self._txargs()) - else: - self.rc0.functions.submitAnswer(expected_reopen_id, ANSWERED_TOO_SOON_VAL, 0).transact(self._txargs(val=3)) - self._advance_clock(33) - self.assertEqual(self.rc0.functions.getFinalAnswer(expected_reopen_id).call(), ANSWERED_TOO_SOON_VAL) - - # If the question is a reopen, it can't itself be reopened until the previous question has been reopened - # This prevents to bounty from being moved to a child before it can be returned to the new replacement of the original question. - expected_reopen_id_b = calculate_question_id(self.rc0.address, 0, "my question", self.arb0.address, 30, 0, 4, self.l2web3.eth.accounts[0], 0) - with self.assertRaises(TransactionFailed): - if ERC20: - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 4, 0, expected_reopen_id, 123).transact(self._txargs()) - else: - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 4, 0, expected_reopen_id).transact(self._txargs(val=123)) - self.raiseOnZeroStatus(txid) - - pre_reopen_bounty = self.rc0.functions.questions(expected_reopen_id).call()[QINDEX_BOUNTY] - - expected_reopen_id_2 = calculate_question_id(self.rc0.address, 0, "my question", self.arb0.address, 30, 0, 2, self.l2web3.eth.accounts[0], 0) - - if ERC20: - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 2, 0, self.question_id, 543).transact(self._txargs()) - else: - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 2, 0, self.question_id).transact(self._txargs(val=543)) - - self.raiseOnZeroStatus(txid) - - post_reopen_bounty = self.rc0.functions.questions(expected_reopen_id).call()[QINDEX_BOUNTY] - self.assertEqual(post_reopen_bounty, 0, "reopening a question moves the bounty from the reopened question to the new question") - - post_reopen_bounty_b = self.rc0.functions.questions(expected_reopen_id_2).call()[QINDEX_BOUNTY] - self.assertEqual(post_reopen_bounty_b, pre_reopen_bounty + 543 - question_fee) - - self.assertEqual(self.rc0.functions.reopened_questions(self.question_id).call(), expected_reopen_id_2, "reopened_questions returns now new question id") - - # Now you've reopened the parent you can reopen the child if you like, although this is usually a bad idea because you should be using the parent - if ERC20: - txid = self.rc0.functions.reopenQuestionERC20( 0, "my question", self.arb0.address, 30, 0, 4, 0, expected_reopen_id, 123).transact(self._txargs()) - else: - txid = self.rc0.functions.reopenQuestion( 0, "my question", self.arb0.address, 30, 0, 4, 0, expected_reopen_id).transact(self._txargs(val=123)) - self.raiseOnZeroStatus(txid) - - if ERC20: - self.rc0.functions.submitAnswerERC20(expected_reopen_id_2, to_answer_for_contract(432), 0, 3).transact(self._txargs()) - else: - self.rc0.functions.submitAnswer(expected_reopen_id_2, to_answer_for_contract(432), 0).transact(self._txargs(val=3)) - - self._advance_clock(33) - self.assertEqual(from_answer_for_contract(self.rc0.functions.getFinalAnswer(expected_reopen_id_2).call()), 432) - self.assertFalse(self.rc0.functions.isSettledTooSoon(expected_reopen_id_2).call()) - - self.assertEqual(from_answer_for_contract(self.rc0.functions.resultForOnceSettled(self.question_id).call()), 432) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_too_soon_bounty(self): - - if VERNUM < 3.0: - print("Skipping test_reopen_question, not a feature of this contract") - return - - k0 = self.l2web3.eth.accounts[0] - k3 = self.l2web3.eth.accounts[3] - k5 = self.l2web3.eth.accounts[5] - if ERC20: - self._issueTokens(self.l2token1, k0, 1000000, 1000000) - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 2, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 2, 4, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 4, 8, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 8, 16, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 16, 32, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, ANSWERED_TOO_SOON_VAL, 32, 64, k3) - claimable = 64+32+16+8+4+2 # no 1000, which was the original bounty - - self._advance_clock(33) - - self.assertEqual(self.rc0.functions.getFinalAnswer(self.question_id).call(), ANSWERED_TOO_SOON_VAL) - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 0) - - # Have an unconnected user do the claim - # This will leave the balance in the contract rather than withdrawing it - self.rc0.functions.claimMultipleAndWithdrawBalance([self.question_id], [len(st['hash'])], st['hash'], st['addr'], st['bond'], st['answer']).transact(self._txargs(sender=k5)) - - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), claimable) - - @unittest.skipIf(WORKING_ONLY, "Not under construction") - def test_too_soon_bonds_under_unrevealed_commit(self): - - if VERNUM < 3.0: - print("Skipping test_reopen_question, not a feature of this contract") - return - - k0 = self.l2web3.eth.accounts[0] - k3 = self.l2web3.eth.accounts[3] - k4 = self.l2web3.eth.accounts[4] - k5 = self.l2web3.eth.accounts[5] - if ERC20: - self._issueTokens(self.l2token1, k0, 1000000, 1000000) - self._issueTokens(self.l2token1, k3, 1000000, 1000000) - self._issueTokens(self.l2token1, k4, 1000000, 1000000) - - st = None - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 0, 200, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 200, 400, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 400, 800, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 800, 1600, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 1600, 3200, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, ANSWERED_TOO_SOON_VAL, 3200, 6400, k3) - st = self.submitAnswerReturnUpdatedState( st, self.question_id, 1001, 6400, 12800, k4, True) # We'll submit this but not reveal it - claimable = 12800+subfee(6400)+subfee(3200)+subfee(1600)+subfee(800)+subfee(400)+subfee(200) # no 1000, which was the original bounty - - self._advance_clock(33) - - self.assertEqual(self.rc0.functions.getFinalAnswer(self.question_id).call(), ANSWERED_TOO_SOON_VAL) - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), 0) - - # Have an unconnected user do the claim - # This will leave the balance in the contract rather than withdrawing it - self.rc0.functions.claimMultipleAndWithdrawBalance([self.question_id], [len(st['hash'])], st['hash'], st['addr'], st['bond'], st['answer']).transact(self._txargs(sender=k5)) - - self.assertEqual(self.rc0.functions.balanceOf(k3).call(), claimable) - - - - - - - - - -if __name__ == '__main__': - main() - -