Created
March 29, 2019 18:16
-
-
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=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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