ArbitrableDeposit.sol
pragma solidity ^0.4.15;
import "./Arbitrable.sol";
/** @title Arbitrable Deposit
* This is a a contract which allow for an owner deposit. Anyone besides the owner can seek arbitration/file a claim as a claimant.
* To develop a contract inheriting from this one, you need to:
* - Redefine RULING_OPTIONS to explain the consequences of the possible rulings.
* - Redefine executeRuling while still calling super.executeRuling to implement the results of the arbitration.
*/
contract ArbitrableDeposit is Arbitrable {
address public owner;
address public claimant;
uint public timeout; // Time in seconds a party can take before being considered unresponding and lose the dispute.
uint public ownerFee; // Total fees paid by the owner.
uint public claimantFee; // Total fees paid by the claimant.
uint public lastInteraction; // Last interaction for the dispute procedure.
uint public disputeID;
uint public amount; // Total amount deposited by owner.
uint public claimAmount; // Claim amount a claimant proposes.
uint public claimRate; // Rate of a claim the claimant must deposit as an integer.
uint internal claimResponseAmount; // Amount which the Owner responds to the claimant's asking claim.
uint public claimDepositAmount; // Total amount a claimant must deposit.
enum Status {NoDispute, WaitingOwner, WaitingClaimant, DisputeCreated, Resolved}
Status public status;
uint8 constant AMOUNT_OF_CHOICES = 2;
uint8 constant OWNER_WINS = 1;
uint8 constant CLAIMANT_WINS = 2;
string constant RULING_OPTIONS = "Owner wins;Claimant wins"; // A plain English of what rulings do. Need to be redefined by the child class.
modifier onlyOwner{require(msg.sender == address(owner), "Can only be called by the owner."); _;}
modifier onlyNotOwner{require(msg.sender != address(owner), "Cannot be called by the owner."); _;}
modifier onlyClaimant{require(msg.sender == address(claimant), "Can only be called by the claimant."); _;}
enum Party {Owner, Claimant}
/** @dev Indicate that a party has to pay a fee or would otherwise be considered as loosing.
* @param _party The party who has to pay.
*/
event HasToPayFee(Party _party);
/** @dev Constructor. Choose the arbitrator
* @param _arbitrator The arbitrator of the contract.
* @param _timeout Time after which a party automatically loose a dispute.
* @param _arbitratorExtraData Extra data for the arbitrator.
* @param _metaEvidence Link to the meta evidence.
*/
constructor(
Arbitrator _arbitrator,
uint _timeout,
bytes _arbitratorExtraData,
uint _claimRate,
string _metaEvidence
) Arbitrable(_arbitrator, _arbitratorExtraData) public payable {
timeout = _timeout;
claimRate = _claimRate;
status = Status.NoDispute;
amount += msg.value;
owner = msg.sender;
address(this).transfer(amount);
emit MetaEvidence(0, _metaEvidence);
}
/** @dev Owner deposit to contract. To be called when the owner makes a deposit.
*/
function deposit() public payable onlyOwner {
amount += msg.value;
address(this).transfer(msg.value);
}
/** @dev File a claim against owner. To be called when someone makes a claim.
* @param _claimAmount The proposed claim amount by the claimant.
*/
function makeClaim(uint _claimAmount) public onlyNotOwner {
require(_claimAmount <= amount, "Cannot claim more than what is deposited.");
claimant = msg.sender;
claimAmount = _claimAmount;
claimDepositAmount = (_claimAmount * claimRate) / 100;
address(this).transfer(claimDepositAmount);
status = Status.WaitingOwner;
}
/** @dev Owner response to claimant. To be called when the owner initates a
* a response.
* @param _responseAmount The counter-offer amount the Owner proposes to a claimant.
*/
function claimResponse(uint _responseAmount) public onlyOwner {
require(_responseAmount <= claimDepositAmount, "The response amount has to be less than the claim deposit amount.");
claimResponseAmount = _responseAmount;
if (_responseAmount == claimDepositAmount) {
claimant.transfer(_responseAmount);
claimAmount = 0;
amount = 0;
status = Status.Resolved;
} else {
payArbitrationFeeByOwner();
}
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the owner. 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.
*/
function payArbitrationFeeByOwner() public payable onlyOwner{
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
ownerFee += msg.value;
// Require that the total pay at least the arbitration cost.
require(ownerFee == arbitrationCost, "Owner fee must equal arbitration cost.");
require(status < Status.DisputeCreated, "A dispute has already been raised."); // Make sure a dispute has not been created yet.
lastInteraction = now;
if (claimantFee < arbitrationCost) { // The claimant still has to pay.
// This can also happens if he has paid, but arbitrationCost has increased.
status = Status.WaitingClaimant;
emit HasToPayFee(Party.Claimant);
} else { // The claimant has also paid the fee. We create the dispute
raiseDispute(arbitrationCost);
}
}
/** @dev Pay the arbitration fee to raise a dispute. To be called by the claimant. UNTRUSTED.
* Note that this function mirror payArbitrationFeeByOwner.
*/
function payArbitrationFeeByClaimant() public payable onlyClaimant {
uint arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData);
claimantFee += msg.value;
// Require that the total pay at least the arbitration cost.
require(claimantFee == arbitrationCost, "Claimant fee must equal arbitration cost.");
require(status < Status.DisputeCreated, "A dispute has already been raised."); // Make sure a dispute has not been created yet.
lastInteraction = now;
if (ownerFee < arbitrationCost) { // The owner still has to pay. This can also happens if he has paid, but arbitrationCost has increased.
status = Status.WaitingOwner;
emit HasToPayFee(Party.Claimant);
} else { // The owner has also paid the fee. We create the dispute
raiseDispute(arbitrationCost);
}
}
/** @dev Create a dispute. UNTRUSTED.
* @param _arbitrationCost Amount to pay the arbitrator.
*/
function raiseDispute(uint _arbitrationCost) internal {
status = Status.DisputeCreated;
disputeID = arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES,arbitratorExtraData);
emit Dispute(arbitrator, disputeID, 0, 0);
}
/** @dev Reimburse owner if claimant fails to pay the fee.
*/
function timeOutByOwner() public onlyOwner {
require(status == Status.WaitingClaimant, "The contract is not waiting for the claimant.");
require(now >= lastInteraction + timeout, "Not enough time has passed.");
executeRuling(disputeID,OWNER_WINS);
}
/** @dev Pay claimant if owner fails to pay the fee.
*/
function timeOutByClaimant() public onlyClaimant {
require(status == Status.WaitingOwner, "The contract is not waiting for the owner.");
require(now >= lastInteraction + timeout, "Not enough time has passed.");
executeRuling(disputeID, CLAIMANT_WINS);
}
/** @dev Execute a ruling of a dispute. Pays parties respective amounts based on ruling.
* This needs to be extended by contract inheriting from it.
* @param _disputeID ID of the dispute in the Arbitrator contract.
* @param _ruling Ruling given by the arbitrator. 1 : Allow owner deposit. 2 : Pay claimant.
*/
function executeRuling(uint _disputeID, uint _ruling) internal {
require(_disputeID == disputeID, "Wrong dispute ID.");
require(_ruling <= AMOUNT_OF_CHOICES, "Invalid ruling.");
if (_ruling == OWNER_WINS) {
owner.transfer(amount + claimAmount);
claimant.transfer(claimResponseAmount);
} else if (_ruling == CLAIMANT_WINS)
claimant.transfer(amount);
amount = 0;
}
}
Copy link
Edit on GitHub