Skip to content

Instantly share code, notes, and snippets.

@kosecki123
Created April 5, 2018 05:53
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 kosecki123/a882782b131b55c249fb307dab61ca50 to your computer and use it in GitHub Desktop.
Save kosecki123/a882782b131b55c249fb307dab61ca50 to your computer and use it in GitHub Desktop.
EAC
pragma solidity ^0.4.21; // solhint-disable-line compiler-fixed
/*
The MIT License (MIT)
Copyright (c) 2018 Murray Software, LLC.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//solhint-disable max-line-length
//solhint-disable no-inline-assembly
/** Notes for auditors of the Ethereum Alarm Clock:
* This file is copied from node_modules/ after `npm install` is ran in this project.
* Alternatively it is also availble on Github: https://github.com/yarrumretep/clone-factory
* It is generated by running the script provided in that repository.
* It was independently verified by lsaether before being used here.
* For the bytecode annotation please see the `bytecode-annotation.txt` document at the root of this project.
* Briefly, this contract will create a "clone" contract that will delegatecall every transaction it is sent to the `target` address.
*/
contract CloneFactory {
event CloneCreated(address indexed target, address clone);
function createClone(address target) internal returns (address result) {
bytes memory clone = hex"3460425760388060106000396000f30036600060ff376000803660ff73beefbeefbeefbeefbeefbeefbeefbeefbeefbeef5af4156032573d80600060ff3e60fff3005b600080fd00";
bytes20 targetBytes = bytes20(target);
for (uint i = 0; i < 20; i++) {
clone[29 + i] = targetBytes[i];
}
assembly {
let len := mload(clone)
let data := add(clone, 0x20)
result := create(0, data, len)
}
}
}
contract RequestFactoryInterface {
event RequestCreated(address request, address indexed owner, int indexed bucket, uint[12] params);
function createRequest(address[3] addressArgs,
uint[12] uintArgs,
bytes callData)
public payable returns (address);
function createValidatedRequest(address[3] addressArgs,
uint[12] uintArgs,
bytes callData)
public payable returns (address);
function validateRequestParams(address[3] addressArgs,
uint[12] uintArgs,
bytes callData,
uint endowment)
public view returns (bool[6]);
function isKnownRequest(address _address)
public view returns (bool);
}
/**
* @title SchedulerInterface
* @dev The base contract that the higher contracts: BaseScheduler, BlockScheduler and TimestampScheduler all inherit from.
*/
contract SchedulerInterface {
using SchedulerLib for SchedulerLib.FutureTransaction;
// The RequestFactory address which produces requests for this scheduler.
address public factoryAddress;
// The TemporalUnit of this scheduler.
RequestScheduleLib.TemporalUnit public temporalUnit;
/*
* Local storage variable used to house the data for transaction
* scheduling.
*/
SchedulerLib.FutureTransaction public futureTransaction;
/*
* When applied to a function, causes the local futureTransaction to
* get reset to it's defaults on each function call.
*/
modifier doReset {
if (temporalUnit == RequestScheduleLib.TemporalUnit.Blocks) {
futureTransaction.resetAsBlock();
} else if (temporalUnit == RequestScheduleLib.TemporalUnit.Timestamp) {
futureTransaction.resetAsTimestamp();
} else {
revert();
}
_;
}
function schedule(address _toAddress,
bytes _callData,
uint[8] _uintArgs)
doReset
public payable returns (address);
}
contract TransactionRequestInterface {
// Primary actions
function execute() public returns (bool);
function cancel() public returns (bool);
function claim() public payable returns (bool);
// Proxy function
function proxy(address recipient, bytes callData)
public payable returns (bool);
// Data accessors
function requestData() public view returns (address[6],
bool[3],
uint[15],
uint8[1]);
function callData() public view returns (bytes);
// Pull mechanisms for payments.
function refundClaimDeposit() public returns (bool);
function sendFee() public returns (bool);
function sendBounty() public returns (bool);
function sendOwnerEther() public returns (bool);
}
/**
* @title IterTools
* @dev Utility library that iterates through a boolean array of length 6.
*/
library IterTools {
/*
* @dev Return true if all of the values in the boolean array are true.
* @param _values A boolean array of length 6.
* @return True if all values are true, False if _any_ are false.
*/
function all(bool[6] _values)
public pure returns (bool)
{
for (uint i = 0; i < _values.length; i++) {
if (!_values[i]) {
return false;
}
}
return true;
}
}
library ClaimLib {
using SafeMath for uint;
struct ClaimData {
address claimedBy; // The address that has claimed the txRequest.
uint claimDeposit; // The deposit amount that was put down by the claimer.
uint requiredDeposit; // The required deposit to claim the txRequest.
uint8 paymentModifier; // An integer constrained between 0-100 that will be applied to the
// request payment as a percentage.
}
/*
* @dev Mark the request as being claimed.
* @param self The ClaimData that is being accessed.
* @param paymentModifier The payment modifier.
*/
function claim(
ClaimData storage self,
uint8 _paymentModifier
)
internal returns (bool)
{
self.claimedBy = msg.sender;
self.claimDeposit = msg.value;
self.paymentModifier = _paymentModifier;
}
/*
* Helper: returns whether this request is claimed.
*/
function isClaimed(ClaimData storage self)
internal view returns (bool)
{
return self.claimedBy != 0x0;
}
/*
* @dev Refund the claim deposit to claimer.
* @param self The Request.ClaimData
* Called in RequestLib's `cancel()` and `refundClaimDeposit()`
*/
function refundDeposit(ClaimData storage self)
internal returns (bool)
{
// Check that the claim deposit is non-zero.
if (self.claimDeposit > 0) {
uint depositAmount;
depositAmount = self.claimDeposit;
self.claimDeposit = 0;
return self.claimedBy.send(depositAmount);
}
return true;
}
}
/**
* @title ExecutionLib
* @dev Contains the logic for executing a scheduled transaction.
*/
library ExecutionLib {
struct ExecutionData {
address toAddress; /// The destination of the transaction.
bytes callData; /// The bytecode that will be sent with the transaction.
uint callValue; /// The wei value that will be sent with the transaction.
uint callGas; /// The amount of gas to be sent with the transaction.
uint gasPrice; /// The gasPrice that should be set for the transaction.
}
/**
* @dev Send the transaction according to the parameters outlined in ExecutionData.
* @param self The ExecutionData object.
*/
function sendTransaction(ExecutionData storage self)
internal returns (bool)
{
/// Should never actually reach this require check, but here in case.
require( self.gasPrice == tx.gasprice );
return self.toAddress.call.value(self.callValue)
.gas(self.callGas)
(self.callData);
}
/**
* Returns the maximum possible gas consumption that a transaction request
* may consume. The EXTRA_GAS value represents the overhead involved in
* request execution.
*/
function CALL_GAS_CEILING(uint EXTRA_GAS)
internal view returns (uint)
{
return block.gaslimit - EXTRA_GAS;
}
/*
* @dev Validation: ensure that the callGas is not above the total possible gas
* for a call.
*/
function validateCallGas(uint callGas, uint EXTRA_GAS)
internal view returns (bool)
{
return callGas < CALL_GAS_CEILING(EXTRA_GAS);
}
/*
* @dev Validation: ensure that the toAddress is not set to the empty address.
*/
function validateToAddress(address toAddress)
internal pure returns (bool)
{
return toAddress != 0x0;
}
}
library MathLib {
uint constant INT_MAX = 57896044618658097711785492504343953926634992332820282019728792003956564819967; // 2**255 - 1
/*
* Subtracts b from a in a manner such that zero is returned when an
* underflow condition is met.
*/
// function flooredSub(uint a, uint b) returns (uint) {
// if (b >= a) {
// return 0;
// } else {
// return a - b;
// }
// }
// /*
// * Adds b to a in a manner that throws an exception when overflow
// * conditions are met.
// */
// function safeAdd(uint a, uint b) returns (uint) {
// if (a + b >= a) {
// return a + b;
// } else {
// throw;
// }
// }
// /*
// * Multiplies a by b in a manner that throws an exception when overflow
// * conditions are met.
// */
// function safeMultiply(uint a, uint b) returns (uint) {
// var result = a * b;
// if (b == 0 || result / b == a) {
// return a * b;
// } else {
// throw;
// }
// }
/*
* Return the larger of a or b. Returns a if a == b.
*/
function max(uint a, uint b)
public pure returns (uint)
{
if (a >= b) {
return a;
} else {
return b;
}
}
/*
* Return the larger of a or b. Returns a if a == b.
*/
function min(uint a, uint b)
public pure returns (uint)
{
if (a <= b) {
return a;
} else {
return b;
}
}
/*
* Returns a represented as a signed integer in a manner that throw an
* exception if casting to signed integer would result in a negative
* number.
*/
function safeCastSigned(uint a)
public pure returns (int)
{
assert(a <= INT_MAX);
return int(a);
}
}
/**
* Library containing the functionality for the bounty and fee payments.
* - Bounty payments are the reward paid to the executing agent of transaction
* requests.
* - Fee payments are the cost of using a Scheduler to make transactions. It is
* a way for developers to monetize their work on the EAC.
*/
library PaymentLib {
using SafeMath for uint;
struct PaymentData {
uint bounty; /// The amount in wei to be paid to the executing agent of the TransactionRequest.
address bountyBenefactor; /// The address that the bounty will be sent to.
uint bountyOwed; /// The amount that is owed to the bountyBenefactor.
uint fee; /// The amount in wei that will be paid to the FEE_RECIPIENT address.
address feeRecipient; /// The address that the fee will be sent to.
uint feeOwed; /// The amount that is owed to the feeRecipient.
}
///---------------
/// GETTERS
///---------------
/**
* @dev Getter function that returns true if a request has a benefactor.
*/
function hasFeeRecipient(PaymentData storage self)
internal view returns (bool)
{
return self.feeRecipient != 0x0;
}
/**
* @dev Computes the amount to send to the feeRecipient.
*/
function getFee(PaymentData storage self)
internal view returns (uint)
{
return self.fee;
}
/**
* @dev Computes the amount to send to the agent that executed the request.
*/
function getBounty(PaymentData storage self)
internal view returns (uint)
{
return self.bounty;
}
/**
* @dev Computes the amount to send to the address that fulfilled the request
* with an additional modifier. This is used when the call was claimed.
*/
function getBountyWithModifier(PaymentData storage self,
uint8 _paymentModifier)
internal view returns (uint)
{
return getBounty(self).mul(_paymentModifier).div(100);
}
///---------------
/// SENDERS
///---------------
/**
* @dev Send the feeOwed amount to the feeRecipient.
* Note: The send is allowed to fail.
*/
function sendFee(PaymentData storage self)
internal returns (bool)
{
uint feeAmount = self.feeOwed;
if (feeAmount > 0) {
// re-entrance protection.
self.feeOwed = 0;
return self.feeRecipient.send(feeAmount);
}
return true;
}
/**
* @dev Send the bountyOwed amount to the bountyBenefactor.
* Note: The send is allowed to fail.
*/
function sendBounty(PaymentData storage self)
internal returns (bool)
{
uint bountyAmount = self.bountyOwed;
if (bountyAmount > 0) {
// re-entrance protection.
self.bountyOwed = 0;
return self.bountyBenefactor.send(bountyAmount);
}
return true;
}
///---------------
/// Endowment
///---------------
/**
* @dev Compute the endowment value for the given TransactionRequest parameters.
* See request_factory.rst in docs folder under Check #1 for more information about
* this calculation.
*/
function computeEndowment(
uint _bounty,
uint _fee,
uint _callGas,
uint _callValue,
uint _gasPrice,
uint _gasOverhead
)
public pure returns (uint)
{
return _bounty
.add(_fee.mul(2))
.add(_callGas.mul(_gasPrice))
.add(_gasOverhead.mul(_gasPrice))
.add(_callValue);
}
/*
* Validation: ensure that the request endowment is sufficient to cover.
* - bounty * maxMultiplier
* - fee * maxMultiplier
* - gasReimbursment
* - callValue
*/
function validateEndowment(uint _endowment,
uint _bounty,
uint _fee,
uint _callGas,
uint _callValue,
uint _gasPrice,
uint _gasOverhead)
public pure returns (bool)
{
return _endowment >= computeEndowment(
_bounty,
_fee,
_callGas,
_callValue,
_gasPrice,
_gasOverhead
);
}
}
library RequestLib {
using ClaimLib for ClaimLib.ClaimData;
using ExecutionLib for ExecutionLib.ExecutionData;
using PaymentLib for PaymentLib.PaymentData;
using RequestMetaLib for RequestMetaLib.RequestMeta;
using RequestScheduleLib for RequestScheduleLib.ExecutionWindow;
using SafeMath for uint;
/*
* This struct exists to circumvent an issue with returning multiple
* values from a library function. I found through experimentation that I
* could not return more than 4 things from a library function, even if I
* put them in arrays. - Piper
*/
struct SerializedRequest {
address[6] addressValues;
bool[3] boolValues;
uint[15] uintValues;
uint8[1] uint8Values;
}
struct Request {
ExecutionLib.ExecutionData txnData;
RequestMetaLib.RequestMeta meta;
PaymentLib.PaymentData paymentData;
ClaimLib.ClaimData claimData;
RequestScheduleLib.ExecutionWindow schedule;
SerializedRequest serializedValues;
}
enum AbortReason {
WasCancelled, //0
AlreadyCalled, //1
BeforeCallWindow, //2
AfterCallWindow, //3
ReservedForClaimer, //4
InsufficientGas, //5
MismatchGasPrice //6
}
event Aborted(uint8 reason);
event Cancelled(uint rewardPayment, uint measuredGasConsumption);
event Claimed();
event Executed(uint bounty, uint fee, uint measuredGasConsumption);
/**
* @dev Validate the initialization parameters of a transaction request.
*/
function validate(
address[4] _addressArgs,
uint[12] _uintArgs,
bytes _callData,
uint _endowment
)
public view returns (bool[6] isValid)
{
Request memory request;
// callData is special.
request.txnData.callData = _callData;
// Address values
request.claimData.claimedBy = 0x0;
request.meta.createdBy = _addressArgs[0];
request.meta.owner = _addressArgs[1];
request.paymentData.feeRecipient = _addressArgs[2];
request.paymentData.bountyBenefactor = 0x0;
request.txnData.toAddress = _addressArgs[3];
// Boolean values
request.meta.isCancelled = false;
request.meta.wasCalled = false;
request.meta.wasSuccessful = false;
// UInt values
request.claimData.claimDeposit = 0;
request.paymentData.fee = _uintArgs[0];
request.paymentData.bounty = _uintArgs[1];
request.paymentData.feeOwed = 0;
request.paymentData.bountyOwed = 0;
request.schedule.claimWindowSize = _uintArgs[2];
request.schedule.freezePeriod = _uintArgs[3];
request.schedule.reservedWindowSize = _uintArgs[4];
// This must be capped at 1 or it throws an exception.
request.schedule.temporalUnit = RequestScheduleLib.TemporalUnit(MathLib.min(_uintArgs[5], 2));
request.schedule.windowSize = _uintArgs[6];
request.schedule.windowStart = _uintArgs[7];
request.txnData.callGas = _uintArgs[8];
request.txnData.callValue = _uintArgs[9];
request.txnData.gasPrice = _uintArgs[10];
request.claimData.requiredDeposit = _uintArgs[11];
// Uint8 values
request.claimData.paymentModifier = 0;
// The order of these errors matters as it determines which
// ValidationError event codes are logged when validation fails.
isValid[0] = PaymentLib.validateEndowment(
_endowment,
request.paymentData.bounty,
request.paymentData.fee,
request.txnData.callGas,
request.txnData.callValue,
request.txnData.gasPrice,
_EXECUTION_GAS_OVERHEAD
);
isValid[1] = RequestScheduleLib.validateReservedWindowSize(
request.schedule.reservedWindowSize,
request.schedule.windowSize
);
isValid[2] = RequestScheduleLib.validateTemporalUnit(_uintArgs[5]);
isValid[3] = RequestScheduleLib.validateWindowStart(
request.schedule.temporalUnit,
request.schedule.freezePeriod,
request.schedule.windowStart
);
isValid[4] = ExecutionLib.validateCallGas(
request.txnData.callGas,
_EXECUTION_GAS_OVERHEAD
);
isValid[5] = ExecutionLib.validateToAddress(request.txnData.toAddress);
return isValid;
}
/**
* @dev Initialize a new Request.
*/
function initialize(
Request storage self,
address[4] _addressArgs,
uint[12] _uintArgs,
bytes _callData
)
public returns (bool initialized)
{
address[6] memory addressValues = [
0x0, // self.claimData.claimedBy
_addressArgs[0], // self.meta.createdBy
_addressArgs[1], // self.meta.owner
_addressArgs[2], // self.paymentData.feeRecipient
0x0, // self.paymentData.bountyBenefactor
_addressArgs[3] // self.txnData.toAddress
];
bool[3] memory boolValues = [false, false, false];
uint[15] memory uintValues = [
0, // self.claimData.claimDeposit
_uintArgs[0], // self.paymentData.fee
0, // self.paymentData.feeOwed
_uintArgs[1], // self.paymentData.bounty
0, // self.paymentData.bountyOwed
_uintArgs[2], // self.schedule.claimWindowSize
_uintArgs[3], // self.schedule.freezePeriod
_uintArgs[4], // self.schedule.reservedWindowSize
_uintArgs[5], // self.schedule.temporalUnit
_uintArgs[6], // self.schedule.windowSize
_uintArgs[7], // self.schedule.windowStart
_uintArgs[8], // self.txnData.callGas
_uintArgs[9], // self.txnData.callValue
_uintArgs[10], // self.txnData.gasPrice
_uintArgs[11] // self.claimData.requiredDeposit
];
uint8[1] memory uint8Values = [
0
];
require( deserialize(self, addressValues, boolValues, uintValues, uint8Values, _callData) );
return true;
}
/*
* Returns the entire data structure of the Request in a *serialized*
* format. This will be missing the `callData` which must be requested
* separately
*
* Parameter order is alphabetical by type, then namespace, then name
*
* NOTE: This exists because of an issue I ran into related to returning
* multiple values from a library function. I found through
* experimentation that I was unable to return more than 4 things, even if
* I used the trick of returning arrays of items.
*/
function serialize(Request storage self)
internal returns (bool serialized)
{
// Address values
self.serializedValues.addressValues[0] = self.claimData.claimedBy;
self.serializedValues.addressValues[1] = self.meta.createdBy;
self.serializedValues.addressValues[2] = self.meta.owner;
self.serializedValues.addressValues[3] = self.paymentData.feeRecipient;
self.serializedValues.addressValues[4] = self.paymentData.bountyBenefactor;
self.serializedValues.addressValues[5] = self.txnData.toAddress;
// Boolean values
self.serializedValues.boolValues[0] = self.meta.isCancelled;
self.serializedValues.boolValues[1] = self.meta.wasCalled;
self.serializedValues.boolValues[2] = self.meta.wasSuccessful;
// UInt256 values
self.serializedValues.uintValues[0] = self.claimData.claimDeposit;
self.serializedValues.uintValues[1] = self.paymentData.fee;
self.serializedValues.uintValues[2] = self.paymentData.feeOwed;
self.serializedValues.uintValues[3] = self.paymentData.bounty;
self.serializedValues.uintValues[4] = self.paymentData.bountyOwed;
self.serializedValues.uintValues[5] = self.schedule.claimWindowSize;
self.serializedValues.uintValues[6] = self.schedule.freezePeriod;
self.serializedValues.uintValues[7] = self.schedule.reservedWindowSize;
self.serializedValues.uintValues[8] = uint(self.schedule.temporalUnit);
self.serializedValues.uintValues[9] = self.schedule.windowSize;
self.serializedValues.uintValues[10] = self.schedule.windowStart;
self.serializedValues.uintValues[11] = self.txnData.callGas;
self.serializedValues.uintValues[12] = self.txnData.callValue;
self.serializedValues.uintValues[13] = self.txnData.gasPrice;
self.serializedValues.uintValues[14] = self.claimData.requiredDeposit;
// Uint8 values
self.serializedValues.uint8Values[0] = self.claimData.paymentModifier;
return true;
}
/**
* @dev Populates a Request object from the full output of `serialize`.
*
* Parameter order is alphabetical by type, then namespace, then name.
*/
function deserialize(
Request storage self,
address[6] _addressValues,
bool[3] _boolValues,
uint[15] _uintValues,
uint8[1] _uint8Values,
bytes _callData
)
internal returns (bool deserialized)
{
// callData is special.
self.txnData.callData = _callData;
// Address values
self.claimData.claimedBy = _addressValues[0];
self.meta.createdBy = _addressValues[1];
self.meta.owner = _addressValues[2];
self.paymentData.feeRecipient = _addressValues[3];
self.paymentData.bountyBenefactor = _addressValues[4];
self.txnData.toAddress = _addressValues[5];
// Boolean values
self.meta.isCancelled = _boolValues[0];
self.meta.wasCalled = _boolValues[1];
self.meta.wasSuccessful = _boolValues[2];
// UInt values
self.claimData.claimDeposit = _uintValues[0];
self.paymentData.fee = _uintValues[1];
self.paymentData.feeOwed = _uintValues[2];
self.paymentData.bounty = _uintValues[3];
self.paymentData.bountyOwed = _uintValues[4];
self.schedule.claimWindowSize = _uintValues[5];
self.schedule.freezePeriod = _uintValues[6];
self.schedule.reservedWindowSize = _uintValues[7];
self.schedule.temporalUnit = RequestScheduleLib.TemporalUnit(_uintValues[8]);
self.schedule.windowSize = _uintValues[9];
self.schedule.windowStart = _uintValues[10];
self.txnData.callGas = _uintValues[11];
self.txnData.callValue = _uintValues[12];
self.txnData.gasPrice = _uintValues[13];
self.claimData.requiredDeposit = _uintValues[14];
// Uint8 values
self.claimData.paymentModifier = _uint8Values[0];
deserialized = true;
}
function execute(Request storage self)
internal returns (bool)
{
/*
* Execute the TransactionRequest
*
* +---------------------+
* | Phase 1: Validation |
* +---------------------+
*
* Must pass all of the following checks:
*
* 1. Not already called.
* 2. Not cancelled.
* 3. Not before the execution window.
* 4. Not after the execution window.
* 5. if (claimedBy == 0x0 or msg.sender == claimedBy):
* - windowStart <= block.number
* - block.number <= windowStart + windowSize
* else if (msg.sender != claimedBy):
* - windowStart + reservedWindowSize <= block.number
* - block.number <= windowStart + windowSize
* else:
* - throw (should be impossible)
*
* 6. gasleft() == callGas
*
* +--------------------+
* | Phase 2: Execution |
* +--------------------+
*
* 1. Mark as called (must be before actual execution to prevent
* re-entrance)
* 2. Send Transaction and record success or failure.
*
* +---------------------+
* | Phase 3: Accounting |
* +---------------------+
*
* 1. Calculate and send fee amount.
* 2. Calculate and send bounty amount.
* 3. Send remaining ether back to owner.
*
*/
// Record the gas at the beginning of the transaction so we can
// calculate how much has been used later.
uint startGas = gasleft();
// +----------------------+
// | Begin: Authorization |
// +----------------------+
if (gasleft() < requiredExecutionGas(self).sub(_PRE_EXECUTION_GAS)) {
emit Aborted(uint8(AbortReason.InsufficientGas));
return false;
} else if (self.meta.wasCalled) {
emit Aborted(uint8(AbortReason.AlreadyCalled));
return false;
} else if (self.meta.isCancelled) {
emit Aborted(uint8(AbortReason.WasCancelled));
return false;
} else if (self.schedule.isBeforeWindow()) {
emit Aborted(uint8(AbortReason.BeforeCallWindow));
return false;
} else if (self.schedule.isAfterWindow()) {
emit Aborted(uint8(AbortReason.AfterCallWindow));
return false;
} else if (self.claimData.isClaimed() &&
msg.sender != self.claimData.claimedBy &&
self.schedule.inReservedWindow()) {
emit Aborted(uint8(AbortReason.ReservedForClaimer));
return false;
} else if (self.txnData.gasPrice != tx.gasprice) {
emit Aborted(uint8(AbortReason.MismatchGasPrice));
return false;
}
// +--------------------+
// | End: Authorization |
// +--------------------+
// +------------------+
// | Begin: Execution |
// +------------------+
// Mark as being called before sending transaction to prevent re-entrance.
self.meta.wasCalled = true;
// Send the transaction...
// The transaction is allowed to fail and the executing agent will still get the bounty.
// `.sendTransaction()` will return false on a failed exeuction.
self.meta.wasSuccessful = self.txnData.sendTransaction();
// +----------------+
// | End: Execution |
// +----------------+
// +-------------------+
// | Begin: Accounting |
// +-------------------+
// Compute the fee amount
if (self.paymentData.hasFeeRecipient()) {
self.paymentData.feeOwed = self.paymentData.getFee()
.add(self.paymentData.feeOwed);
}
// Record this locally so that we can log it later.
// `.sendFee()` below will change `self.paymentData.feeOwed` to 0 to prevent re-entrance.
uint totalFeePayment = self.paymentData.feeOwed;
// Send the fee. This transaction may also fail but can be called again after
// execution.
self.paymentData.sendFee();
// Compute the bounty amount.
self.paymentData.bountyBenefactor = msg.sender;
if (self.claimData.isClaimed()) {
// If the transaction request was claimed, we add the deposit to the bounty whether
// or not the same agent who claimed is executing.
self.paymentData.bountyOwed = self.claimData.claimDeposit
.add(self.paymentData.bountyOwed);
// To prevent re-entrance we zero out the claim deposit since it is now accounted for
// in the bounty value.
self.claimData.claimDeposit = 0;
// Depending on when the transaction request was claimed, we apply the modifier to the
// bounty payment and add it to the bounty already owed.
self.paymentData.bountyOwed = self.paymentData.getBountyWithModifier(self.claimData.paymentModifier)
.add(self.paymentData.bountyOwed);
} else {
// Not claimed. Just add the full bounty.
self.paymentData.bountyOwed = self.paymentData.getBounty()
.add(self.paymentData.bountyOwed);
}
// Take down the amount of gas used so far in execution to compensate the executing agent.
uint measuredGasConsumption = startGas
.sub(gasleft())
.add(_EXECUTE_EXTRA_GAS);
// // +----------------------------------------------------------------------+
// // | NOTE: All code after this must be accounted for by EXECUTE_EXTRA_GAS |
// // +----------------------------------------------------------------------+
// Add the gas reimbursment amount to the bounty.
self.paymentData.bountyOwed = measuredGasConsumption
.mul(tx.gasprice)
.add(self.paymentData.bountyOwed);
// Log the bounty and fee. Otherwise it is non-trivial to figure
// out how much was payed.
emit Executed(self.paymentData.bountyOwed, totalFeePayment, measuredGasConsumption);
// Attempt to send the bounty. as with `.sendFee()` it may fail and need to be caled after execution.
self.paymentData.sendBounty();
// If any ether is left, send it back to the owner of the transaction request.
_sendOwnerEther(self);
// +-----------------+
// | End: Accounting |
// +-----------------+
// Successful
return true;
}
// This is the amount of gas that it takes to enter from the
// `TransactionRequest.execute()` contract into the `RequestLib.execute()`
// method at the point where the gas check happens.
uint private constant _PRE_EXECUTION_GAS = 25000; // TODO is this number still accurate?
function PRE_EXECUTION_GAS()
public pure returns (uint)
{
return _PRE_EXECUTION_GAS;
}
function requiredExecutionGas(Request storage self)
public view returns (uint requiredGas)
{
requiredGas = self.txnData.callGas.add(_EXECUTION_GAS_OVERHEAD);
}
/*
* The amount of gas needed to complete the execute method after
* the transaction has been sent.
*/
uint private constant _EXECUTION_GAS_OVERHEAD = 180000; // TODO check accuracy of this number
function EXECUTION_GAS_OVERHEAD()
public pure returns (uint)
{
return _EXECUTION_GAS_OVERHEAD;
}
/*
* The amount of gas used by the portion of the `execute` function
* that cannot be accounted for via gas tracking.
*/
uint private constant _EXECUTE_EXTRA_GAS = 90000; // again, check for accuracy... Doubled this from Piper's original - Logan
function EXECUTE_EXTRA_GAS()
public pure returns (uint)
{
return _EXECUTE_EXTRA_GAS;
}
/*
* @dev Performs the checks to see if a request can be cancelled.
* Must satisfy the following conditions.
*
* 1. Not Cancelled
* 2. either:
* * not wasCalled && afterExecutionWindow
* * not claimed && beforeFreezeWindow && msg.sender == owner
*/
function isCancellable(Request storage self)
internal view returns (bool)
{
if (self.meta.isCancelled) {
// already cancelled!
return false;
} else if (!self.meta.wasCalled && self.schedule.isAfterWindow()) {
// not called but after the window
return true;
} else if (!self.claimData.isClaimed() && self.schedule.isBeforeFreeze() && msg.sender == self.meta.owner) {
// not claimed and before freezePeriod and owner is cancelling
return true;
} else {
// otherwise cannot cancel
return false;
}
}
/*
* Constant value to account for the gas usage that cannot be accounted
* for using gas-tracking within the `cancel` function.
*/
uint private constant _CANCEL_EXTRA_GAS = 85000; // Check accuracy
function CANCEL_EXTRA_GAS()
public pure returns (uint)
{
return _CANCEL_EXTRA_GAS;
}
/*
* Cancel the transaction request, attempting to send all appropriate
* refunds. To incentivise cancellation by other parties, a small reward
* payment is issued to the party that cancels the request if they are not
* the owner.
*/
function cancel(Request storage self)
public returns (bool)
{
uint startGas = gasleft();
uint rewardPayment;
uint measuredGasConsumption;
// Checks if this transactionRequest can be cancelled.
require( isCancellable(self) );
// Set here to prevent re-entrance attacks.
self.meta.isCancelled = true;
// Refund the claim deposit (if there is one)
require( self.claimData.refundDeposit() );
// Send a reward to the cancelling agent if they are not the owner.
// This is to incentivize the cancelling of expired transaction requests.
// This also guarantees that it is being cancelled after the call window
// since the `isCancellable()` function checks this.
if (msg.sender != self.meta.owner) {
// Create the rewardBenefactor
address rewardBenefactor = msg.sender;
// Create the rewardOwed variable, it is one-hundredth
// of the bounty.
uint rewardOwed = self.paymentData.bountyOwed
.add(self.paymentData.bounty.div(100));
// Calculate the amount of gas cancelling agent used in this transaction.
measuredGasConsumption = startGas
.sub(gasleft())
.add(_CANCEL_EXTRA_GAS);
// Add their gas fees to the reward.
rewardOwed = measuredGasConsumption
.mul(tx.gasprice)
.add(rewardOwed);
// Take note of the rewardPayment to log it.
rewardPayment = rewardOwed;
// Transfers the rewardPayment.
if (rewardOwed > 0) {
self.paymentData.bountyOwed = 0;
rewardBenefactor.transfer(rewardOwed);
}
}
// Log it!
emit Cancelled(rewardPayment, measuredGasConsumption);
// Send the remaining ether to the owner.
return sendOwnerEther(self);
}
/*
* @dev Performs some checks to verify that a transaction request is claimable.
* @param self The Request object.
*/
function isClaimable(Request storage self)
internal view returns (bool)
{
// Require not claimed and not cancelled.
require( !self.claimData.isClaimed() );
require( !self.meta.isCancelled );
// Require that it's in the claim window and the value sent is over the required deposit.
require( self.schedule.inClaimWindow() );
require( msg.value >= self.claimData.requiredDeposit );
return true;
}
/*
* @dev Claims the request.
* @param self The Request object.
* Payable because it requires the sender to send enough ether to cover the claimDeposit.
*/
function claim(Request storage self)
internal returns (bool claimed)
{
require( isClaimable(self) );
self.claimData.claim(self.schedule.computePaymentModifier());
emit Claimed();
claimed = true;
}
/*
* @dev Refund claimer deposit.
*/
function refundClaimDeposit(Request storage self)
public returns (bool)
{
require( self.meta.isCancelled || self.schedule.isAfterWindow() );
return self.claimData.refundDeposit();
}
/*
* Send fee. Wrapper over the real function that perform an extra
* check to see if it's after the execution window (and thus the first transaction failed)
*/
function sendFee(Request storage self)
public returns (bool)
{
if (self.schedule.isAfterWindow()) {
return self.paymentData.sendFee();
}
return false;
}
/*
* Send bounty. Wrapper over the real function that performs an extra
* check to see if it's after execution window (and thus the first transaction failed)
*/
function sendBounty(Request storage self)
public returns (bool)
{
/// check wasCalled
if (self.schedule.isAfterWindow()) {
return self.paymentData.sendBounty();
}
return false;
}
/**
* Send owner ether. Wrapper over the real function that performs an extra
* check to see if it's after execution window (and thus the first transaction failed)
*/
function sendOwnerEther(Request storage self)
public returns (bool)
{
if( self.meta.isCancelled || self.schedule.isAfterWindow() ) {
return _sendOwnerEther(self);
}
return false;
}
function _sendOwnerEther(Request storage self)
internal returns (bool)
{
// Note! This does not do any checks since it is used in the execute function.
// The public version of the function should be used for checks and in the cancel function.
uint ownerRefund = address(this).balance
.sub(self.claimData.claimDeposit)
.sub(self.paymentData.bountyOwed)
.sub(self.paymentData.feeOwed);
return self.meta.owner.send(ownerRefund);
}
}
/**
* @title RequestMetaLib
* @dev Small library holding all the metadata about a TransactionRequest.
*/
library RequestMetaLib {
struct RequestMeta {
address owner; /// The address that created this request.
address createdBy; /// The address of the RequestFactory which created this request.
bool isCancelled; /// Was the TransactionRequest cancelled?
bool wasCalled; /// Was the TransactionRequest called?
bool wasSuccessful; /// Was the return value from the TransactionRequest execution successful?
}
}
/**
* @title RequestScheduleLib
* @dev Library containing the logic for request scheduling.
*/
library RequestScheduleLib {
using SafeMath for uint;
/**
* The manner in which this schedule specifies time.
*
* Null: present to require this value be explicitely specified
* Blocks: execution schedule determined by block.number
* Timestamp: execution schedule determined by block.timestamp
*/
enum TemporalUnit {
Null, // 0
Blocks, // 1
Timestamp // 2
}
struct ExecutionWindow {
TemporalUnit temporalUnit; /// The type of unit used to measure time.
uint windowStart; /// The starting point in temporal units from which the transaction can be executed.
uint windowSize; /// The length in temporal units of the execution time period.
uint freezePeriod; /// The length in temporal units before the windowStart where no activity is allowed.
uint reservedWindowSize; /// The length in temporal units at the beginning of the executionWindow in which only the claim address can execute.
uint claimWindowSize; /// The length in temporal units before the freezeperiod in which an address can claim the execution.
}
/**
* @dev Get the `now` represented in the temporal units assigned to this request.
* @param self The ExecutionWindow object.
* @return The unsigned integer representation of `now` in appropiate temporal units.
*/
function getNow(ExecutionWindow storage self)
public view returns (uint)
{
return _getNow(self.temporalUnit);
}
/**
* @dev Internal function to return the `now` based on the appropiate temporal units.
* @param _temporalUnit The assigned TemporalUnit to this transaction.
*/
function _getNow(TemporalUnit _temporalUnit)
internal view returns (uint)
{
if (_temporalUnit == TemporalUnit.Timestamp) {
return block.timestamp;
}
if (_temporalUnit == TemporalUnit.Blocks) {
return block.number;
}
/// Only reaches here if the unit is unset, unspecified or unsupported.
revert();
}
/**
* @dev The modifier that will be applied to the bounty value depending
* on when a call was claimed.
*/
function computePaymentModifier(ExecutionWindow storage self)
internal view returns (uint8)
{
uint paymentModifier = (getNow(self).sub(firstClaimBlock(self)))
.mul(100)
.div(self.claimWindowSize);
assert(paymentModifier <= 100);
return uint8(paymentModifier);
}
/*
* Helper: computes the end of the execution window.
*/
function windowEnd(ExecutionWindow storage self)
internal view returns (uint)
{
return self.windowStart.add(self.windowSize);
}
/*
* Helper: computes the end of the reserved portion of the execution
* window.
*/
function reservedWindowEnd(ExecutionWindow storage self)
internal view returns (uint)
{
return self.windowStart.add(self.reservedWindowSize);
}
/*
* Helper: computes the time when the request will be frozen until execution.
*/
function freezeStart(ExecutionWindow storage self)
internal view returns (uint)
{
return self.windowStart.sub(self.freezePeriod);
}
/*
* Helper: computes the time when the request will be frozen until execution.
*/
function firstClaimBlock(ExecutionWindow storage self)
internal view returns (uint)
{
return freezeStart(self).sub(self.claimWindowSize);
}
/*
* Helper: Returns boolean if we are before the execution window.
*/
function isBeforeWindow(ExecutionWindow storage self)
internal view returns (bool)
{
return getNow(self) < self.windowStart;
}
/*
* Helper: Returns boolean if we are after the execution window.
*/
function isAfterWindow(ExecutionWindow storage self)
internal view returns (bool)
{
return getNow(self) > windowEnd(self);
}
/*
* Helper: Returns boolean if we are inside the execution window.
*/
function inWindow(ExecutionWindow storage self)
internal view returns (bool)
{
return self.windowStart <= getNow(self) && getNow(self) < windowEnd(self);
}
/*
* Helper: Returns boolean if we are inside the reserved portion of the
* execution window.
*/
function inReservedWindow(ExecutionWindow storage self)
internal view returns (bool)
{
return self.windowStart <= getNow(self) && getNow(self) < reservedWindowEnd(self);
}
/*
* @dev Helper: Returns boolean if we are inside the claim window.
*/
function inClaimWindow(ExecutionWindow storage self)
internal view returns (bool)
{
/// Checks that the firstClaimBlock is in the past or now.
/// Checks that now is before the start of the freezePeriod.
if (firstClaimBlock(self) <= getNow(self)
&& getNow(self) < freezeStart(self)) {
return true;
}
return false;
}
/*
* Helper: Returns boolean if we are before the freeze period.
*/
function isBeforeFreeze(ExecutionWindow storage self)
internal view returns (bool)
{
return getNow(self) < freezeStart(self);
}
/*
* Helper: Returns boolean if we are before the freeze period.
*/
function isBeforeClaimWindow(ExecutionWindow storage self)
internal view returns (bool)
{
return getNow(self) < firstClaimBlock(self);
}
///---------------
/// VALIDATION
///---------------
/**
* @dev Validation: Ensure that the reservedWindowSize is less than or equal to the windowSize.
* @param _reservedWindowSize The size of the reserved window.
* @param _windowSize The size of the execution window.
* @return True if the reservedWindowSize is within the windowSize.
*/
function validateReservedWindowSize(uint _reservedWindowSize,
uint _windowSize)
public pure returns (bool)
{
return _reservedWindowSize <= _windowSize;
}
/**
* @dev Validation: Ensure that the startWindow is at least freezePeriod amount of time in the future.
* @param _temporalUnit The temporalUnit of this request.
* @param _freezePeriod The freezePeriod in temporal units.
* @param _windowStart The time in the future which represents the start of the execution window.
* @return True if the windowStart is at least freezePeriod amount of time in the future.
*/
function validateWindowStart(TemporalUnit _temporalUnit,
uint _freezePeriod,
uint _windowStart)
public view returns (bool)
{
return _getNow(_temporalUnit).add(_freezePeriod) <= _windowStart;
}
/*
* Validation: ensure that the temporal unit passed in is constrained to 0 or 1
*/
function validateTemporalUnit(uint _temporalUnitAsUInt)
public pure returns (bool)
{
return (_temporalUnitAsUInt != uint(TemporalUnit.Null)
&& (_temporalUnitAsUInt == uint(TemporalUnit.Blocks)
|| _temporalUnitAsUInt == uint(TemporalUnit.Timestamp))
);
}
}
library SchedulerLib {
using SafeMath for uint;
struct FutureTransaction {
address toAddress; // Destination of the transaction.
bytes callData; // Bytecode to be included with the transaction.
uint callGas; // Amount of gas to be used with the transaction.
uint callValue; // Amount of ether to send with the transaction.
uint windowSize; // The size of the execution window.
uint windowStart; // Block or timestamp for when the window starts.
uint gasPrice; // The gasPrice to be sent with the transaction.
uint fee; // Fee value attached to the transaction.
uint bounty; // Bounty value attached to the transaction.
uint requiredDeposit; // The deposit required to claim the transaction.
uint reservedWindowSize; // The size of the window in which claimer has exclusive rights to execute.
uint freezePeriod; // The size of the window in which nothing happens... Is before execution
uint claimWindowSize; // The size of the window in which someone can claim the txRequest
RequestScheduleLib.TemporalUnit temporalUnit;
}
/*
* @dev Set common default values.
*/
function resetCommon(FutureTransaction storage self)
public returns (bool complete)
{
uint defaultBounty = tx.gasprice.mul(1000000);
if (self.bounty != defaultBounty) {
self.bounty = defaultBounty;
}
uint defaultFee = self.bounty.div(100);
if (self.fee != defaultFee ) {
self.fee = defaultFee;
}
uint defaultDeposit = self.bounty.mul(2);
if (self.requiredDeposit != defaultDeposit) {
self.requiredDeposit = defaultDeposit;
}
if (self.toAddress != msg.sender) {
self.toAddress = msg.sender;
}
if (self.callGas != 90000) {
self.callGas = 90000;
}
if (self.callData.length != 0) {
self.callData = "";
}
if (self.gasPrice != 100) {
self.gasPrice = 100;
}
complete = true;
}
/*
* @dev Set default values for block based scheduling.
*/
function resetAsBlock(FutureTransaction storage self)
public returns (bool complete)
{
require(resetCommon(self));
if (self.windowSize != 255) {
self.windowSize = 255;
}
if (self.windowStart != block.number + 10) {
self.windowStart = block.number + 10;
}
if (self.reservedWindowSize != 16) {
self.reservedWindowSize = 16;
}
if (self.freezePeriod != 10) {
self.freezePeriod = 10;
}
if (self.claimWindowSize != 255) {
self.claimWindowSize = 255;
}
complete = true;
}
/*
* Set default values for timestamp based scheduling.
*/
function resetAsTimestamp(FutureTransaction storage self)
public returns (bool complete)
{
require(resetCommon(self));
if (self.windowSize != 60 minutes) {
self.windowSize = 60 minutes;
}
if (self.windowStart != now + 5 minutes) {
self.windowStart = now + 5 minutes;
}
if (self.reservedWindowSize != 5 minutes) {
self.reservedWindowSize = 5 minutes;
}
if (self.freezePeriod != 3 minutes) {
self.freezePeriod = 3 minutes;
}
if (self.claimWindowSize != 60 minutes) {
self.claimWindowSize = 60 minutes;
}
complete = true;
}
/**
* @dev The lower level interface for creating a transaction request.
* @param self The FutureTransaction object created in schedule transaction calls.
* @param _factoryAddress The address of the RequestFactory which creates TransactionRequests.
* @return The address of a new TransactionRequest.
*/
function schedule(
FutureTransaction storage self,
address _factoryAddress,
address _feeRecipient
)
internal returns (address newRequestAddress)
{
RequestFactoryInterface factory = RequestFactoryInterface(_factoryAddress);
uint endowment = MathLib.min(
PaymentLib.computeEndowment(
self.bounty,
self.fee,
self.callGas,
self.callValue,
self.gasPrice,
RequestLib.EXECUTION_GAS_OVERHEAD() //180000, line 459 RequestLib
), this.balance);
newRequestAddress = factory.createValidatedRequest.value(endowment)(
[
msg.sender, // meta.owner
_feeRecipient, // paymentData.feeRecipient
self.toAddress // txnData.toAddress
],
[
self.fee, // paymentData.fee
self.bounty, // paymentData.bounty
self.claimWindowSize, // scheduler.claimWindowSize
self.freezePeriod, // scheduler.freezePeriod
self.reservedWindowSize, // scheduler.reservedWindowSize
uint(self.temporalUnit), // scheduler.temporalUnit (1: block, 2: timestamp)
self.windowSize, // scheduler.windowSize
self.windowStart, // scheduler.windowStart
self.callGas, // txnData.callGas
self.callValue, // txnData.callValue
self.gasPrice, // txnData.gasPrice
self.requiredDeposit // claimData.requiredDeposit
],
self.callData
);
// This check is redundant. see line 55 in BaseScheduler.sol
// I'm keeping it for now just to cover my bases, even if I'm covering them twice.
require(newRequestAddress != 0x0);
return newRequestAddress;
}
}
/**
* @title RequestFactory
* @dev Contract which will produce new TransactionRequests.
*/
contract RequestFactory is RequestFactoryInterface, CloneFactory {
using IterTools for bool[6];
TransactionRequestCore public transactionRequestCore;
uint constant public BLOCKS_BUCKET_SIZE = 240; //~1h
uint constant public TIMESTAMP_BUCKET_SIZE = 3600; //1h
function RequestFactory(
address _transactionRequestCore
) public {
require( _transactionRequestCore != 0x0 );
transactionRequestCore = TransactionRequestCore(_transactionRequestCore);
}
/**
* @dev The lowest level interface for creating a transaction request.
*
* @param _addressArgs [0] - meta.owner
* @param _addressArgs [1] - paymentData.feeRecipient
* @param _addressArgs [2] - txnData.toAddress
* @param _uintArgs [0] - paymentData.fee
* @param _uintArgs [1] - paymentData.bounty
* @param _uintArgs [2] - schedule.claimWindowSize
* @param _uintArgs [3] - schedule.freezePeriod
* @param _uintArgs [4] - schedule.reservedWindowSize
* @param _uintArgs [5] - schedule.temporalUnit
* @param _uintArgs [6] - schedule.windowSize
* @param _uintArgs [7] - schedule.windowStart
* @param _uintArgs [8] - txnData.callGas
* @param _uintArgs [9] - txnData.callValue
* @param _uintArgs [10] - txnData.gasPrice
* @param _uintArgs [11] - claimData.requiredDeposit
* @param _callData - The call data
*/
function createRequest(
address[3] _addressArgs,
uint[12] _uintArgs,
bytes _callData
)
public payable returns (address)
{
// Create a new transaction request clone from transactionRequestCore.
address transactionRequest = createClone(transactionRequestCore);
// Call initialize on the transaction request clone.
TransactionRequestCore(transactionRequest).initialize.value(msg.value)(
[
msg.sender, // Created by
_addressArgs[0], // meta.owner
_addressArgs[1], // paymentData.feeRecipient
_addressArgs[2] // txnData.toAddress
],
_uintArgs, //uint[12]
_callData
);
// Track the address locally
requests[transactionRequest] = true;
// Log the creation.
emit RequestCreated(
transactionRequest,
_addressArgs[0],
getBucket(_uintArgs[7], RequestScheduleLib.TemporalUnit(_uintArgs[5])),
_uintArgs
);
return transactionRequest;
}
/**
* The same as createRequest except that it requires validation prior to
* creation.
*
* Parameters are the same as `createRequest`
*/
function createValidatedRequest(
address[3] _addressArgs,
uint[12] _uintArgs,
bytes _callData
)
public payable returns (address)
{
bool[6] memory isValid = validateRequestParams(
_addressArgs,
_uintArgs,
_callData,
msg.value
);
if (!isValid.all()) {
if (!isValid[0]) {
emit ValidationError(uint8(Errors.InsufficientEndowment));
}
if (!isValid[1]) {
emit ValidationError(uint8(Errors.ReservedWindowBiggerThanExecutionWindow));
}
if (!isValid[2]) {
emit ValidationError(uint8(Errors.InvalidTemporalUnit));
}
if (!isValid[3]) {
emit ValidationError(uint8(Errors.ExecutionWindowTooSoon));
}
if (!isValid[4]) {
emit ValidationError(uint8(Errors.CallGasTooHigh));
}
if (!isValid[5]) {
emit ValidationError(uint8(Errors.EmptyToAddress));
}
// Try to return the ether sent with the message. If this failed
// then revert() to force it to be returned
if (!msg.sender.send(msg.value)) {
revert();
}
return 0x0;
}
return createRequest(_addressArgs, _uintArgs, _callData);
}
/// ----------------------------
/// Internal
/// ----------------------------
/*
* @dev The enum for launching `ValidationError` events and mapping them to an error.
*/
enum Errors {
InsufficientEndowment,
ReservedWindowBiggerThanExecutionWindow,
InvalidTemporalUnit,
ExecutionWindowTooSoon,
CallGasTooHigh,
EmptyToAddress
}
event ValidationError(uint8 error);
/*
* @dev Validate the constructor arguments for either `createRequest` or `createValidatedRequest`.
*/
function validateRequestParams(
address[3] _addressArgs,
uint[12] _uintArgs,
bytes _callData,
uint _endowment
)
public view returns (bool[6])
{
return RequestLib.validate(
[
msg.sender, // meta.createdBy
_addressArgs[0], // meta.owner
_addressArgs[1], // paymentData.feeRecipient
_addressArgs[2] // txnData.toAddress
],
_uintArgs,
_callData,
_endowment
);
}
/// Mapping to hold known requests.
mapping (address => bool) requests;
function isKnownRequest(address _address)
public view returns (bool isKnown)
{
return requests[_address];
}
function getBucket(uint windowStart, RequestScheduleLib.TemporalUnit unit)
public pure returns(int)
{
uint bucketSize;
/* since we want to handle both blocks and timestamps
and do not want to get into case where buckets overlaps
block buckets are going to be negative ints
timestamp buckets are going to be positive ints
we'll overflow after 2**255-1 blocks instead of 2**256-1 since we encoding this on int256
*/
int sign;
if (unit == RequestScheduleLib.TemporalUnit.Blocks) {
bucketSize = BLOCKS_BUCKET_SIZE;
sign = -1;
} else if (unit == RequestScheduleLib.TemporalUnit.Timestamp) {
bucketSize = TIMESTAMP_BUCKET_SIZE;
sign = 1;
} else {
throw;
}
return sign * int(windowStart - (windowStart % bucketSize));
}
}
/**
* @title BaseScheduler
* @dev The foundational contract which provides the API for scheduling future transactions on the Alarm Client.
*/
contract BaseScheduler is SchedulerInterface {
using SchedulerLib for SchedulerLib.FutureTransaction;
address public feeRecipient; // Recipient of the fee.
/*
* @dev Fallback function to be able to receive ether. This can occur
* legitimately when scheduling fails due to a validation error.
*/
function() public payable {}
/// Event that bubbles up the address of new requests made with this scheduler.
event NewRequest(address request);
/**
* @dev Schedules a new TransactionRequest using the 'full' parameters.
* @param _toAddress The address destination of the transaction.
* @param _callData The bytecode that will be included with the transaction.
* @param _uintArgs [0] The callGas of the transaction.
* @param _uintArgs [1] The value of ether to be sent with the transaction.
* @param _uintArgs [2] The size of the execution window of the transaction.
* @param _uintArgs [3] The (block or timestamp) of when the execution window starts.
* @param _uintArgs [4] The gasPrice which will be used to execute this transaction.
* @param _uintArgs [5] The fee attached to this transaction.
* @param _uintArgs [6] The bounty attached to this transaction.
* @param _uintArgs [7] The deposit required to claim this transaction.
* @return The address of the new TransactionRequest.
*/
function schedule (
address _toAddress,
bytes _callData,
uint[8] _uintArgs
)
doReset
public payable returns (address newRequest)
{
futureTransaction.toAddress = _toAddress;
futureTransaction.callData = _callData;
futureTransaction.callGas = _uintArgs[0];
futureTransaction.callValue = _uintArgs[1];
futureTransaction.windowSize = _uintArgs[2];
futureTransaction.windowStart = _uintArgs[3];
futureTransaction.gasPrice = _uintArgs[4];
futureTransaction.fee = _uintArgs[5];
futureTransaction.bounty = _uintArgs[6];
futureTransaction.requiredDeposit = _uintArgs[7];
futureTransaction.temporalUnit = temporalUnit;
newRequest = futureTransaction.schedule(factoryAddress, feeRecipient);
require( newRequest != 0x0 );
NewRequest(newRequest);
return newRequest;
}
}
/**
* @title BlockScheduler
* @dev Top-level contract that exposes the API to the Ethereum Alarm Clock service and passes in blocks as temporal unit.
*/
contract BlockScheduler is BaseScheduler {
/**
* @dev Constructor
* @param _factoryAddress Address of the RequestFactory which creates requests for this scheduler.
*/
function BlockScheduler(address _factoryAddress, address _feeRecipient) public {
require(_factoryAddress != 0x0);
// Default temporal unit is block number.
temporalUnit = RequestScheduleLib.TemporalUnit.Blocks;
// Sets the factoryAddress variable found in SchedulerInterface contract.
factoryAddress = _factoryAddress;
// Sets the fee recipient for these schedulers.
feeRecipient = _feeRecipient;
}
}
/**
* @title TimestampScheduler
* @dev Top-level contract that exposes the API to the Ethereum Alarm Clock service and passes in timestamp as temporal unit.
*/
contract TimestampScheduler is BaseScheduler {
/**
* @dev Constructor
* @param _factoryAddress Address of the RequestFactory which creates requests for this scheduler.
*/
function TimestampScheduler(address _factoryAddress, address _feeRecipient) public {
require(_factoryAddress != 0x0);
// Default temporal unit is timestamp.
temporalUnit = RequestScheduleLib.TemporalUnit.Timestamp;
// Sets the factoryAddress variable found in SchedulerInterface contract.
factoryAddress = _factoryAddress;
// Sets the fee recipient for these schedulers.
feeRecipient = _feeRecipient;
}
}
contract TransactionRequestCore is TransactionRequestInterface {
using RequestLib for RequestLib.Request;
using RequestScheduleLib for RequestScheduleLib.ExecutionWindow;
RequestLib.Request txnRequest;
/*
* addressArgs[0] - meta.createdBy
* addressArgs[1] - meta.owner
* addressArgs[2] - paymentData.feeRecipient
* addressArgs[3] - txnData.toAddress
*
* uintArgs[0] - paymentData.fee
* uintArgs[1] - paymentData.bounty
* uintArgs[2] - schedule.claimWindowSize
* uintArgs[3] - schedule.freezePeriod
* uintArgs[4] - schedule.reservedWindowSize
* uintArgs[5] - schedule.temporalUnit
* uintArgs[7] - schedule.executionWindowSize
* uintArgs[6] - schedule.windowStart
* uintArgs[8] - txnData.callGas
* uintArgs[9] - txnData.callValue
* uintArgs[10] - txnData.gasPrice
* uintArgs[11] - claimData.requiredDeposit
*/
function initialize(
address[4] addressArgs,
uint[12] uintArgs,
bytes callData
)
public payable
{
txnRequest.initialize(addressArgs, uintArgs, callData);
}
/*
* Allow receiving ether. This is needed if there is a large increase in
* network gas prices.
*/
function() public payable {}
/*
* Actions
*/
function execute() public returns (bool) {
return txnRequest.execute();
}
function cancel() public returns (bool) {
return txnRequest.cancel();
}
function claim() public payable returns (bool) {
return txnRequest.claim();
}
/*
* Data accessor functions.
*/
// Declaring this function `view`, although it creates a compiler warning, is
// necessary to return values from it.
function requestData()
public view returns (address[6], bool[3], uint[15], uint8[1])
{
if (txnRequest.serialize()) {
return (
txnRequest.serializedValues.addressValues,
txnRequest.serializedValues.boolValues,
txnRequest.serializedValues.uintValues,
txnRequest.serializedValues.uint8Values
);
} else {
revert();
}
}
function callData()
public view returns (bytes data)
{
data = txnRequest.txnData.callData;
}
/**
* @dev Proxy a call from this contract to another contract.
* This function is only callable by the scheduler and can only
* be called after the execution window ends. One purpose is to
* provide a way to transfer assets held by this contract somewhere else.
* For example, if this request was used to buy tokens during an ICO,
* it would become the owner of the tokens and this function would need
* to be called with the encoded data to the token contract to transfer
* the assets somewhere else. */
function proxy(address _to, bytes _data)
public payable returns (bool success)
{
require(txnRequest.meta.owner == msg.sender
&& txnRequest.schedule.isAfterWindow());
return _to.call.value(msg.value)(_data);
}
/*
* Pull based payment functions.
*/
function refundClaimDeposit() public returns (bool) {
txnRequest.refundClaimDeposit();
}
function sendFee() public returns (bool) {
return txnRequest.sendFee();
}
function sendBounty() public returns (bool) {
return txnRequest.sendBounty();
}
function sendOwnerEther() public returns (bool) {
return txnRequest.sendOwnerEther();
}
}
/// Example of using the Scheduler from a smart contract to delay a payment.
contract DelayedPayment {
SchedulerInterface public scheduler;
uint lockedUntil;
address recipient;
function DelayedPayment(
address _scheduler,
uint _numBlocks,
address _recipient
) {
scheduler = SchedulerInterface(_scheduler);
lockedUntil = block.number + _numBlocks;
recipient = _recipient;
scheduler.schedule.value(2 ether)(
recipient, // toAddress
"", // callData
[
2000000, // The amount of gas to be sent with the transaction.
0, // The amount of wei to be sent.
255, // The size of the execution window.
lockedUntil, // The start of the execution window.
30000000000 wei, // The gasprice for the transaction (aka 30 gwei)
12345 wei, // The fee included in the transaction.
224455 wei, // The bounty that awards the executor of the transaction.
20000 wei // The required amount of wei the claimer must send as deposit.
]
);
}
function () {
if (this.balance > 0) {
payout();
} else {
revert();
}
}
function payout()
public returns (bool)
{
require(getNow() >= lockedUntil);
recipient.transfer(this.balance);
return true;
}
function getNow()
internal view returns (uint)
{
return block.number;
}
}
/// Super simple token contract that moves funds into the owner account on creation and
/// only exposes an API to be used for `test/proxy.js`
contract SimpleToken {
address public owner;
mapping(address => uint) balances;
function SimpleToken (uint _initialSupply) public {
owner = msg.sender;
balances[owner] = _initialSupply;
}
function transfer (address _to, uint _amount)
public returns (bool success)
{
require(balances[msg.sender] > _amount);
balances[msg.sender] -= _amount;
balances[_to] += _amount;
success = true;
}
uint public constant rate = 30;
function buyTokens()
public payable returns (bool success)
{
require(msg.value > 0);
balances[msg.sender] += msg.value * rate;
success = true;
}
function balanceOf (address _who)
public view returns (uint balance)
{
balance = balances[_who];
}
}
contract TransactionRecorder {
address owner;
bool public wasCalled;
uint public lastCallValue;
address public lastCaller;
bytes public lastCallData = "";
uint public lastCallGas;
function TransactionRecorder() {
owner = msg.sender;
}
function() payable {
lastCallGas = gasleft();
lastCallData = msg.data;
lastCaller = msg.sender;
lastCallValue = msg.value;
wasCalled = true;
}
function __reset__() public {
lastCallGas = 0;
lastCallData = "";
lastCaller = 0x0;
lastCallValue = 0;
wasCalled = false;
}
function kill() public {
require(msg.sender == owner);
selfdestruct(owner);
}
}
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
require(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// require(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// require(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment