Skip to content

Instantly share code, notes, and snippets.

@marsrobertson
Created April 30, 2019 23:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marsrobertson/7fe9db4a7ec2a8147279a146fd0a4839 to your computer and use it in GitHub Desktop.
Save marsrobertson/7fe9db4a7ec2a8147279a146fd0a4839 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.4.26+commit.4563c3fc.js&optimize=false&gist=
/**
* @title Arbitrable
* @author Clément Lesaege - <clement@lesaege.com>
* Bug Bounties: This code hasn't undertaken a bug bounty program yet.
*/
pragma solidity ^0.4.15;
import "./IArbitrable.sol";
/** @title Arbitrable
* Arbitrable abstract contract.
* When developing arbitrable contracts, we need to:
* -Define the action taken when a ruling is received by the contract. We should do so in executeRuling.
* -Allow dispute creation. For this a function must:
* -Call arbitrator.createDispute.value(_fee)(_choices,_extraData);
* -Create the event Dispute(_arbitrator,_disputeID,_rulingOptions);
*/
contract Arbitrable is IArbitrable {
Arbitrator public arbitrator;
bytes public arbitratorExtraData; // Extra data to require particular dispute and appeal behaviour.
modifier onlyArbitrator {require(msg.sender == address(arbitrator), "Can only be called by the arbitrator."); _;}
/** @dev Constructor. Choose the arbitrator.
* @param _arbitrator The arbitrator of the contract.
* @param _arbitratorExtraData Extra data for the arbitrator.
*/
constructor(Arbitrator _arbitrator, bytes _arbitratorExtraData) public {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
/** @dev Give a ruling for a dispute. Must be called by the arbitrator.
* The purpose of this function is to ensure that the address calling it has the right to rule on the contract.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function rule(uint _disputeID, uint _ruling) public onlyArbitrator {
emit Ruling(Arbitrator(msg.sender),_disputeID,_ruling);
executeRuling(_disputeID,_ruling);
}
/** @dev Execute a ruling of a dispute.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function executeRuling(uint _disputeID, uint _ruling) internal;
}
/**
* @authors: [@mtsalenc]
* @reviewers: []
* @auditors: []
* @bounties: []
* @deployments: []
*/
/* solium-disable max-len*/
pragma solidity ^0.4.24;
import "./Arbitrable.sol";
import "./PermissionInterface.sol";
import "./CappedMath.sol";
/**
* @title ArbitrableAddressList
* This contract is an arbitrable token curated registry for addresses, sometimes referred to as a Address Curated Registry. Users can send requests to register or remove addresses from the registry, which can in turn, be challenged by parties that disagree with them.
* A crowdsourced insurance system allows parties to contribute to arbitration fees and win rewards if the side they backed ultimately wins a dispute.
* NOTE: This contract trusts that the Arbitrator is honest and will not reenter or modify its costs during a call. This contract is only to be used with an arbitrator returning appealPeriod and having non-zero fees. The governor contract (which will be a DAO) is also to be trusted.
*/
contract ArbitrableAddressList is PermissionInterface, Arbitrable {
using CappedMath for uint; // Operations bounded between 0 and 2**256 - 1.
/* Enums */
enum AddressStatus {
Absent, // The address is not in the registry.
Registered, // The address is in the registry.
RegistrationRequested, // The address has a request to be added to the registry.
ClearingRequested // The address has a request to be removed from the registry.
}
enum Party {
None, // Party per default when there is no challenger or requester. Also used for unconclusive ruling.
Requester, // Party that made the request to change an address status.
Challenger // Party that challenges the request to change an address status.
}
// ************************ //
// * Request Life Cycle * //
// ************************ //
// Changes to the address status are made via requests for either listing or removing an address from the Address Curated Registry.
// To make or challenge a request, a party must pay a deposit. This value will be rewarded to the party that ultimately wins a dispute. If no one challenges the request, the value will be reimbursed to the requester.
// Additionally to the challenge reward, in the case a party challenges a request, both sides must fully pay the amount of arbitration fees required to raise a dispute. The party that ultimately wins the case will be reimbursed.
// Finally, arbitration fees can be crowdsourced. To incentivise insurers, an additional fee stake must be deposited. Contributors that fund the side that ultimately wins a dispute will be reimbursed and rewarded with the other side's fee stake proportionally to their contribution.
// In summary, costs for placing or challenging a request are the following:
// - A challenge reward given to the party that wins a potential dispute.
// - Arbitration fees used to pay jurors.
// - A fee stake that is distributed among insurers of the side that ultimately wins a dispute.
/* Structs */
struct Address {
AddressStatus status; // The status of the address.
Request[] requests; // List of status change requests made for the address.
}
// Some arrays below have 3 elements to map with the Party enums for better readability:
// - 0: is unused, matches `Party.None`.
// - 1: for `Party.Requester`.
// - 2: for `Party.Challenger`.
struct Request {
bool disputed; // True if a dispute was raised.
uint disputeID; // ID of the dispute, if any.
uint submissionTime; // Time when the request was made. Used to track when the challenge period ends.
bool resolved; // True if the request was executed and/or any disputes raised were resolved.
address[3] parties; // Address of requester and challenger, if any.
Round[] rounds; // Tracks each round of a dispute.
Party ruling; // The final ruling given, if any.
Arbitrator arbitrator; // The arbitrator trusted to solve disputes for this request.
bytes arbitratorExtraData; // The extra data for the trusted arbitrator of this request.
}
struct Round {
uint[3] paidFees; // Tracks the fees paid by each side on this round.
bool[3] hasPaid; // True when the side has fully paid its fee. False otherwise.
uint feeRewards; // Sum of reimbursable fees and stake rewards available to the parties that made contributions to the side that ultimately wins a dispute.
mapping(address => uint[3]) contributions; // Maps contributors to their contributions for each side.
}
/* Storage */
// Constants
uint RULING_OPTIONS = 2; // The amount of non 0 choices the arbitrator can give.
// Settings
address public governor; // The address that can make governance changes to the parameters of the Address Curated Registry.
uint public requesterBaseDeposit; // The base deposit to make a request.
uint public challengerBaseDeposit; // The base deposit to challenge a request.
uint public challengePeriodDuration; // The time before a request becomes executable if not challenged.
uint public metaEvidenceUpdates; // The number of times the meta evidence has been updated. Used to track the latest meta evidence ID.
// The required fee stake that a party must pay depends on who won the previous round and is proportional to the arbitration cost such that the fee stake for a round is stake multiplier * arbitration cost for that round.
// Multipliers are in basis points.
uint public winnerStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that won the previous round.
uint public loserStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that lost the previous round.
uint public sharedStakeMultiplier; // Multiplier for calculating the fee stake that must be paid in the case where there isn't a winner and loser (e.g. when it's the first round or the arbitrator ruled "refused to rule"/"could not rule").
uint public constant MULTIPLIER_DIVISOR = 10000; // Divisor parameter for multipliers.
// Registry data.
mapping(address => Address) public addresses; // Maps the address to the address data.
mapping(address => mapping(uint => address)) public arbitratorDisputeIDToAddress; // Maps a dispute ID to the address with the disputed request. On the form arbitratorDisputeIDToAddress[arbitrator][disputeID].
address[] public addressList; // List of submitted addresses.
/* Modifiers */
modifier onlyGovernor {require(msg.sender == governor, "The caller must be the governor."); _;}
/* Events */
/**
* @dev Emitted when a party submits a new address.
* @param _address The address.
* @param _requester The address of the party that made the request.
*/
event AddressSubmitted(address indexed _address, address indexed _requester);
/** @dev Emitted when a party makes a request to change an address status.
* @param _address The affected address.
* @param _registrationRequest Whether the request is a registration request. False means it is a clearing request.
*/
event RequestSubmitted(address indexed _address, bool _registrationRequest);
/**
* @dev Emitted when a party makes a request, dispute or appeals are raised, or when a request is resolved.
* @param _requester Address of the party that submitted the request.
* @param _challenger Address of the party that has challenged the request, if any.
* @param _address The address.
* @param _status The status of the address.
* @param _disputed Whether the address is disputed.
* @param _appealed Whether the current round was appealed.
*/
event AddressStatusChange(
address indexed _requester,
address indexed _challenger,
address indexed _address,
AddressStatus _status,
bool _disputed,
bool _appealed
);
/** @dev Emitted when a reimbursements and/or contribution rewards are withdrawn.
* @param _address The address from which the withdrawal was made.
* @param _contributor The address that sent the contribution.
* @param _request The request from which the withdrawal was made.
* @param _round The round from which the reward was taken.
* @param _value The value of the reward.
*/
event RewardWithdrawal(address indexed _address, address indexed _contributor, uint indexed _request, uint _round, uint _value);
/* Constructor */
/**
* @dev Constructs the arbitrable token curated registry.
* @param _arbitrator The trusted arbitrator to resolve potential disputes.
* @param _arbitratorExtraData Extra data for the trusted arbitrator contract.
* @param _registrationMetaEvidence The URI of the meta evidence object for registration requests.
* @param _clearingMetaEvidence The URI of the meta evidence object for clearing requests.
* @param _governor The trusted governor of this contract.
* @param _requesterBaseDeposit The base deposit to make a request.
* @param _challengerBaseDeposit The base deposit to challenge a request.
* @param _challengePeriodDuration The time in seconds, parties have to challenge a request.
* @param _sharedStakeMultiplier Multiplier of the arbitration cost that each party must pay as fee stake for a round when there isn't a winner/loser in the previous round (e.g. when it's the first round or the arbitrator refused to or did not rule). In basis points.
* @param _winnerStakeMultiplier Multiplier of the arbitration cost that the winner has to pay as fee stake for a round in basis points.
* @param _loserStakeMultiplier Multiplier of the arbitration cost that the loser has to pay as fee stake for a round in basis points.
*/
constructor(
Arbitrator _arbitrator,
bytes _arbitratorExtraData,
string _registrationMetaEvidence,
string _clearingMetaEvidence,
address _governor,
uint _requesterBaseDeposit,
uint _challengerBaseDeposit,
uint _challengePeriodDuration,
uint _sharedStakeMultiplier,
uint _winnerStakeMultiplier,
uint _loserStakeMultiplier
) Arbitrable(_arbitrator, _arbitratorExtraData) public {
emit MetaEvidence(0, _registrationMetaEvidence);
emit MetaEvidence(1, _clearingMetaEvidence);
governor = _governor;
requesterBaseDeposit = _requesterBaseDeposit;
challengerBaseDeposit = _challengerBaseDeposit;
challengePeriodDuration = _challengePeriodDuration;
sharedStakeMultiplier = _sharedStakeMultiplier;
winnerStakeMultiplier = _winnerStakeMultiplier;
loserStakeMultiplier = _loserStakeMultiplier;
}
/* External and Public */
// ************************ //
// * Requests * //
// ************************ //
/** @dev Submits a request to change an address status. Accepts enough ETH to fund a potential dispute considering the current required amount and reimburses the rest. TRUSTED.
* @param _address The address.
*/
function requestStatusChange(address _address)
external
payable
{
Address storage addr = addresses[_address];
if (addr.requests.length == 0) {
// Initial address registration.
addressList.push(_address);
emit AddressSubmitted(_address, msg.sender);
}
// Update address status.
if (addr.status == AddressStatus.Absent)
addr.status = AddressStatus.RegistrationRequested;
else if (addr.status == AddressStatus.Registered)
addr.status = AddressStatus.ClearingRequested;
else
revert("Address already has a pending request.");
// Setup request.
Request storage request = addr.requests[addr.requests.length++];
request.parties[uint(Party.Requester)] = msg.sender;
request.submissionTime = now;
request.arbitrator = arbitrator;
request.arbitratorExtraData = arbitratorExtraData;
Round storage round = request.rounds[request.rounds.length++];
emit RequestSubmitted(_address, addr.status == AddressStatus.RegistrationRequested);
// Amount required to fully the requester: requesterBaseDeposit + arbitration cost + (arbitration cost * multiplier).
uint arbitrationCost = request.arbitrator.arbitrationCost(request.arbitratorExtraData);
uint totalCost = arbitrationCost.addCap((arbitrationCost.mulCap(sharedStakeMultiplier)) / MULTIPLIER_DIVISOR).addCap(requesterBaseDeposit);
contribute(round, Party.Requester, msg.sender, msg.value, totalCost);
require(round.paidFees[uint(Party.Requester)] >= totalCost, "You must fully fund your side.");
round.hasPaid[uint(Party.Requester)] = true;
emit AddressStatusChange(
request.parties[uint(Party.Requester)],
address(0x0),
_address,
addr.status,
false,
false
);
}
/** @dev Challenges the latest request of an address. Accepts enough ETH to fund a potential dispute considering the current required amount. Reimburses unused ETH. TRUSTED.
* @param _address The address with the request to challenge.
* @param _evidence A link to an evidence using its URI. Ignored if not provided or if not enough funds were provided to create a dispute.
*/
function challengeRequest(address _address, string _evidence) external payable {
Address storage addr = addresses[_address];
require(
addr.status == AddressStatus.RegistrationRequested || addr.status == AddressStatus.ClearingRequested,
"The address must have a pending request."
);
Request storage request = addr.requests[addr.requests.length - 1];
require(now - request.submissionTime <= challengePeriodDuration, "Challenges must occur during the challenge period.");
require(!request.disputed, "The request should not have already been disputed.");
// Take the deposit and save the challenger's address.
request.parties[uint(Party.Challenger)] = msg.sender;
Round storage round = request.rounds[request.rounds.length - 1];
uint arbitrationCost = request.arbitrator.arbitrationCost(request.arbitratorExtraData);
uint totalCost = arbitrationCost.addCap((arbitrationCost.mulCap(sharedStakeMultiplier)) / MULTIPLIER_DIVISOR).addCap(challengerBaseDeposit);
contribute(round, Party.Challenger, msg.sender, msg.value, totalCost);
require(round.paidFees[uint(Party.Challenger)] >= totalCost, "You must fully fund your side.");
round.hasPaid[uint(Party.Challenger)] = true;
// Raise a dispute.
request.disputeID = request.arbitrator.createDispute.value(arbitrationCost)(RULING_OPTIONS, request.arbitratorExtraData);
arbitratorDisputeIDToAddress[request.arbitrator][request.disputeID] = _address;
request.disputed = true;
request.rounds.length++;
round.feeRewards = round.feeRewards.subCap(arbitrationCost);
emit Dispute(
request.arbitrator,
request.disputeID,
addr.status == AddressStatus.RegistrationRequested
? 2 * metaEvidenceUpdates
: 2 * metaEvidenceUpdates + 1,
uint(keccak256(abi.encodePacked(_address,addr.requests.length - 1)))
);
emit AddressStatusChange(
request.parties[uint(Party.Requester)],
request.parties[uint(Party.Challenger)],
_address,
addr.status,
true,
false
);
if (bytes(_evidence).length > 0)
emit Evidence(request.arbitrator, uint(keccak256(abi.encodePacked(_address,addr.requests.length - 1))), msg.sender, _evidence);
}
/** @dev Takes up to the total amount required to fund a side of an appeal. Reimburses the rest. Creates an appeal if both sides are fully funded. TRUSTED.
* @param _address The address with the request to fund.
* @param _side The recipient of the contribution.
*/
function fundAppeal(address _address, Party _side) external payable {
// Recipient must be either the requester or challenger.
require(_side == Party.Requester || _side == Party.Challenger); // solium-disable-line error-reason
Address storage addr = addresses[_address];
require(
addr.status == AddressStatus.RegistrationRequested || addr.status == AddressStatus.ClearingRequested,
"The address must have a pending request."
);
Request storage request = addr.requests[addr.requests.length - 1];
require(request.disputed, "A dispute must have been raised to fund an appeal.");
(uint appealPeriodStart, uint appealPeriodEnd) = request.arbitrator.appealPeriod(request.disputeID);
require(
now >= appealPeriodStart && now < appealPeriodEnd,
"Contributions must be made within the appeal period."
);
// Amount required to fully fund each side: arbitration cost + (arbitration cost * multiplier)
Round storage round = request.rounds[request.rounds.length - 1];
Party winner = Party(request.arbitrator.currentRuling(request.disputeID));
Party loser;
if (winner == Party.Requester)
loser = Party.Challenger;
else if (winner == Party.Challenger)
loser = Party.Requester;
require(!(_side==loser) || (now-appealPeriodStart < (appealPeriodEnd-appealPeriodStart)/2), "The loser must contribute during the first half of the appeal period.");
uint multiplier;
if (_side == winner)
multiplier = winnerStakeMultiplier;
else if (_side == loser)
multiplier = loserStakeMultiplier;
else
multiplier = sharedStakeMultiplier;
uint appealCost = request.arbitrator.appealCost(request.disputeID, request.arbitratorExtraData);
uint totalCost = appealCost.addCap((appealCost.mulCap(multiplier)) / MULTIPLIER_DIVISOR);
contribute(round, _side, msg.sender, msg.value, totalCost);
if (round.paidFees[uint(_side)] >= totalCost)
round.hasPaid[uint(_side)] = true;
// Raise appeal if both sides are fully funded.
if (round.hasPaid[uint(Party.Challenger)] && round.hasPaid[uint(Party.Requester)]) {
request.arbitrator.appeal.value(appealCost)(request.disputeID, request.arbitratorExtraData);
request.rounds.length++;
round.feeRewards = round.feeRewards.subCap(appealCost);
emit AddressStatusChange(
request.parties[uint(Party.Requester)],
request.parties[uint(Party.Challenger)],
_address,
addr.status,
true,
true
);
}
}
/** @dev Reimburses contributions if no disputes were raised. If a dispute was raised, sends the fee stake rewards and reimbursements proportional to the contributions made to the winner of a dispute.
* @param _beneficiary The address that made contributions to a request.
* @param _address The address submission with the request from which to withdraw.
* @param _request The request from which to withdraw.
* @param _round The round from which to withdraw.
*/
function withdrawFeesAndRewards(address _beneficiary, address _address, uint _request, uint _round) public {
Address storage addr = addresses[_address];
Request storage request = addr.requests[_request];
Round storage round = request.rounds[_round];
// The request must be executed and there can be no disputes pending resolution.
require(request.resolved); // solium-disable-line error-reason
uint reward;
if (!request.disputed || request.ruling == Party.None) {
// No disputes were raised, or there isn't a winner and loser. Reimburse unspent fees proportionally.
uint rewardRequester = round.paidFees[uint(Party.Requester)] > 0
? (round.contributions[_beneficiary][uint(Party.Requester)] * round.feeRewards) / (round.paidFees[uint(Party.Challenger)] + round.paidFees[uint(Party.Requester)])
: 0;
uint rewardChallenger = round.paidFees[uint(Party.Challenger)] > 0
? (round.contributions[_beneficiary][uint(Party.Challenger)] * round.feeRewards) / (round.paidFees[uint(Party.Challenger)] + round.paidFees[uint(Party.Requester)])
: 0;
reward = rewardRequester + rewardChallenger;
round.contributions[_beneficiary][uint(Party.Requester)] = 0;
round.contributions[_beneficiary][uint(Party.Challenger)] = 0;
} else {
// Reward the winner.
reward = round.paidFees[uint(request.ruling)] > 0
? (round.contributions[_beneficiary][uint(request.ruling)] * round.feeRewards) / round.paidFees[uint(request.ruling)]
: 0;
round.contributions[_beneficiary][uint(request.ruling)] = 0;
}
emit RewardWithdrawal(_address, _beneficiary, _request, _round, reward);
_beneficiary.send(reward); // It is the user responsibility to accept ETH.
}
/** @dev Withdraws rewards and reimbursements of multiple rounds at once. This function is O(n) where n is the number of rounds. This could exceed gas limits, therefore this function should be used only as a utility and not be relied upon by other contracts.
* @param _beneficiary The address that made contributions to the request.
* @param _address The address with funds to be withdrawn.
* @param _request The request from which to withdraw contributions.
* @param _cursor The round from where to start withdrawing.
* @param _count The number of rounds to iterate. If set to 0 or a value larger than the number of rounds, iterates until the last round.
*/
function batchRoundWithdraw(address _beneficiary, address _address, uint _request, uint _cursor, uint _count) public {
Address storage addr = addresses[_address];
Request storage request = addr.requests[_request];
for (uint i = _cursor; i<request.rounds.length && (_count==0 || i<_count); i++)
withdrawFeesAndRewards(_beneficiary, _address, _request, i);
}
/** @dev Withdraws rewards and reimbursements of multiple requests at once. This function is O(n*m) where n is the number of requests and m is the number of rounds. This could exceed gas limits, therefore this function should be used only as a utility and not be relied upon by other contracts.
* @param _beneficiary The address that made contributions to the request.
* @param _address The address with funds to be withdrawn.
* @param _cursor The request from which to start withdrawing.
* @param _count The number of requests to iterate. If set to 0 or a value larger than the number of request, iterates until the last request.
* @param _roundCursor The round of each request from where to start withdrawing.
* @param _roundCount The number of rounds to iterate on each request. If set to 0 or a value larger than the number of rounds a request has, iteration for that request will stop at the last round.
*/
function batchRequestWithdraw(
address _beneficiary,
address _address,
uint _cursor,
uint _count,
uint _roundCursor,
uint _roundCount
) external {
Address storage addr = addresses[_address];
for (uint i = _cursor; i<addr.requests.length && (_count==0 || i<_count); i++)
batchRoundWithdraw(_beneficiary, _address, i, _roundCursor, _roundCount);
}
/** @dev Executes a request if the challenge period passed and no one challenged the request.
* @param _address The address with the request to execute.
*/
function executeRequest(address _address) external {
Address storage addr = addresses[_address];
Request storage request = addr.requests[addr.requests.length - 1];
require(
now - request.submissionTime > challengePeriodDuration,
"Time to challenge the request must have passed."
);
require(!request.disputed, "The request should not be disputed.");
if (addr.status == AddressStatus.RegistrationRequested)
addr.status = AddressStatus.Registered;
else if (addr.status == AddressStatus.ClearingRequested)
addr.status = AddressStatus.Absent;
else
revert("There must be a request.");
request.resolved = true;
withdrawFeesAndRewards(request.parties[uint(Party.Requester)], _address, addr.requests.length - 1, 0); // Automatically withdraw for the requester.
emit AddressStatusChange(
request.parties[uint(Party.Requester)],
address(0x0),
_address,
addr.status,
false,
false
);
}
/** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED.
* Overrides parent function to account for the situation where the winner loses a case due to paying less appeal fees than expected.
* @param _disputeID ID of the dispute in the arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function rule(uint _disputeID, uint _ruling) public {
Party resultRuling = Party(_ruling);
address _address = arbitratorDisputeIDToAddress[msg.sender][_disputeID];
Address storage addr = addresses[_address];
Request storage request = addr.requests[addr.requests.length - 1];
Round storage round = request.rounds[request.rounds.length - 1];
require(_ruling <= RULING_OPTIONS); // solium-disable-line error-reason
require(request.arbitrator == msg.sender); // solium-disable-line error-reason
require(!request.resolved); // solium-disable-line error-reason
// The ruling is inverted if the loser paid its fees.
if (round.hasPaid[uint(Party.Requester)] == true) // If one side paid its fees, the ruling is in its favor. Note that if the other side had also paid, an appeal would have been created.
resultRuling = Party.Requester;
else if (round.hasPaid[uint(Party.Challenger)] == true)
resultRuling = Party.Challenger;
emit Ruling(Arbitrator(msg.sender), _disputeID, uint(resultRuling));
executeRuling(_disputeID, uint(resultRuling));
}
/** @dev Submit a reference to evidence. EVENT.
* @param _evidence A link to an evidence using its URI.
*/
function submitEvidence(address _address, string _evidence) external {
Address storage addr = addresses[_address];
Request storage request = addr.requests[addr.requests.length - 1];
require(!request.resolved, "The dispute must not already be resolved.");
emit Evidence(request.arbitrator, uint(keccak256(abi.encodePacked(_address,addr.requests.length - 1))), msg.sender, _evidence);
}
// ************************ //
// * Governance * //
// ************************ //
/** @dev Change the duration of the challenge period.
* @param _challengePeriodDuration The new duration of the challenge period.
*/
function changeTimeToChallenge(uint _challengePeriodDuration) external onlyGovernor {
challengePeriodDuration = _challengePeriodDuration;
}
/** @dev Change the base amount required as a deposit to make a request.
* @param _requesterBaseDeposit The new base amount of wei required to make a request.
*/
function changeRequesterBaseDeposit(uint _requesterBaseDeposit) external onlyGovernor {
requesterBaseDeposit = _requesterBaseDeposit;
}
/** @dev Change the base amount required as a deposit to challenge a request.
* @param _challengerBaseDeposit The new base amount of wei required to challenge a request.
*/
function changeChallengerBaseDeposit(uint _challengerBaseDeposit) external onlyGovernor {
challengerBaseDeposit = _challengerBaseDeposit;
}
/** @dev Change the governor of the token curated registry.
* @param _governor The address of the new governor.
*/
function changeGovernor(address _governor) external onlyGovernor {
governor = _governor;
}
/** @dev Change the percentage of arbitration fees that must be paid as fee stake by parties when there isn't a winner or loser.
* @param _sharedStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeSharedStakeMultiplier(uint _sharedStakeMultiplier) external onlyGovernor {
sharedStakeMultiplier = _sharedStakeMultiplier;
}
/** @dev Change the percentage of arbitration fees that must be paid as fee stake by the winner of the previous round.
* @param _winnerStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeWinnerStakeMultiplier(uint _winnerStakeMultiplier) external onlyGovernor {
winnerStakeMultiplier = _winnerStakeMultiplier;
}
/** @dev Change the percentage of arbitration fees that must be paid as fee stake by the party that lost the previous round.
* @param _loserStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points.
*/
function changeLoserStakeMultiplier(uint _loserStakeMultiplier) external onlyGovernor {
loserStakeMultiplier = _loserStakeMultiplier;
}
/** @dev Change the arbitrator to be used for disputes that may be raised in the next requests. The arbitrator is trusted to support appeal periods and not reenter.
* @param _arbitrator The new trusted arbitrator to be used in the next requests.
* @param _arbitratorExtraData The extra data used by the new arbitrator.
*/
function changeArbitrator(Arbitrator _arbitrator, bytes _arbitratorExtraData) external onlyGovernor {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
/** @dev Update the meta evidence used for disputes.
* @param _registrationMetaEvidence The meta evidence to be used for future registration request disputes.
* @param _clearingMetaEvidence The meta evidence to be used for future clearing request disputes.
*/
function changeMetaEvidence(string _registrationMetaEvidence, string _clearingMetaEvidence) external onlyGovernor {
metaEvidenceUpdates++;
emit MetaEvidence(2 * metaEvidenceUpdates, _registrationMetaEvidence);
emit MetaEvidence(2 * metaEvidenceUpdates + 1, _clearingMetaEvidence);
}
/* Internal */
/** @dev Returns the contribution value and remainder from available ETH and required amount.
* @param _available The amount of ETH available for the contribution.
* @param _requiredAmount The amount of ETH required for the contribution.
* @return taken The amount of ETH taken.
* @return remainder The amount of ETH left from the contribution.
*/
function calculateContribution(uint _available, uint _requiredAmount)
internal
pure
returns(uint taken, uint remainder)
{
if (_requiredAmount > _available)
return (_available, 0); // Take whatever is available, return 0 as leftover ETH.
remainder = _available - _requiredAmount;
return (_requiredAmount, remainder);
}
/** @dev Make a fee contribution.
* @param _round The round to contribute.
* @param _side The side for which to contribute.
* @param _contributor The contributor.
* @param _amount The amount contributed.
* @param _totalRequired The total amount required for this side.
*/
function contribute(Round storage _round, Party _side, address _contributor, uint _amount, uint _totalRequired) internal {
// Take up to the amount necessary to fund the current round at the current costs.
uint contribution; // Amount contributed.
uint remainingETH; // Remaining ETH to send back.
(contribution, remainingETH) = calculateContribution(_amount, _totalRequired.subCap(_round.paidFees[uint(_side)]));
_round.contributions[_contributor][uint(_side)] += contribution;
_round.paidFees[uint(_side)] += contribution;
_round.feeRewards += contribution;
// Reimburse leftover ETH.
_contributor.send(remainingETH); // Deliberate use of send in order to not block the contract in case of reverting fallback.
}
/** @dev Execute the ruling of a dispute.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function executeRuling(uint _disputeID, uint _ruling) internal {
address _address = arbitratorDisputeIDToAddress[msg.sender][_disputeID];
Address storage addr = addresses[_address];
Request storage request = addr.requests[addr.requests.length - 1];
Party winner = Party(_ruling);
// Update address state
if (winner == Party.Requester) { // Execute Request
if (addr.status == AddressStatus.RegistrationRequested)
addr.status = AddressStatus.Registered;
else
addr.status = AddressStatus.Absent;
} else { // Revert to previous state.
if (addr.status == AddressStatus.RegistrationRequested)
addr.status = AddressStatus.Absent;
else if (addr.status == AddressStatus.ClearingRequested)
addr.status = AddressStatus.Registered;
}
request.resolved = true;
request.ruling = Party(_ruling);
// Automatically withdraw.
if (winner == Party.None) {
withdrawFeesAndRewards(request.parties[uint(Party.Requester)], _address, addr.requests.length-1, 0);
withdrawFeesAndRewards(request.parties[uint(Party.Challenger)], _address, addr.requests.length-1, 0);
} else {
withdrawFeesAndRewards(request.parties[uint(winner)], _address, addr.requests.length-1, 0);
}
emit AddressStatusChange(
request.parties[uint(Party.Requester)],
request.parties[uint(Party.Challenger)],
_address,
addr.status,
request.disputed,
false
);
}
/* Views */
/** @dev Return true if the address is on the list.
* @param _address The address to be queried.
* @return allowed True if the address is allowed, false otherwise.
*/
function isPermitted(bytes32 _address) external view returns (bool allowed) {
Address storage addr = addresses[address(_address)];
return addr.status == AddressStatus.Registered || addr.status == AddressStatus.ClearingRequested;
}
/* Interface Views */
/** @dev Return the sum of withdrawable wei of a request an account is entitled to. This function is O(n), where n is the number of rounds of the request. This could exceed the gas limit, therefore this function should only be used for interface display and not by other contracts.
* @param _address The address to query.
* @param _beneficiary The contributor for which to query.
* @param _request The request from which to query for.
* @return The total amount of wei available to withdraw.
*/
function amountWithdrawable(address _address, address _beneficiary, uint _request) external view returns (uint total){
Request storage request = addresses[_address].requests[_request];
if (!request.resolved) return total;
for (uint i = 0; i < request.rounds.length; i++) {
Round storage round = request.rounds[i];
if (!request.disputed || request.ruling == Party.None) {
uint rewardRequester = round.paidFees[uint(Party.Requester)] > 0
? (round.contributions[_beneficiary][uint(Party.Requester)] * round.feeRewards) / (round.paidFees[uint(Party.Requester)] + round.paidFees[uint(Party.Challenger)])
: 0;
uint rewardChallenger = round.paidFees[uint(Party.Challenger)] > 0
? (round.contributions[_beneficiary][uint(Party.Challenger)] * round.feeRewards) / (round.paidFees[uint(Party.Requester)] + round.paidFees[uint(Party.Challenger)])
: 0;
total += rewardRequester + rewardChallenger;
} else {
total += round.paidFees[uint(request.ruling)] > 0
? (round.contributions[_beneficiary][uint(request.ruling)] * round.feeRewards) / round.paidFees[uint(request.ruling)]
: 0;
}
}
return total;
}
/** @dev Return the numbers of addresses that were submitted. Includes addresses that never made it to the list or were later removed.
* @return count The numbers of addresses in the list.
*/
function addressCount() external view returns (uint count) {
return addressList.length;
}
/** @dev Return the numbers of addresses with each status. This function is O(n), where n is the number of addresses. This could exceed the gas limit, therefore this function should only be used for interface display and not by other contracts.
* @return The numbers of addresses in the list per status.
*/
function countByStatus()
external
view
returns (
uint absent,
uint registered,
uint registrationRequest,
uint clearingRequest,
uint challengedRegistrationRequest,
uint challengedClearingRequest
)
{
for (uint i = 0; i < addressList.length; i++) {
Address storage addr = addresses[addressList[i]];
Request storage request = addr.requests[addr.requests.length - 1];
if (addr.status == AddressStatus.Absent) absent++;
else if (addr.status == AddressStatus.Registered) registered++;
else if (addr.status == AddressStatus.RegistrationRequested && !request.disputed) registrationRequest++;
else if (addr.status == AddressStatus.ClearingRequested && !request.disputed) clearingRequest++;
else if (addr.status == AddressStatus.RegistrationRequested && request.disputed) challengedRegistrationRequest++;
else if (addr.status == AddressStatus.ClearingRequested && request.disputed) challengedClearingRequest++;
}
}
/** @dev Return the values of the addresses the query finds. This function is O(n), where n is the number of addresses. This could exceed the gas limit, therefore this function should only be used for interface display and not by other contracts.
* @param _cursor The address from which to start iterating. To start from either the oldest or newest item.
* @param _count The number of addresses to return.
* @param _filter The filter to use. Each element of the array in sequence means:
* - Include absent addresses in result.
* - Include registered addresses in result.
* - Include addresses with registration requests that are not disputed in result.
* - Include addresses with clearing requests that are not disputed in result.
* - Include disputed addresses with registration requests in result.
* - Include disputed addresses with clearing requests in result.
* - Include addresses submitted by the caller.
* - Include addresses challenged by the caller.
* @param _oldestFirst Whether to sort from oldest to the newest item.
* @return The values of the addresses found and whether there are more addresses for the current filter and sort.
*/
function queryAddresses(address _cursor, uint _count, bool[8] _filter, bool _oldestFirst)
external
view
returns (address[] values, bool hasMore)
{
uint cursorIndex;
values = new address[](_count);
uint index = 0;
if (_cursor == 0)
cursorIndex = 0;
else {
for (uint j = 0; j < addressList.length; j++) {
if (addressList[j] == _cursor) {
cursorIndex = j;
break;
}
}
require(cursorIndex != 0, "The cursor is invalid.");
}
for (
uint i = cursorIndex == 0 ? (_oldestFirst ? 0 : 1) : (_oldestFirst ? cursorIndex + 1 : addressList.length - cursorIndex + 1);
_oldestFirst ? i < addressList.length : i <= addressList.length;
i++
) { // Oldest or newest first.
Address storage addr = addresses[addressList[_oldestFirst ? i : addressList.length - i]];
Request storage request = addr.requests[addr.requests.length - 1];
if (
/* solium-disable operator-whitespace */
(_filter[0] && addr.status == AddressStatus.Absent) ||
(_filter[1] && addr.status == AddressStatus.Registered) ||
(_filter[2] && addr.status == AddressStatus.RegistrationRequested && !request.disputed) ||
(_filter[3] && addr.status == AddressStatus.ClearingRequested && !request.disputed) ||
(_filter[4] && addr.status == AddressStatus.RegistrationRequested && request.disputed) ||
(_filter[5] && addr.status == AddressStatus.ClearingRequested && request.disputed) ||
(_filter[6] && request.parties[uint(Party.Requester)] == msg.sender) || // My Submissions.
(_filter[7] && request.parties[uint(Party.Challenger)] == msg.sender) // My Challenges.
/* solium-enable operator-whitespace */
) {
if (index < _count) {
values[index] = addressList[_oldestFirst ? i : addressList.length - i];
index++;
} else {
hasMore = true;
break;
}
}
}
}
/** @dev Gets the contributions made by a party for a given round of a request.
* @param _address The address.
* @param _request The position of the request.
* @param _round The position of the round.
* @param _contributor The address of the contributor.
* @return The contributions.
*/
function getContributions(
address _address,
uint _request,
uint _round,
address _contributor
) external view returns(uint[3] contributions) {
Address storage addr = addresses[_address];
Request storage request = addr.requests[_request];
Round storage round = request.rounds[_round];
contributions = round.contributions[_contributor];
}
/** @dev Returns address information. Includes length of requests array.
* @param _address The queried address.
* @return The address information.
*/
function getAddressInfo(address _address)
external
view
returns (
AddressStatus status,
uint numberOfRequests
)
{
Address storage addr = addresses[_address];
return (
addr.status,
addr.requests.length
);
}
/** @dev Gets information on a request made for an address.
* @param _address The queried address.
* @param _request The request to be queried.
* @return The request information.
*/
function getRequestInfo(address _address, uint _request)
external
view
returns (
bool disputed,
uint disputeID,
uint submissionTime,
bool resolved,
address[3] parties,
uint numberOfRounds,
Party ruling,
Arbitrator arbitrator,
bytes arbitratorExtraData
)
{
Request storage request = addresses[_address].requests[_request];
return (
request.disputed,
request.disputeID,
request.submissionTime,
request.resolved,
request.parties,
request.rounds.length,
request.ruling,
request.arbitrator,
request.arbitratorExtraData
);
}
/** @dev Gets the information on a round of a request.
* @param _address The queried address.
* @param _request The request to be queried.
* @param _round The round to be queried.
* @return The round information.
*/
function getRoundInfo(address _address, uint _request, uint _round)
external
view
returns (
bool appealed,
uint[3] paidFees,
bool[3] hasPaid,
uint feeRewards
)
{
Address storage addr = addresses[_address];
Request storage request = addr.requests[_request];
Round storage round = request.rounds[_round];
return (
_round != (request.rounds.length-1),
round.paidFees,
round.hasPaid,
round.feeRewards
);
}
}
/**
* @title Arbitrator
* @author Clément Lesaege - <clement@lesaege.com>
* Bug Bounties: This code hasn't undertaken a bug bounty program yet.
*/
pragma solidity ^0.4.15;
import "./Arbitrable.sol";
/** @title Arbitrator
* Arbitrator abstract contract.
* When developing arbitrator contracts we need to:
* -Define the functions for dispute creation (createDispute) and appeal (appeal). Don't forget to store the arbitrated contract and the disputeID (which should be unique, use nbDisputes).
* -Define the functions for cost display (arbitrationCost and appealCost).
* -Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling).
*/
contract Arbitrator {
enum DisputeStatus {Waiting, Appealable, Solved}
modifier requireArbitrationFee(bytes _extraData) {
require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration costs.");
_;
}
modifier requireAppealFee(uint _disputeID, bytes _extraData) {
require(msg.value >= appealCost(_disputeID, _extraData), "Not enough ETH to cover appeal costs.");
_;
}
/** @dev To be raised when a dispute is created.
* @param _disputeID ID of the dispute.
* @param _arbitrable The contract which created the dispute.
*/
event DisputeCreation(uint indexed _disputeID, Arbitrable indexed _arbitrable);
/** @dev To be raised when a dispute can be appealed.
* @param _disputeID ID of the dispute.
*/
event AppealPossible(uint indexed _disputeID, Arbitrable indexed _arbitrable);
/** @dev To be raised when the current ruling is appealed.
* @param _disputeID ID of the dispute.
* @param _arbitrable The contract which created the dispute.
*/
event AppealDecision(uint indexed _disputeID, Arbitrable indexed _arbitrable);
/** @dev Create a dispute. Must be called by the arbitrable contract.
* Must be paid at least arbitrationCost(_extraData).
* @param _choices Amount of choices the arbitrator can make in this dispute.
* @param _extraData Can be used to give additional info on the dispute to be created.
* @return disputeID ID of the dispute created.
*/
function createDispute(uint _choices, bytes _extraData) public requireArbitrationFee(_extraData) payable returns(uint disputeID) {}
/** @dev Compute the cost of arbitration. It is recommended not to increase it often, as it can be highly time and gas consuming for the arbitrated contracts to cope with fee augmentation.
* @param _extraData Can be used to give additional info on the dispute to be created.
* @return fee Amount to be paid.
*/
function arbitrationCost(bytes _extraData) public view returns(uint fee);
/** @dev Appeal a ruling. Note that it has to be called before the arbitrator contract calls rule.
* @param _disputeID ID of the dispute to be appealed.
* @param _extraData Can be used to give extra info on the appeal.
*/
function appeal(uint _disputeID, bytes _extraData) public requireAppealFee(_disputeID,_extraData) payable {
emit AppealDecision(_disputeID, Arbitrable(msg.sender));
}
/** @dev Compute the cost of appeal. It is recommended not to increase it often, as it can be higly time and gas consuming for the arbitrated contracts to cope with fee augmentation.
* @param _disputeID ID of the dispute to be appealed.
* @param _extraData Can be used to give additional info on the dispute to be created.
* @return fee Amount to be paid.
*/
function appealCost(uint _disputeID, bytes _extraData) public view returns(uint fee);
/** @dev Compute the start and end of the dispute's current or next appeal period, if possible.
* @param _disputeID ID of the dispute.
* @return The start and end of the period.
*/
function appealPeriod(uint _disputeID) public view returns(uint start, uint end) {}
/** @dev Return the status of a dispute.
* @param _disputeID ID of the dispute to rule.
* @return status The status of the dispute.
*/
function disputeStatus(uint _disputeID) public view returns(DisputeStatus status);
/** @dev Return the current ruling of a dispute. This is useful for parties to know if they should appeal.
* @param _disputeID ID of the dispute.
* @return ruling The ruling which has been given or the one which will be given if there is no appeal.
*/
function currentRuling(uint _disputeID) public view returns(uint ruling);
}
/**
* @authors: [@clesaege]
* @reviewers: []
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity ^0.4.24;
import "./Arbitrator.sol";
import "./CappedMath.sol";
/** @title Auto Appealable Arbitrator
* @dev This is a centralized arbitrator which either gives direct rulings or provides a time and fee for appeal.
*/
contract AutoAppealableArbitrator is Arbitrator {
using CappedMath for uint; // Operations bounded between 0 and 2**256 - 1.
address public owner = msg.sender;
uint arbitrationPrice; // Not public because arbitrationCost already acts as an accessor.
uint constant NOT_PAYABLE_VALUE = (2**256 - 2) / 2; // High value to be sure that the appeal is too expensive.
struct Dispute {
Arbitrable arbitrated; // The contract requiring arbitration.
uint choices; // The amount of possible choices, 0 excluded.
uint fees; // The total amount of fees collected by the arbitrator.
uint ruling; // The current ruling.
DisputeStatus status; // The status of the dispute.
uint appealCost; // The cost to appeal. 0 before it is appealable.
uint appealPeriodStart; // The start of the appeal period. 0 before it is appealable.
uint appealPeriodEnd; // The end of the appeal Period. 0 before it is appealable.
}
modifier onlyOwner {require(msg.sender==owner, "Can only be called by the owner."); _;}
Dispute[] public disputes;
/** @dev Constructor. Set the initial arbitration price.
* @param _arbitrationPrice Amount to be paid for arbitration.
*/
constructor(uint _arbitrationPrice) public {
arbitrationPrice = _arbitrationPrice;
}
/** @dev Set the arbitration price. Only callable by the owner.
* @param _arbitrationPrice Amount to be paid for arbitration.
*/
function setArbitrationPrice(uint _arbitrationPrice) external onlyOwner {
arbitrationPrice = _arbitrationPrice;
}
/** @dev Cost of arbitration. Accessor to arbitrationPrice.
* @param _extraData Not used by this contract.
* @return fee Amount to be paid.
*/
function arbitrationCost(bytes _extraData) public view returns(uint fee) {
return arbitrationPrice;
}
/** @dev Cost of appeal. If appeal is not possible, it's a high value which can never be paid.
* @param _disputeID ID of the dispute to be appealed.
* @param _extraData Not used by this contract.
* @return fee Amount to be paid.
*/
function appealCost(uint _disputeID, bytes _extraData) public view returns(uint fee) {
Dispute storage dispute = disputes[_disputeID];
if (dispute.status == DisputeStatus.Appealable)
return dispute.appealCost;
else
return NOT_PAYABLE_VALUE;
}
/** @dev Create a dispute. Must be called by the arbitrable contract.
* Must be paid at least arbitrationCost().
* @param _choices Amount of choices the arbitrator can make in this dispute. When ruling <= choices.
* @param _extraData Can be used to give additional info on the dispute to be created.
* @return disputeID ID of the dispute created.
*/
function createDispute(uint _choices, bytes _extraData) public payable returns(uint disputeID) {
super.createDispute(_choices, _extraData);
disputeID = disputes.push(Dispute({
arbitrated: Arbitrable(msg.sender),
choices: _choices,
fees: msg.value,
ruling: 0,
status: DisputeStatus.Waiting,
appealCost: 0,
appealPeriodStart: 0,
appealPeriodEnd: 0
})) - 1; // Create the dispute and return its number.
emit DisputeCreation(disputeID, Arbitrable(msg.sender));
}
/** @dev Give a ruling. UNTRUSTED.
* @param _disputeID ID of the dispute to rule.
* @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision".
*/
function giveRuling(uint _disputeID, uint _ruling) external onlyOwner {
Dispute storage dispute = disputes[_disputeID];
require(_ruling <= dispute.choices, "Invalid ruling.");
require(dispute.status == DisputeStatus.Waiting, "The dispute must be waiting for arbitration.");
dispute.ruling = _ruling;
dispute.status = DisputeStatus.Solved;
msg.sender.send(dispute.fees); // Avoid blocking.
dispute.arbitrated.rule(_disputeID, _ruling);
}
/** @dev Give an appealable ruling.
* @param _disputeID ID of the dispute to rule.
* @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision".
* @param _appealCost The cost of appeal.
* @param _timeToAppeal The time to appeal the ruling.
*/
function giveAppealableRuling(uint _disputeID, uint _ruling, uint _appealCost, uint _timeToAppeal) external onlyOwner {
Dispute storage dispute = disputes[_disputeID];
require(_ruling <= dispute.choices, "Invalid ruling.");
require(dispute.status == DisputeStatus.Waiting, "The dispute must be waiting for arbitration.");
dispute.ruling = _ruling;
dispute.status = DisputeStatus.Appealable;
dispute.appealCost = _appealCost;
dispute.appealPeriodStart = now;
dispute.appealPeriodEnd = now.addCap(_timeToAppeal);
}
/** @dev Change the appeal fee of a dispute.
* @param _disputeID The ID of the dispute to update.
* @param _appealCost The new cost to appeal this ruling.
*/
function changeAppealFee(uint _disputeID, uint _appealCost) external onlyOwner {
Dispute storage dispute = disputes[_disputeID];
require(dispute.status == DisputeStatus.Appealable, "The dispute must be appealable.");
dispute.appealCost = _appealCost;
}
/** @dev Appeal a ruling. Note that it has to be called before the arbitrator contract calls rule.
* @param _disputeID ID of the dispute to be appealed.
* @param _extraData Can be used to give extra info on the appeal.
*/
function appeal(uint _disputeID, bytes _extraData) public requireAppealFee(_disputeID, _extraData) payable {
Dispute storage dispute = disputes[_disputeID];
require(dispute.status == DisputeStatus.Appealable, "The dispute must be appealable.");
require(now < dispute.appealPeriodEnd, "The appeal must occur before the end of the appeal period.");
dispute.fees += msg.value;
dispute.status = DisputeStatus.Waiting;
emit AppealDecision(_disputeID, Arbitrable(msg.sender));
}
/** @dev Execute the ruling of a dispute after the appeal period has passed. UNTRUSTED.
* @param _disputeID ID of the dispute to execute.
*/
function executeRuling(uint _disputeID) external {
Dispute storage dispute = disputes[_disputeID];
require(dispute.status == DisputeStatus.Appealable, "The dispute must be appealable.");
require(now >= dispute.appealPeriodEnd, "The dispute must be executed after its appeal period has ended.");
dispute.status = DisputeStatus.Solved;
msg.sender.send(dispute.fees); // Avoid blocking.
dispute.arbitrated.rule(_disputeID, dispute.ruling);
}
/** @dev Return the status of a dispute (in the sense of ERC792, not the Dispute property).
* @param _disputeID ID of the dispute to rule.
* @return status The status of the dispute.
*/
function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) {
Dispute storage dispute = disputes[_disputeID];
if (disputes[_disputeID].status==DisputeStatus.Appealable && now>=dispute.appealPeriodEnd) // If the appeal period is over, consider it solved even if rule has not been called yet.
return DisputeStatus.Solved;
else
return disputes[_disputeID].status;
}
/** @dev Return the ruling of a dispute.
* @param _disputeID ID of the dispute.
* @return ruling The ruling which have been given or which would be given if no appeals are raised.
*/
function currentRuling(uint _disputeID) public view returns(uint ruling) {
return disputes[_disputeID].ruling;
}
/** @dev Compute the start and end of the dispute's current or next appeal period, if possible.
* @param _disputeID ID of the dispute.
* @return The start and end of the period.
*/
function appealPeriod(uint _disputeID) public view returns(uint start, uint end) {
Dispute storage dispute = disputes[_disputeID];
return (dispute.appealPeriodStart, dispute.appealPeriodEnd);
}
}
/**
* @authors: [@mtsalenc]
* @reviewers: [@clesaege]
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity ^0.4.24;
/**
* @title CappedMath
* @dev Math operations with caps for under and overflow.
*/
library CappedMath {
uint constant private UINT_MAX = 2**256 - 1;
/**
* @dev Adds two unsigned integers, returns 2^256 - 1 on overflow.
*/
function addCap(uint _a, uint _b) internal pure returns (uint) {
uint c = _a + _b;
return c >= _a ? c : UINT_MAX;
}
/**
* @dev Subtracts two integers, returns 0 on underflow.
*/
function subCap(uint _a, uint _b) internal pure returns (uint) {
if (_b > _a)
return 0;
else
return _a - _b;
}
/**
* @dev Multiplies two unsigned integers, returns 2^256 - 1 on overflow.
*/
function mulCap(uint _a, uint _b) internal pure returns (uint) {
// Gas optimization: this is cheaper than requiring '_a' not being zero, but the
// benefit is lost if '_b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (_a == 0)
return 0;
uint c = _a * _b;
return c / _a == _b ? c : UINT_MAX;
}
}
/**
* @authors: [@clesaege, @n1c01a5, @epiqueras, @ferittuncer]
* @reviewers: [@clesaege*, @unknownunknown1*]
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity ^0.4.15;
import "./Arbitrator.sol";
/** @title Centralized Arbitrator
* @dev This is a centralized arbitrator deciding alone on the result of disputes. No appeals are possible.
*/
contract CentralizedArbitrator is Arbitrator {
address public owner = msg.sender;
uint arbitrationPrice; // Not public because arbitrationCost already acts as an accessor.
uint constant NOT_PAYABLE_VALUE = (2**256-2)/2; // High value to be sure that the appeal is too expensive.
struct DisputeStruct {
Arbitrable arbitrated;
uint choices;
uint fee;
uint ruling;
DisputeStatus status;
}
modifier onlyOwner {require(msg.sender==owner, "Can only be called by the owner."); _;}
DisputeStruct[] public disputes;
/** @dev Constructor. Set the initial arbitration price.
* @param _arbitrationPrice Amount to be paid for arbitration.
*/
constructor(uint _arbitrationPrice) public {
arbitrationPrice = _arbitrationPrice;
}
/** @dev Set the arbitration price. Only callable by the owner.
* @param _arbitrationPrice Amount to be paid for arbitration.
*/
function setArbitrationPrice(uint _arbitrationPrice) public onlyOwner {
arbitrationPrice = _arbitrationPrice;
}
/** @dev Cost of arbitration. Accessor to arbitrationPrice.
* @param _extraData Not used by this contract.
* @return fee Amount to be paid.
*/
function arbitrationCost(bytes _extraData) public view returns(uint fee) {
return arbitrationPrice;
}
/** @dev Cost of appeal. Since it is not possible, it's a high value which can never be paid.
* @param _disputeID ID of the dispute to be appealed. Not used by this contract.
* @param _extraData Not used by this contract.
* @return fee Amount to be paid.
*/
function appealCost(uint _disputeID, bytes _extraData) public view returns(uint fee) {
return NOT_PAYABLE_VALUE;
}
/** @dev Create a dispute. Must be called by the arbitrable contract.
* Must be paid at least arbitrationCost().
* @param _choices Amount of choices the arbitrator can make in this dispute. When ruling ruling<=choices.
* @param _extraData Can be used to give additional info on the dispute to be created.
* @return disputeID ID of the dispute created.
*/
function createDispute(uint _choices, bytes _extraData) public payable returns(uint disputeID) {
super.createDispute(_choices, _extraData);
disputeID = disputes.push(DisputeStruct({
arbitrated: Arbitrable(msg.sender),
choices: _choices,
fee: msg.value,
ruling: 0,
status: DisputeStatus.Waiting
})) - 1; // Create the dispute and return its number.
emit DisputeCreation(disputeID, Arbitrable(msg.sender));
}
/** @dev Give a ruling. UNTRUSTED.
* @param _disputeID ID of the dispute to rule.
* @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision".
*/
function _giveRuling(uint _disputeID, uint _ruling) internal {
DisputeStruct storage dispute = disputes[_disputeID];
require(_ruling <= dispute.choices, "Invalid ruling.");
require(dispute.status != DisputeStatus.Solved, "The dispute must not be solved already.");
dispute.ruling = _ruling;
dispute.status = DisputeStatus.Solved;
msg.sender.send(dispute.fee); // Avoid blocking.
dispute.arbitrated.rule(_disputeID,_ruling);
}
/** @dev Give a ruling. UNTRUSTED.
* @param _disputeID ID of the dispute to rule.
* @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision".
*/
function giveRuling(uint _disputeID, uint _ruling) public onlyOwner {
return _giveRuling(_disputeID, _ruling);
}
/** @dev Return the status of a dispute.
* @param _disputeID ID of the dispute to rule.
* @return status The status of the dispute.
*/
function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) {
return disputes[_disputeID].status;
}
/** @dev Return the ruling of a dispute.
* @param _disputeID ID of the dispute to rule.
* @return ruling The ruling which would or has been given.
*/
function currentRuling(uint _disputeID) public view returns(uint ruling) {
return disputes[_disputeID].ruling;
}
}
/**
* @title IArbitrable
* @author Enrique Piqueras - <enrique@kleros.io>
* Bug Bounties: This code hasn't undertaken a bug bounty program yet.
*/
pragma solidity ^0.4.15;
import "./Arbitrator.sol";
/** @title IArbitrable
* Arbitrable interface.
* When developing arbitrable contracts, we need to:
* -Define the action taken when a ruling is received by the contract. We should do so in executeRuling.
* -Allow dispute creation. For this a function must:
* -Call arbitrator.createDispute.value(_fee)(_choices,_extraData);
* -Create the event Dispute(_arbitrator,_disputeID,_rulingOptions);
*/
interface IArbitrable {
/** @dev To be emmited when meta-evidence is submitted.
* @param _metaEvidenceID Unique identifier of meta-evidence.
* @param _evidence A link to the meta-evidence JSON.
*/
event MetaEvidence(uint indexed _metaEvidenceID, string _evidence);
/** @dev To be emmited when a dispute is created to link the correct meta-evidence to the disputeID
* @param _arbitrator The arbitrator of the contract.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _metaEvidenceID Unique identifier of meta-evidence.
* @param _evidenceGroupID Unique identifier of the evidence group that is linked to this dispute.
*/
event Dispute(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _metaEvidenceID, uint _evidenceGroupID);
/** @dev To be raised when evidence are submitted. Should point to the ressource (evidences are not to be stored on chain due to gas considerations).
* @param _arbitrator The arbitrator of the contract.
* @param _evidenceGroupID Unique identifier of the evidence group the evidence belongs to.
* @param _party The address of the party submiting the evidence. Note that 0x0 refers to evidence not submitted by any party.
* @param _evidence A URI to the evidence JSON file whose name should be its keccak256 hash followed by .json.
*/
event Evidence(Arbitrator indexed _arbitrator, uint indexed _evidenceGroupID, address indexed _party, string _evidence);
/** @dev To be raised when a ruling is given.
* @param _arbitrator The arbitrator giving the ruling.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling The ruling which was given.
*/
event Ruling(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling);
/** @dev Give a ruling for a dispute. Must be called by the arbitrator.
* The purpose of this function is to ensure that the address calling it has the right to rule on the contract.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision".
*/
function rule(uint _disputeID, uint _ruling) public;
}
/**
* @title Permission Interface
* @author Clément Lesaege - <clement@lesaege.com>
*/
pragma solidity ^0.4.15;
/**
* @title Permission Interface
* This is a permission interface for arbitrary values. The values can be cast to the required types.
*/
interface PermissionInterface{
/* External */
/**
* @dev Return true if the value is allowed.
* @param _value The value we want to check.
* @return allowed True if the value is allowed, false otherwise.
*/
function isPermitted(bytes32 _value) external view returns (bool allowed);
}
pragma solidity ^0.4.24;
import "./Arbitrable.sol";
import "./Arbitrator.sol";
contract SelfCommitment is IArbitrable {
event Log(string debugInfo); // I'm totally new to this business, figuring things out
modifier onlyArbitrator {require(msg.sender == address(arbitrator), "Can only be called by the arbitrator."); _;}
Arbitrator public arbitrator; // address of the arbitrator contract
bytes public arbitratorExtraData; // potentially Court ID)
uint8 constant AMOUNT_OF_CHOICES = 2;
uint8 constant REFUSED = 0; // arbitrator always has
uint8 constant OK = 1;
uint8 constant FAIL = 2;
uint constant MAX_INT = (2**256-2)/2; // 0 is valid disputeID, initialising with MAX_INT
modifier onlyOwner {require(msg.sender == address(owner), "Can only be called by the owner."); _;}
address public owner;
constructor(Arbitrator _arbitrator, bytes _arbitratorExtraData) public {
owner = msg.sender;
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
function changeArbitrator(Arbitrator _arbitrator, bytes _arbitratorExtraData) onlyOwner public {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
enum ChallengeState { initial, inprogress, success, failed }
enum SubmissionState { initial, challenged, accepted, rejected }
event ChallengeAdd(address creator, uint deposit, string description, uint beginning, uint end, uint count, uint timestamp);
event SubmissionAdd(address creator, uint challengeID, string url, string comment, uint timestamp);
struct Challenge {
address user;
uint deposit;
string description;
uint beginning;
uint end;
uint count; // how many
ChallengeState state;
}
struct Submission {
uint challengeID;
string url; // YouTube url, have IPFS integration ready: https://discuss.ipfs.io/t/is-there-any-other-methods-to-upload-a-file-files-to-ipfs-through-a-web-browser-beside-using-api-port-5001/3143/10?u=mars
string comment;
uint timestamp;
uint disputeID;
SubmissionState state;
}
Challenge[] public challenges;
Submission[] public submissions;
string[] public MetaEvidenceHashes;
function getChallengesCount() public view returns(uint) { return challenges.length; }
function getSubmissionsCount() public view returns(uint) { return submissions.length; }
mapping (address => uint[]) public userToChallengeIDs; // user can have multiple challanges
mapping (uint => uint[]) public chalengeIDToSubmissionIDs; // challenge has multiple submissions, returning the array containing all of them
mapping (uint => uint) public disputeIDToSubmissionID; // dispute refers to particular submission, not the whole challenge
mapping (uint => string) public challengeIDToMetaEvidenceHash; // challenge has multiple submissions but only one MetaEvidence (that describes challenge)
function getUserChallengeIDs(address user) public view returns(uint[] memory) {
uint[] memory IDs = userToChallengeIDs[user];
return IDs;
}
function getChallengeSubmissionIDs(uint challengeID) public view returns(uint[] memory) {
uint[] memory IDs = chalengeIDToSubmissionIDs[challengeID];
return IDs;
}
function createChallenge(string memory _description, uint _beginning, uint _end, uint _count) payable public returns (uint) {
require(msg.value > 0, "You need to send a deposit"); // require a deposit, otherwise what's the point.
// require(_beginning > now, "Challenge cannot start in the past");
// require(_end > now + 1 days, "Challenge must last at least 1 day");
require(_count > 1, "You need to commit to do the thing at least once");
Challenge memory challenge = Challenge({user: msg.sender, deposit: msg.value, description: _description, beginning: _beginning, end: _end, count: _count, state: ChallengeState.initial});
uint id = challenges.push(challenge) - 1; // push returns the lenghts of the array https://ethereum.stackexchange.com/questions/40312/what-is-the-return-of-array-push-in-solidity
userToChallengeIDs[msg.sender].push(id);
emit ChallengeAdd(msg.sender, msg.value, _description, _beginning, _end, _count, now);
return id;
}
function createSubmission(string memory _url, string memory _comment, uint _challengeID) public returns (uint) {
require(challenges[_challengeID].user == msg.sender); // only the guy who sets the challenge can add new stuff
Submission memory submission = Submission({challengeID : _challengeID, url : _url, comment: _comment, timestamp: now, disputeID: MAX_INT, state: SubmissionState.initial });
uint id = submissions.push(submission) - 1;
chalengeIDToSubmissionIDs[_challengeID].push(id);
emit SubmissionAdd(msg.sender, _challengeID, _url, _comment, now);
return id;
}
function getChallengeById(uint256 challengeID) public view returns(address, uint, string memory, uint, uint, uint, ChallengeState) {
Challenge memory c = challenges[challengeID];
return(c.user, c.deposit, c.description, c.beginning, c.end, c.count, c.state);
}
function getSubmissionById(uint256 submissionID) public view returns(uint, string memory, string memory, uint, SubmissionState) {
Submission memory s = submissions[submissionID];
return(s.challengeID, s.url, s.comment, s.timestamp, s.state);
}
// A lot things will happen on the front-end
// Building metaEvidence.json and evidence.json and then uploading to IPFS
// Here we are only submitting IPFS paths to preserve on-chain storage
// We could use any "traditional" centralized storage, that's why using ipfs:// URI qualifier
function disputeSubmission(uint _submissionID, string _metaEvidenceURI, string _evidenceURI) public { // public: any internet troll can dispute submission
emit Log("before disputeSubmission");
uint disputeID = arbitrator.createDispute.value(0)(AMOUNT_OF_CHOICES, ""); // "" means no extraData
Submission storage s = submissions[_submissionID];
s.disputeID = disputeID;
s.state = SubmissionState.voting;
emit Log("after disputeSubmission");
}
// @override (why Solidity do not specify @override keyword?) part of IArbitrable
function rule(uint _disputeID, uint _ruling) public onlyArbitrator {
emit Ruling(Arbitrator(msg.sender), _disputeID, _ruling);
executeRuling(_disputeID, _ruling);
}
function executeRuling(uint _disputeID, uint _ruling) internal {
Submission storage s = submissions[ disputeIDToSubmissionID[_disputeID] ];
if (_ruling == OK) {
s.state = SubmissionState.accepted;
} else if (_ruling == FAIL || _ruling == REFUSED) {
s.state = SubmissionState.rejected;
}
emit Log("executeRuling");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment