Skip to content

Instantly share code, notes, and snippets.

@n1c01a5
Created March 29, 2019 18:16
Show Gist options
  • Save n1c01a5/23fef2c6478ae9e1e81d5eb357f63503 to your computer and use it in GitHub Desktop.
Save n1c01a5/23fef2c6478ae9e1e81d5eb357f63503 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.24+commit.e67f0147.js&optimize=true&gist=
pragma solidity >=0.4.22 <0.6.0;
contract Ballot {
struct Voter {
uint weight;
bool voted;
uint8 vote;
address delegate;
}
struct Proposal {
uint voteCount;
}
address chairperson;
mapping(address => Voter) voters;
Proposal[] proposals;
/// Create a new ballot with $(_numProposals) different proposals.
constructor(uint8 _numProposals) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
proposals.length = _numProposals;
}
/// Give $(toVoter) the right to vote on this ballot.
/// May only be called by $(chairperson).
function giveRightToVote(address toVoter) public {
if (msg.sender != chairperson || voters[toVoter].voted) return;
voters[toVoter].weight = 1;
}
/// Delegate your vote to the voter $(to).
function delegate(address to) public {
Voter storage sender = voters[msg.sender]; // assigns reference
if (sender.voted) return;
while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender)
to = voters[to].delegate;
if (to == msg.sender) return;
sender.voted = true;
sender.delegate = to;
Voter storage delegateTo = voters[to];
if (delegateTo.voted)
proposals[delegateTo.vote].voteCount += sender.weight;
else
delegateTo.weight += sender.weight;
}
/// Give a single vote to proposal $(toProposal).
function vote(uint8 toProposal) public {
Voter storage sender = voters[msg.sender];
if (sender.voted || toProposal >= proposals.length) return;
sender.voted = true;
sender.vote = toProposal;
proposals[toProposal].voteCount += sender.weight;
}
function winningProposal() public view returns (uint8 _winningProposal) {
uint256 winningVoteCount = 0;
for (uint8 prop = 0; prop < proposals.length; prop++)
if (proposals[prop].voteCount > winningVoteCount) {
winningVoteCount = proposals[prop].voteCount;
_winningProposal = prop;
}
}
}
import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./ballot.sol";
contract test3 {
Ballot ballotToTest;
function beforeAll () public {
ballotToTest = new Ballot(2);
}
function checkWinningProposal () public {
ballotToTest.vote(1);
Assert.equal(ballotToTest.winningProposal(), uint(1), "1 should be the winning proposal");
}
function checkWinninProposalWithReturnValue () public view returns (bool) {
return ballotToTest.winningProposal() == 1;
}
}
pragma solidity ^0.4.24;
interface IArbitrable {
event MetaEvidence(uint indexed _metaEvidenceID, string _evidence);
event Dispute(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _metaEvidenceID, uint _evidenceGroupID);
event Evidence(Arbitrator indexed _arbitrator, uint indexed _evidenceGroupID, address indexed _party, string _evidence);
event Ruling(Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling);
function rule(uint _disputeID, uint _ruling) external;
}
contract Arbitrable is IArbitrable {
Arbitrator public arbitrator;
bytes public arbitratorExtraData;
modifier onlyArbitrator {require(msg.sender == address(arbitrator), "Can only be called by the arbitrator."); _;}
constructor(Arbitrator _arbitrator, bytes memory _arbitratorExtraData) public {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
}
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;
}
contract Arbitrator {
enum DisputeStatus {Waiting, Appealable, Solved}
modifier requireArbitrationFee(bytes memory _extraData) {
require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration costs.");
_;
}
modifier requireAppealFee(uint _disputeID, bytes memory _extraData) {
require(msg.value >= appealCost(_disputeID, _extraData), "Not enough ETH to cover appeal costs.");
_;
}
event DisputeCreation(uint indexed _disputeID, Arbitrable indexed _arbitrable);
event AppealPossible(uint indexed _disputeID, Arbitrable indexed _arbitrable);
event AppealDecision(uint indexed _disputeID, Arbitrable indexed _arbitrable);
function createDispute(uint _choices, bytes memory _extraData) public requireArbitrationFee(_extraData) payable returns(uint disputeID) {}
function arbitrationCost(bytes memory _extraData) public view returns(uint fee);
function appeal(uint _disputeID, bytes memory _extraData) public requireAppealFee(_disputeID,_extraData) payable {
emit AppealDecision(_disputeID, Arbitrable(msg.sender));
}
function appealCost(uint _disputeID, bytes memory _extraData) public view returns(uint fee);
function appealPeriod(uint _disputeID) public view returns(uint start, uint end) {}
function disputeStatus(uint _disputeID) public view returns(DisputeStatus status);
function currentRuling(uint _disputeID) public view returns(uint ruling);
}
/**
* @authors: [@clesaege, @n1c01a5, @epiqueras, @ferittuncer]
* @reviewers: [@clesaege*, @unknownunknown1*]
* @auditors: []
* @bounties: []
* @deployments: []
*/
/** @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 AppealableArbitrator
* @author Enrique Piqueras - <epiquerass@gmail.com>
* @dev A centralized arbitrator that can be appealed.
*/
contract AppealableArbitrator is CentralizedArbitrator, Arbitrable {
/* Structs */
struct AppealDispute {
uint rulingTime;
Arbitrator arbitrator;
uint appealDisputeID;
}
/* Storage */
uint public timeOut;
mapping(uint => AppealDispute) public appealDisputes;
mapping(uint => uint) public appealDisputeIDsToDisputeIDs;
/* Constructor */
/** @dev Constructs the `AppealableArbitrator` contract.
* @param _arbitrationPrice The amount to be paid for arbitration.
* @param _arbitrator The back up arbitrator.
* @param _arbitratorExtraData Not used by this contract.
* @param _timeOut The time out for the appeal period.
*/
constructor(
uint _arbitrationPrice,
Arbitrator _arbitrator,
bytes _arbitratorExtraData,
uint _timeOut
) public CentralizedArbitrator(_arbitrationPrice) Arbitrable(_arbitrator, _arbitratorExtraData) {
timeOut = _timeOut;
}
/* External */
/** @dev Changes the back up arbitrator.
* @param _arbitrator The new back up arbitrator.
*/
function changeArbitrator(Arbitrator _arbitrator) external onlyOwner {
arbitrator = _arbitrator;
}
/** @dev Changes the time out.
* @param _timeOut The new time out.
*/
function changeTimeOut(uint _timeOut) external onlyOwner {
timeOut = _timeOut;
}
/* External Views */
/** @dev Gets the specified dispute's latest appeal ID.
* @param _disputeID The ID of the dispute.
*/
function getAppealDisputeID(uint _disputeID) external view returns(uint disputeID) {
if (appealDisputes[_disputeID].arbitrator != Arbitrator(address(0)))
disputeID = AppealableArbitrator(appealDisputes[_disputeID].arbitrator).getAppealDisputeID(appealDisputes[_disputeID].appealDisputeID);
else disputeID = _disputeID;
}
/* Public */
/** @dev Appeals a ruling.
* @param _disputeID The ID of the dispute.
* @param _extraData Additional info about the appeal.
*/
function appeal(uint _disputeID, bytes _extraData) public payable requireAppealFee(_disputeID, _extraData) {
super.appeal(_disputeID, _extraData);
if (appealDisputes[_disputeID].arbitrator != Arbitrator(address(0)))
appealDisputes[_disputeID].arbitrator.appeal.value(msg.value)(appealDisputes[_disputeID].appealDisputeID, _extraData);
else {
appealDisputes[_disputeID].arbitrator = arbitrator;
appealDisputes[_disputeID].appealDisputeID = arbitrator.createDispute.value(msg.value)(disputes[_disputeID].choices, _extraData);
appealDisputeIDsToDisputeIDs[appealDisputes[_disputeID].appealDisputeID] = _disputeID;
}
}
/** @dev Gives a ruling.
* @param _disputeID The ID of the dispute.
* @param _ruling The ruling.
*/
function giveRuling(uint _disputeID, uint _ruling) public {
require(disputes[_disputeID].status != DisputeStatus.Solved, "The specified dispute is already resolved.");
if (appealDisputes[_disputeID].arbitrator != Arbitrator(address(0))) {
require(Arbitrator(msg.sender) == appealDisputes[_disputeID].arbitrator, "Appealed disputes must be ruled by their back up arbitrator.");
super._giveRuling(_disputeID, _ruling);
} else {
require(msg.sender == owner, "Not appealed disputes must be ruled by the owner.");
if (disputes[_disputeID].status == DisputeStatus.Appealable) {
if (now - appealDisputes[_disputeID].rulingTime > timeOut)
super._giveRuling(_disputeID, disputes[_disputeID].ruling);
else revert("Time out time has not passed yet.");
} else {
disputes[_disputeID].ruling = _ruling;
disputes[_disputeID].status = DisputeStatus.Appealable;
appealDisputes[_disputeID].rulingTime = now;
emit AppealPossible(_disputeID, disputes[_disputeID].arbitrated);
}
}
}
/* Public Views */
/** @dev Gets the cost of appeal for the specified dispute.
* @param _disputeID The ID of the dispute.
* @param _extraData Additional info about the appeal.
* @return The cost of the appeal.
*/
function appealCost(uint _disputeID, bytes _extraData) public view returns(uint cost) {
if (appealDisputes[_disputeID].arbitrator != Arbitrator(address(0)))
cost = appealDisputes[_disputeID].arbitrator.appealCost(appealDisputes[_disputeID].appealDisputeID, _extraData);
else if (disputes[_disputeID].status == DisputeStatus.Appealable) cost = arbitrator.arbitrationCost(_extraData);
else cost = NOT_PAYABLE_VALUE;
}
/** @dev Gets the status of the specified dispute.
* @param _disputeID The ID of the dispute.
* @return The status.
*/
function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) {
if (appealDisputes[_disputeID].arbitrator != Arbitrator(address(0)))
status = appealDisputes[_disputeID].arbitrator.disputeStatus(appealDisputes[_disputeID].appealDisputeID);
else status = disputes[_disputeID].status;
}
/* Internal */
/** @dev Executes the ruling of the specified dispute.
* @param _disputeID The ID of the dispute.
* @param _ruling The ruling.
*/
function executeRuling(uint _disputeID, uint _ruling) internal {
require(
appealDisputes[appealDisputeIDsToDisputeIDs[_disputeID]].arbitrator != Arbitrator(address(0)),
"The dispute must have been appealed."
);
giveRuling(appealDisputeIDsToDisputeIDs[_disputeID], _ruling);
}
}
contract MultipleArbitrableTransaction is IArbitrable {
// **************************** //
// * Contract variables * //
// **************************** //
uint8 constant AMOUNT_OF_CHOICES = 2;
uint8 constant SENDER_WINS = 1;
uint8 constant RECEIVER_WINS = 2;
enum Party {Sender, Receiver}
enum Status {NoDispute, WaitingSender, WaitingReceiver, DisputeCreated, Resolved}
struct Transaction {
address sender;
address receiver;
uint256 amount;
uint256 timeoutPayment; // Time in seconds after which the transaction can be automatically executed if not disputed.
uint disputeId; // If dispute exists, the ID of the dispute.
uint senderFee; // Total fees paid by the sender.
uint receiverFee; // Total fees paid by the receiver.
uint lastInteraction; // Last interaction for the dispute procedure.
Status status;
}
Transaction[] public transactions;
bytes public arbitratorExtraData; // Extra data to set up the arbitration.
Arbitrator public arbitrator; // Address of the arbitrator contract.
uint public feeTimeout; // Time in seconds a party can take to pay arbitration fees before being considered unresponding and lose the dispute.
mapping (uint => uint) public disputeIDtoTransactionID; // One-to-one relationship between the dispute and the transaction.
// **************************** //
// * Events * //
// **************************** //
/** @dev To be emitted when meta-evidence is submitted.
* @param _metaEvidenceID Unique identifier of meta-evidence. Should be the `transactionID`.
* @param _evidence A link to the meta-evidence JSON that follows the ERC 1497 Evidence standard (https://github.com/ethereum/EIPs/issues/1497).
*/
event MetaEvidence(uint indexed _metaEvidenceID, string _evidence);
/** @dev Indicate that a party has to pay a fee or would otherwise be considered as losing.
* @param _transactionID The index of the transaction.
* @param _party The party who has to pay.
*/
event HasToPayFee(uint indexed _transactionID, Party _party);
/** @dev To be raised when evidence is submitted. Should point to the resource (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 submitting the evidence. Note that 0 is kept for evidences not submitted by any party.
* @param _evidence A link to an evidence JSON that follows the ERC 1497 Evidence standard (https://github.com/ethereum/EIPs/issues/1497).
*/
event Evidence(Arbitrator indexed _arbitrator, uint indexed _evidenceGroupID, address indexed _party, string _evidence);
/** @dev To be emitted 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. Should be the transactionID.
* @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 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);
// **************************** //
// * Arbitrable functions * //
// * Modifying the state * //
// **************************** //
/** @dev Constructor.
* @param _arbitrator The arbitrator of the contract.
* @param _arbitratorExtraData Extra data for the arbitrator.
* @param _feeTimeout Arbitration fee timeout for the parties.
*/
constructor (
Arbitrator _arbitrator,
bytes _arbitratorExtraData,
uint _feeTimeout
) public {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
feeTimeout = _feeTimeout;
}
/** @dev Create a transaction.
* @param _timeoutPayment Time after which a party can automatically execute the arbitrable transaction.
* @param _receiver The recipient of the transaction.
* @param _metaEvidence Link to the meta-evidence.
* @return transactionID The index of the transaction.
*/
function createTransaction(
uint _timeoutPayment,
address _receiver,
string _metaEvidence
) public payable returns (uint transactionID) {
transactions.push(Transaction({
sender: msg.sender,
receiver: _receiver,
amount: msg.value,
timeoutPayment: _timeoutPayment,
disputeId: 0,
senderFee: 0,
receiverFee: 0,
lastInteraction: now,
status: Status.NoDispute
}));
emit MetaEvidence(transactions.length - 1, _metaEvidence);
return transactions.length - 1;
}
/** @dev Pay receiver. To be called if the good or service is provided.
* @param _transactionID The index of the transaction.
* @param _amount Amount to pay in wei.
*/
function pay(uint _transactionID, uint _amount) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.sender == msg.sender, "The caller must be the sender.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
require(_amount <= transaction.amount, "The amount paid has to be less than or equal to the transaction.");
transaction.receiver.transfer(_amount);
transaction.amount -= _amount;
}
/** @dev Reimburse sender. To be called if the good or service can't be fully provided.
* @param _transactionID The index of the transaction.
* @param _amountReimbursed Amount to reimburse in wei.
*/
function reimburse(uint _transactionID, uint _amountReimbursed) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.receiver == msg.sender, "The caller must be the receiver.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
require(_amountReimbursed <= transaction.amount, "The amount reimbursed has to be less or equal than the transaction.");
transaction.sender.transfer(_amountReimbursed);
transaction.amount -= _amountReimbursed;
}
/** @dev Transfer the transaction's amount to the receiver if the timeout has passed.
* @param _transactionID The index of the transaction.
*/
function executeTransaction(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(now - transaction.lastInteraction >= transaction.timeoutPayment, "The timeout has not passed yet.");
require(transaction.status == Status.NoDispute, "The transaction shouldn't be disputed.");
transaction.receiver.transfer(transaction.amount);
transaction.amount = 0;
transaction.status = Status.Resolved;
}
/** @dev Reimburse sender if receiver fails to pay the fee.
* @param _transactionID The index of the transaction.
*/
function timeOutBySender(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.status == Status.WaitingReceiver, "The transaction is not waiting on the receiver.");
require(now - transaction.lastInteraction >= feeTimeout, "Timeout time has not passed yet.");
executeRuling(_transactionID, SENDER_WINS);
}
/** @dev Pay receiver if sender fails to pay the fee.
* @param _transactionID The index of the transaction.
*/
function timeOutByReceiver(uint _transactionID) public {
Transaction storage transaction = transactions[_transactionID];
require(transaction.status == Status.WaitingSender, "The transaction is not waiting on the sender.");
require(now - transaction.lastInteraction >= feeTimeout, "Timeout time has not passed yet.");
executeRuling(_transactionID, RECEIVER_WINS);
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the sender. UNTRUSTED.
* Note that the arbitrator can have createDispute throw, which will make this function throw and therefore lead to a party being timed-out.
* This is not a vulnerability as the arbitrator can rule in favor of one party anyway.
* @param _transactionID The index of the transaction.
*/
function payArbitrationFeeBySender(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
require(transaction.status < Status.DisputeCreated, "Dispute has already been created or because the transaction has been executed.");
require(msg.sender == transaction.sender, "The caller must be the sender.");
transaction.senderFee += msg.value;
// Require that the total pay at least the arbitration cost.
require(transaction.senderFee >= arbitrationCost, "The sender fee must cover arbitration costs.");
transaction.lastInteraction = now;
// The receiver still has to pay. This can also happen if he has paid, but arbitrationCost has increased.
if (transaction.receiverFee < arbitrationCost) {
transaction.status = Status.WaitingReceiver;
emit HasToPayFee(_transactionID, Party.Receiver);
} else { // The receiver has also paid the fee. We create the dispute.
raiseDispute(_transactionID, arbitrationCost);
}
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the receiver. UNTRUSTED.
* Note that this function mirrors payArbitrationFeeBySender.
* @param _transactionID The index of the transaction.
*/
function payArbitrationFeeByReceiver(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
require(transaction.status < Status.DisputeCreated, "Dispute has already been created or because the transaction has been executed.");
require(msg.sender == transaction.receiver, "The caller must be the receiver.");
transaction.receiverFee += msg.value;
// Require that the total paid to be at least the arbitration cost.
require(transaction.receiverFee >= arbitrationCost, "The receiver fee must cover arbitration costs.");
transaction.lastInteraction = now;
// The sender still has to pay. This can also happen if he has paid, but arbitrationCost has increased.
if (transaction.senderFee < arbitrationCost) {
transaction.status = Status.WaitingSender;
emit HasToPayFee(_transactionID, Party.Sender);
} else { // The sender has also paid the fee. We create the dispute.
raiseDispute(_transactionID, arbitrationCost);
}
}
/** @dev Create a dispute. UNTRUSTED.
* @param _transactionID The index of the transaction.
* @param _arbitrationCost Amount to pay the arbitrator.
*/
function raiseDispute(uint _transactionID, uint _arbitrationCost) internal {
Transaction storage transaction = transactions[_transactionID];
transaction.status = Status.DisputeCreated;
transaction.disputeId = arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES, arbitratorExtraData);
disputeIDtoTransactionID[transaction.disputeId] = _transactionID;
emit Dispute(arbitrator, transaction.disputeId, _transactionID, _transactionID);
// Refund sender if it overpaid.
if (transaction.senderFee > _arbitrationCost) {
uint extraFeeSender = transaction.senderFee - _arbitrationCost;
transaction.senderFee = _arbitrationCost;
transaction.sender.send(extraFeeSender);
}
// Refund receiver if it overpaid.
if (transaction.receiverFee > _arbitrationCost) {
uint extraFeeReceiver = transaction.receiverFee - _arbitrationCost;
transaction.receiverFee = _arbitrationCost;
transaction.receiver.send(extraFeeReceiver);
}
}
/** @dev Submit a reference to evidence. EVENT.
* @param _transactionID The index of the transaction.
* @param _evidence A link to an evidence using its URI.
*/
function submitEvidence(uint _transactionID, string _evidence) public {
Transaction storage transaction = transactions[_transactionID];
require(
msg.sender == transaction.sender || msg.sender == transaction.receiver,
"The caller must be the sender or the receiver."
);
require(
transaction.status < Status.Resolved,
"Must not send evidence if the dispute is resolved."
);
emit Evidence(arbitrator, _transactionID, msg.sender, _evidence);
}
/** @dev Appeal an appealable ruling.
* Transfer the funds to the arbitrator.
* Note that no checks are required as the checks are done by the arbitrator.
* @param _transactionID The index of the transaction.
*/
function appeal(uint _transactionID) public payable {
Transaction storage transaction = transactions[_transactionID];
arbitrator.appeal.value(msg.value)(transaction.disputeId, 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 {
uint transactionID = disputeIDtoTransactionID[_disputeID];
Transaction storage transaction = transactions[transactionID];
require(msg.sender == address(arbitrator), "The caller must be the arbitrator.");
require(transaction.status == Status.DisputeCreated, "The dispute has already been resolved.");
emit Ruling(Arbitrator(msg.sender), _disputeID, _ruling);
executeRuling(transactionID, _ruling);
}
/** @dev Execute a ruling of a dispute. It reimburses the fee to the winning party.
* @param _transactionID The index of the transaction.
* @param _ruling Ruling given by the arbitrator. 1 : Reimburse the receiver. 2 : Pay the sender.
*/
function executeRuling(uint _transactionID, uint _ruling) internal {
Transaction storage transaction = transactions[_transactionID];
require(_ruling <= AMOUNT_OF_CHOICES, "Invalid ruling.");
// Give the arbitration fee back.
// Note that we use send to prevent a party from blocking the execution.
if (_ruling == SENDER_WINS) {
transaction.sender.send(transaction.senderFee + transaction.amount);
} else if (_ruling == RECEIVER_WINS) {
transaction.receiver.send(transaction.receiverFee + transaction.amount);
} else {
uint split_amount = (transaction.senderFee + transaction.amount) / 2;
transaction.sender.send(split_amount);
transaction.receiver.send(split_amount);
}
transaction.amount = 0;
transaction.senderFee = 0;
transaction.receiverFee = 0;
transaction.status = Status.Resolved;
}
// **************************** //
// * Constant getters * //
// **************************** //
/** @dev Getter to know the count of transactions.
* @return countTransactions The count of transactions.
*/
function getCountTransactions() public view returns (uint countTransactions) {
return transactions.length;
}
/** @dev Get IDs for transactions where the specified address is the receiver and/or the sender.
* This function must be used by the UI and not by other smart contracts.
* Note that the complexity is O(t), where t is amount of arbitrable transactions.
* @param _address The specified address.
* @return transactionIDs The transaction IDs.
*/
function getTransactionIDsByAddress(address _address) public view returns (uint[] transactionIDs) {
uint count = 0;
for (uint i = 0; i < transactions.length; i++) {
if (transactions[i].sender == _address || transactions[i].receiver == _address)
count++;
}
transactionIDs = new uint[](count);
count = 0;
for (uint j = 0; j < transactions.length; j++) {
if (transactions[j].sender == _address || transactions[j].receiver == _address)
transactionIDs[count++] = j;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment