Skip to content

Instantly share code, notes, and snippets.

@hbarcelos
Created October 15, 2020 14:32
Show Gist options
  • Save hbarcelos/75696e1dbb6f3f0dcdb14ef5e4308c85 to your computer and use it in GitHub Desktop.
Save hbarcelos/75696e1dbb6f3f0dcdb14ef5e4308c85 to your computer and use it in GitHub Desktop.
Cross-chain Realitio Arbitration
// SPDX-License-Identifier: MIT
// File: src/dependencies/IAMB.sol
pragma solidity ^0.7.2;
interface IAMB {
function requireToPassMessage(
address _contract,
bytes memory _data,
uint256 _gas
) external returns (bytes32);
function maxGasPerTx() external view returns (uint256);
function messageSender() external view returns (address);
function messageId() external view returns (bytes32);
}
// File: src/dependencies/RealitioInterface.sol
/* solhint-disable var-name-mixedcase */
interface RealitioInterface {
/**
* @dev The arbitrator contract is trusted to only call this if they've been paid, and tell us who paid them.
* @notice Notify the contract that the arbitrator has been paid for a question, freezing it pending their decision.
* @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;
/**
* @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;
/**
* @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;
/**
* @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) external view returns (bool);
/**
* @notice Returns the current best answer.
* @param question_id The ID of the question.
* @return The current best answer.
*/
function getBestAnswer(bytes32 question_id) external view returns (bytes32);
}
// File: src/ArbitrationProxyInterfaces.sol
interface IHomeArbitrationProxy {
/**
* @dev Recieves the requested arbitration for a question. TRUSTED.
* @param _questionID The ID of the question.
* @param _requesterAnswer The answer the requester deem to be correct.
* @param _requester The address of the user that requested arbitration.
*/
function receiveArbitrationRequest(
bytes32 _questionID,
bytes32 _requesterAnswer,
address _requester
) external;
/**
* @dev Recieves a failed attempt to request arbitration. TRUSTED.
* @param _questionID The ID of the question.
*/
function receiveArbitrationFailure(bytes32 _questionID) external;
/**
* @dev Recieves the answer to a specified question. TRUSTED.
* @param _questionID The ID of the question.
* @param _answer The answer from the arbitratior.
*/
function receiveArbitrationAnswer(bytes32 _questionID, bytes32 _answer) external;
}
interface IForeignArbitrationProxy {
/**
* @dev Requests arbitration for given question ID. TRUSTED.
* @param _questionID The ID of the question.
*/
function acknowledgeArbitration(bytes32 _questionID) external;
/**
* @dev Cancels the arbitration request. TRUSTED.
* @param _questionID The ID of the question.
*/
function cancelArbitration(bytes32 _questionID) external;
}
// File: src/RealitioHomeArbitrationProxy.sol
contract RealitioHomeArbitrationProxy is IHomeArbitrationProxy {
/// @dev The contract governor. TRUSTED.
address public governor = msg.sender;
/// @dev The address of the Realitio contract. TRUSTED.
RealitioInterface public realitio;
/// @dev ArbitraryMessageBridge contract address. TRUSTED.
IAMB public amb;
/// @dev Address of the counter-party proxy on the Foreign Chain. TRUSTED.
address public foreignProxy;
enum Status {None, Pending, Accepted, AwaitingRuling, Ruled}
struct Request {
Status status;
address requester;
bytes32 requesterAnswer;
bytes32 arbitratorAnswer;
}
/// @dev Associates an arbitration request with a question ID.
mapping(bytes32 => Request) public questionIDToRequest;
/**
* @dev To be emitted when arbitration request is received but remained pending.
* @notice This will happen if the best answer for a given question changes between
* the arbitration is requested on the Foreign Chain and the cross-chain message
* reaches the home chain and becomes the same answer as the one from requester.
* @param _questionID The ID of the question.
* @param _requesterAnswer The answer the requester deem to be correct.
* @param _requester The address of the user that requested arbitration.
*/
event RequestPending(bytes32 indexed _questionID, bytes32 _requesterAnswer, address indexed _requester);
/**
* @dev To be emitted when the Realitio contract has been notified of an arbitration request.
* @notice This will happen if the best answer for a given question changes between
* the arbitration is requested on the Foreign Chain and the cross-chain message
* reaches the home chain and becomes the same answer as the one from requester.
* @param _questionID The ID of the question.
* @param _requesterAnswer The answer the requester deem to be correct.
* @param _requester The address of the user that requested arbitration.
*/
event RequestNotified(bytes32 indexed _questionID, bytes32 _requesterAnswer, address indexed _requester);
/**
* @dev To be emitted when there arbitration request acknowledgement is sent to the Foreign Chain.
* @param _questionID The ID of the question.
*/
event RequestAcknowledged(bytes32 indexed _questionID);
/**
* @dev To be emitted when there arbitration request is cancelled.
* @param _questionID The ID of the question.
*/
event RequestCancelled(bytes32 indexed _questionID);
/**
* @dev To be emitted when the dispute could not be created on the Foreign Chain.
* @notice This will happen if there is a remaining arbitration fee users fail to pay.
* @param _questionID The ID of the question.
*/
event ArbitrationFailed(bytes32 indexed _questionID);
/**
* @dev To be emitted when receiving the answer from the arbitrator.
* @param _questionID The ID of the question.
* @param _answer The answer from the arbitrator.
*/
event ArbitratorAnswered(bytes32 indexed _questionID, bytes32 _answer);
/**
* @dev To be emitted when reporting the arbitrator answer to Realitio.
* @param _questionID The ID of the question.
*/
event ArbitrationCompleted(bytes32 indexed _questionID);
modifier onlyGovernor() {
require(msg.sender == governor, "Only governor allowed");
_;
}
modifier onlyAmb() {
require(msg.sender == address(amb), "Only AMB allowed");
_;
}
modifier onlyForeignProxy() {
require(amb.messageSender() == foreignProxy, "Only foreign proxy allowed");
_;
}
/**
* @dev Creates an arbitration proxy on the home chain.
* @param _amb ArbitraryMessageBridge contract address.
* @param _realitio Realitio contract address.
*/
constructor(IAMB _amb, RealitioInterface _realitio) {
amb = _amb;
realitio = _realitio;
}
/**
* @dev Sets the address of a new governor.
* @param _governor The address of the new governor.
*/
function setGovernor(address _governor) external onlyGovernor {
governor = _governor;
}
/**
* @dev Sets the address of the ArbitraryMessageBridge.
* @param _amb The address of the new ArbitraryMessageBridge.
*/
function setAmb(IAMB _amb) external onlyGovernor {
amb = _amb;
}
/**
* @dev Sets the address of the arbitration proxy on the Foreign Chain.
* @param _foreignProxy The address of the proxy.
*/
function setForeignProxy(address _foreignProxy) external onlyGovernor {
foreignProxy = _foreignProxy;
}
/**
* @dev Recieves the requested arbitration for a question.
* @param _questionID The ID of the question.
* @param _requesterAnswer The answer the requester deem to be correct.
* @param _requester The address of the user that requested arbitration.
*/
function receiveArbitrationRequest(
bytes32 _questionID,
bytes32 _requesterAnswer,
address _requester
) external override onlyAmb onlyForeignProxy {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.None, "Request already exists");
bytes32 currentAnswer = realitio.getBestAnswer(_questionID);
if (currentAnswer == _requesterAnswer) {
request.status = Status.Pending;
request.requester = _requester;
request.requesterAnswer = _requesterAnswer;
emit RequestPending(_questionID, _requesterAnswer, _requester);
} else {
request.status = Status.Accepted;
realitio.notifyOfArbitrationRequest(_questionID, _requester, 0);
emit RequestNotified(_questionID, _requesterAnswer, _requester);
}
}
/**
* @dev Handles arbitration request after it has been notified to Realitio for a given question.
* @notice Sends the arbitration acknowledgement to the Foreign Chain.
* @param _questionID The ID of the question.
*/
function handleNotifiedRequest(bytes32 _questionID) external {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.Accepted, "Invalid request status");
request.status = Status.AwaitingRuling;
bytes4 selector = IForeignArbitrationProxy(0).acknowledgeArbitration.selector;
bytes memory data = abi.encodeWithSelector(selector, _questionID);
amb.requireToPassMessage(foreignProxy, data, amb.maxGasPerTx());
emit RequestAcknowledged(_questionID);
}
/**
* @dev Handles changed answer for a given question.
* @notice Sends the arbitration acknowledgement to the Foreign Chain.
* @param _questionID The ID of the question.
*/
function handleChangedAnswer(bytes32 _questionID) external {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.Pending, "Invalid request status");
bytes32 currentAnswer = realitio.getBestAnswer(_questionID);
require(request.requesterAnswer != currentAnswer, "Answers are the same");
request.status = Status.AwaitingRuling;
realitio.notifyOfArbitrationRequest(_questionID, request.requester, 0);
emit RequestNotified(_questionID, request.requesterAnswer, request.requester);
bytes4 selector = IForeignArbitrationProxy(0).acknowledgeArbitration.selector;
bytes memory data = abi.encodeWithSelector(selector, _questionID);
amb.requireToPassMessage(foreignProxy, data, amb.maxGasPerTx());
emit RequestAcknowledged(_questionID);
}
/**
* @dev Handles a given question being finalized.
* @notice Sends the arbitration cancellation to the Foreign Chain.
* @param _questionID The ID of the question.
*/
function handleFinalizedQuestion(bytes32 _questionID) external {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.Pending, "Invalid request status");
bool isFinalized = realitio.isFinalized(_questionID);
require(isFinalized, "Question not finalized");
delete questionIDToRequest[_questionID];
bytes4 selector = IForeignArbitrationProxy(0).cancelArbitration.selector;
bytes memory data = abi.encodeWithSelector(selector, _questionID);
amb.requireToPassMessage(foreignProxy, data, amb.maxGasPerTx());
emit RequestCancelled(_questionID);
}
/**
* @dev Recieves a failed attempt to request arbitration.
* @param _questionID The ID of the question.
*/
function receiveArbitrationFailure(bytes32 _questionID) external override onlyAmb onlyForeignProxy {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.AwaitingRuling, "Invalid request status");
delete questionIDToRequest[_questionID];
realitio.cancelArbitration(_questionID);
emit ArbitrationFailed(_questionID);
}
/**
* @dev Recieves the answer to a specified question.
* @param _questionID The ID of the question.
* @param _answer The answer from the arbitratior.
*/
function receiveArbitrationAnswer(bytes32 _questionID, bytes32 _answer) external override onlyAmb onlyForeignProxy {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.AwaitingRuling, "Invalid request status");
request.status = Status.Ruled;
request.arbitratorAnswer = _answer;
emit ArbitratorAnswered(_questionID, _answer);
}
/**
* @dev Report the answer provided by the arbitrator to a specified question.
* @param _questionID The ID of the question.
* @param _lastHistoryHash The history hash given with the last answer to the question in the Realitio contract.
* @param _lastAnswerOrCommitmentID The last answer given, or its commitment ID if it was a commitment, to the question in the Realitio contract.
* @param _lastAnswerer The last answerer to the question in the Realitio contract.
*/
function reportAnswer(
bytes32 _questionID,
bytes32 _lastHistoryHash,
bytes32 _lastAnswerOrCommitmentID,
address _lastAnswerer
) external {
Request storage request = questionIDToRequest[_questionID];
require(request.status == Status.Ruled, "Arbitrator has not ruled yet");
realitio.assignWinnerAndSubmitAnswerByArbitrator(
_questionID,
request.arbitratorAnswer,
request.requester,
_lastHistoryHash,
_lastAnswerOrCommitmentID,
_lastAnswerer
);
delete questionIDToRequest[_questionID];
emit ArbitrationCompleted(_questionID);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment