// File: openzeppelin-solidity/contracts/token/ERC20/IERC20.sol
pragma solidity 0.5.8;
/**
* @title ERC20 interface
* @dev see https://eips.ethereum.org/EIPS/eip-20
*/
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// File: openzeppelin-solidity/contracts/math/SafeMath.sol
pragma solidity 0.5.8;
/**
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b);
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a);
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a);
return c;
}
/**
* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0);
return a % b;
}
}
// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
pragma solidity 0.5.8;
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://eips.ethereum.org/EIPS/eip-20
* Originally based on code by FirstBlood:
* https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*
* This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
* all accounts just by listening to said events. Note that this isn't required by the specification, and other
* compliant implementations may not do it.
*/
contract ERC20 is IERC20 {
using SafeMath for uint256;
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowed;
uint256 private _totalSupply;
/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return A uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowed[owner][spender];
}
/**
* @dev Transfer token to a specified address
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev Transfer tokens from one address to another.
* Note that while this function emits an Approval event, this is not required as per the specification,
* and other compliant implementations may not emit the event.
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(address from, address to, uint256 value) public returns (bool) {
_transfer(from, to, value);
_approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
return true;
}
/**
* @dev Increase the amount of tokens that an owner allowed to a spender.
* approve should be called when _allowed[msg.sender][spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param addedValue The amount of tokens to increase the allowance by.
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
return true;
}
/**
* @dev Decrease the amount of tokens that an owner allowed to a spender.
* approve should be called when _allowed[msg.sender][spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param subtractedValue The amount of tokens to decrease the allowance by.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
return true;
}
/**
* @dev Transfer token for a specified addresses
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function _transfer(address from, address to, uint256 value) internal {
require(to != address(0));
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
emit Transfer(from, to, value);
}
/**
* @dev Internal function that mints an amount of the token and assigns it to
* an account. This encapsulates the modification of balances such that the
* proper events are emitted.
* @param account The account that will receive the created tokens.
* @param value The amount that will be created.
*/
function _mint(address account, uint256 value) internal {
require(account != address(0));
_totalSupply = _totalSupply.add(value);
_balances[account] = _balances[account].add(value);
emit Transfer(address(0), account, value);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account.
* @param account The account whose tokens will be burnt.
* @param value The amount that will be burnt.
*/
function _burn(address account, uint256 value) internal {
require(account != address(0));
_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
emit Transfer(account, address(0), value);
}
/**
* @dev Approve an address to spend another addresses' tokens.
* @param owner The address that owns the tokens.
* @param spender The address that will spend the tokens.
* @param value The number of tokens that can be spent.
*/
function _approve(address owner, address spender, uint256 value) internal {
require(spender != address(0));
require(owner != address(0));
_allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @dev Internal function that burns an amount of the token of a given
* account, deducting from the sender's allowance for said account. Uses the
* internal burn function.
* Emits an Approval event (reflecting the reduced allowance).
* @param account The account whose tokens will be burnt.
* @param value The amount that will be burnt.
*/
function _burnFrom(address account, uint256 value) internal {
_burn(account, value);
_approve(account, msg.sender, _allowed[account][msg.sender].sub(value));
}
}
// File: openzeppelin-solidity/contracts/access/Roles.sol
pragma solidity 0.5.8;
/**
* @title Roles
* @dev Library for managing addresses assigned to a Role.
*/
library Roles {
struct Role {
mapping (address => bool) bearer;
}
/**
* @dev give an account access to this role
*/
function add(Role storage role, address account) internal {
require(account != address(0));
require(!has(role, account));
role.bearer[account] = true;
}
/**
* @dev remove an account's access to this role
*/
function remove(Role storage role, address account) internal {
require(account != address(0));
require(has(role, account));
role.bearer[account] = false;
}
/**
* @dev check if an account has this role
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0));
return role.bearer[account];
}
}
// File: openzeppelin-solidity/contracts/access/roles/MinterRole.sol
pragma solidity 0.5.8;
contract MinterRole {
using Roles for Roles.Role;
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
Roles.Role private _minters;
constructor () internal {
_addMinter(msg.sender);
}
modifier onlyMinter() {
require(isMinter(msg.sender));
_;
}
function isMinter(address account) public view returns (bool) {
return _minters.has(account);
}
function addMinter(address account) public onlyMinter {
_addMinter(account);
}
function renounceMinter() public {
_removeMinter(msg.sender);
}
function _addMinter(address account) internal {
_minters.add(account);
emit MinterAdded(account);
}
function _removeMinter(address account) internal {
_minters.remove(account);
emit MinterRemoved(account);
}
}
// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol
pragma solidity 0.5.8;
/**
* @title ERC20Mintable
* @dev ERC20 minting logic
*/
contract ERC20Mintable is ERC20, MinterRole {
/**
* @dev Function to mint tokens
* @param to The address that will receive the minted tokens.
* @param value The amount of tokens to mint.
* @return A boolean that indicates if the operation was successful.
*/
function mint(address to, uint256 value) public onlyMinter returns (bool) {
_mint(to, value);
return true;
}
}
// File: contracts/ownership/PayableOwnable.sol
pragma solidity 0.5.8;
/**
* @title PayableOwnable
* @dev The PayableOwnable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
* PayableOwnable is extended from open-zeppelin Ownable smart contract, with the difference of making the owner
* a payable address.
*/
contract PayableOwnable {
address payable private _owner;
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @return the address of the owner.
*/
function owner() public view returns (address payable) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner());
_;
}
/**
* @return true if `msg.sender` is the owner of the contract.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Allows the current owner to relinquish control of the contract.
* @notice Renouncing to ownership will leave the contract without an owner.
* It will not be possible to call the functions with the `onlyOwner`
* modifier anymore.
*/
function renounceOwnership() public onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address payable newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function _transferOwnership(address payable newOwner) internal {
require(newOwner != address(0));
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// File: contracts/PumaPayPullPaymentV2.sol
pragma solidity 0.5.8;
contract PumaPayPullPayment is PayableOwnable {
using SafeMath for uint256;
/// ===============================================================================================================
/// Events
/// ===============================================================================================================
event LogExecutorAdded(address executor);
event LogExecutorRemoved(address executor);
event LogSetConversionRate(string currency, uint256 conversionRate);
event LogPullPaymentRegistered(
address customerAddress,
bytes32 paymentID,
string uniqueReferenceID
);
event LogPullPaymentCancelled(
address customerAddress,
bytes32 paymentID,
string uniqueReferenceID
);
event LogPullPaymentExecuted(
address customerAddress,
bytes32 paymentID,
string uniqueReferenceID,
uint256 pmaAmountTransferred
);
/// ===============================================================================================================
/// Constants
/// ===============================================================================================================
uint256 constant private DECIMAL_FIXER = 10 ** 10; /// 1e^10 - This transforms the Rate from decimals to uint256
uint256 constant private FIAT_TO_CENT_FIXER = 100; /// Fiat currencies have 100 cents in 1 basic monetary unit.
uint256 constant private OVERFLOW_LIMITER_NUMBER = 10 ** 20; /// 1e^20 - Prevent numeric overflows
uint256 constant private ONE_ETHER = 1 ether; /// PumaPay token has 18 decimals - same as one ETHER
uint256 constant private FUNDING_AMOUNT = 0.5 ether; /// Amount to transfer to owner/executor
uint256 constant private MINIMUM_AMOUNT_OF_ETH_FOR_OPERATORS = 0.15 ether; /// min amount of ETH for owner/executor
/// ===============================================================================================================
/// Members
/// ===============================================================================================================
IERC20 public token;
mapping(string => uint256) private conversionRates;
mapping(address => bool) public executors;
mapping(address => mapping(address => PullPayment)) public pullPayments;
struct PullPayment {
bytes32 paymentID; /// ID of the payment
bytes32 businessID; /// ID of the business
string uniqueReferenceID; /// unique reference ID the business is adding on the pull payment
string currency; /// 3-letter abbr i.e. 'EUR' / 'USD' etc.
uint256 initialPaymentAmountInCents; /// initial payment amount in fiat in cents
uint256 fiatAmountInCents; /// payment amount in fiat in cents
uint256 frequency; /// how often merchant can pull - in seconds
uint256 numberOfPayments; /// amount of pull payments merchant can make
uint256 startTimestamp; /// when subscription starts - in seconds
uint256 nextPaymentTimestamp; /// timestamp of next payment
uint256 lastPaymentTimestamp; /// timestamp of last payment
uint256 cancelTimestamp; /// timestamp the payment was cancelled
address treasuryAddress; /// address which pma tokens will be transfer to on execution
}
/// ===============================================================================================================
/// Modifiers
/// ===============================================================================================================
modifier isExecutor() {
require(executors[msg.sender], "msg.sender not an executor");
_;
}
modifier executorExists(address _executor) {
require(executors[_executor], "Executor does not exists.");
_;
}
modifier executorDoesNotExists(address _executor) {
require(!executors[_executor], "Executor already exists.");
_;
}
modifier paymentExists(address _customer, address _pullPaymentExecutor) {
require(doesPaymentExist(_customer, _pullPaymentExecutor), "Pull Payment does not exists");
_;
}
modifier paymentNotCancelled(address _customer, address _pullPaymentExecutor) {
require(pullPayments[_customer][_pullPaymentExecutor].cancelTimestamp == 0, "Pull Payment is cancelled.");
_;
}
modifier isValidPullPaymentExecutionRequest(address _customer, address _pullPaymentExecutor, bytes32 _paymentID) {
require(
(pullPayments[_customer][_pullPaymentExecutor].initialPaymentAmountInCents > 0 ||
(now >= pullPayments[_customer][_pullPaymentExecutor].startTimestamp &&
now >= pullPayments[_customer][_pullPaymentExecutor].nextPaymentTimestamp)
), "Invalid pull payment execution request - Time of execution is invalid."
);
require(pullPayments[_customer][_pullPaymentExecutor].numberOfPayments > 0,
"Invalid pull payment execution request - Number of payments is zero.");
require((pullPayments[_customer][_pullPaymentExecutor].cancelTimestamp == 0 ||
pullPayments[_customer][_pullPaymentExecutor].cancelTimestamp > pullPayments[_customer][_pullPaymentExecutor].nextPaymentTimestamp),
"Invalid pull payment execution request - Pull payment is cancelled");
require(keccak256(
abi.encodePacked(pullPayments[_customer][_pullPaymentExecutor].paymentID)
) == keccak256(abi.encodePacked(_paymentID)),
"Invalid pull payment execution request - Payment ID not matching.");
_;
}
modifier isValidDeletionRequest(bytes32 _paymentID, address _customer, address _pullPaymentExecutor) {
require(_customer != address(0), "Invalid deletion request - Client address is ZERO_ADDRESS.");
require(_pullPaymentExecutor != address(0), "Invalid deletion request - Beneficiary address is ZERO_ADDRESS.");
require(_paymentID.length != 0, "Invalid deletion request - Payment ID is empty.");
_;
}
modifier isValidAddress(address _address) {
require(_address != address(0), "Invalid address - ZERO_ADDRESS provided");
_;
}
modifier validConversionRate(string memory _currency) {
require(bytes(_currency).length != 0, "Invalid conversion rate - Currency is empty.");
require(conversionRates[_currency] > 0, "Invalid conversion rate - Must be higher than zero.");
_;
}
modifier validAmount(uint256 _fiatAmountInCents) {
require(_fiatAmountInCents > 0, "Invalid amount - Must be higher than zero");
_;
}
/// ===============================================================================================================
/// Constructor
/// ===============================================================================================================
/// @dev Contract constructor - sets the token address that the contract facilitates.
/// @param _token Token Address.
constructor (address _token)
public {
require(_token != address(0), "Invalid address for token - ZERO_ADDRESS provided");
token = IERC20(_token);
}
// @notice Will receive any eth sent to the contract
function() external payable {
}
/// ===============================================================================================================
/// Public Functions - Owner Only
/// ===============================================================================================================
/// @dev Adds a new executor. - can be executed only by the onwer.
/// When adding a new executor 1 ETH is tranferred to allow the executor to pay for gas.
/// The balance of the owner is also checked and if funding is needed 1 ETH is transferred.
/// @param _executor - address of the executor which cannot be zero address.
function addExecutor(address payable _executor)
public
onlyOwner
isValidAddress(_executor)
executorDoesNotExists(_executor)
{
_executor.transfer(FUNDING_AMOUNT);
executors[_executor] = true;
if (isFundingNeeded(owner())) {
owner().transfer(FUNDING_AMOUNT);
}
emit LogExecutorAdded(_executor);
}
/// @dev Removes a new executor. - can be executed only by the onwer.
/// The balance of the owner is checked and if funding is needed 1 ETH is transferred.
/// @param _executor - address of the executor which cannot be zero address.
function removeExecutor(address payable _executor)
public
onlyOwner
isValidAddress(_executor)
executorExists(_executor)
{
executors[_executor] = false;
if (isFundingNeeded(owner())) {
owner().transfer(FUNDING_AMOUNT);
}
emit LogExecutorRemoved(_executor);
}
/// @dev Sets the exchange rate for a currency. - can be executed only by the onwer.
/// Emits 'LogSetConversionRate' with the currency and the updated rate.
/// The balance of the owner is checked and if funding is needed 1 ETH is transferred.
/// @param _currency - address of the executor which cannot be zero address
/// @param _rate - address of the executor which cannot be zero address
function setRate(string memory _currency, uint256 _rate)
public
onlyOwner
returns (bool) {
conversionRates[_currency] = _rate;
emit LogSetConversionRate(_currency, _rate);
if (isFundingNeeded(owner())) {
owner().transfer(FUNDING_AMOUNT);
}
return true;
}
/// ===============================================================================================================
/// Public Functions - Executors Only
/// ===============================================================================================================
/// @dev Registers a new pull payment to the PumaPay Pull Payment Contract - The registration can be executed only
/// by one of the executors of the PumaPay Pull Payment Contract
/// and the PumaPay Pull Payment Contract checks that the pull payment has been singed by the customer of the account.
/// The balance of the executor (msg.sender) is checked and if funding is needed 1 ETH is transferred.
/// Emits 'LogPullPaymentRegistered' with customer address, beneficiary address and paymentID.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _ids - array with the IDs for the payment ([0] paymentID, [1] businessID).
/// @param _addresses - all the relevant addresses for the payment.
/// @param _currency - currency of the payment / 3-letter abbr i.e. 'EUR'.
/// @param _uniqueReferenceID - unique reference ID is the id that the business uses within their system.
/// @param _fiatAmountInCents - payment amount in fiat in cents.
/// @param _frequency - how often merchant can pull - in seconds.
/// @param _numberOfPayments - amount of pull payments merchant can make
/// @param _startTimestamp - when subscription starts - in seconds.
function registerPullPayment(
uint8 v,
bytes32 r,
bytes32 s,
bytes32[2] memory _ids, // [0] paymentID, [1] businessID
address[3] memory _addresses, // [0] customer, [1] pull payment executor, [2] treasury wallet
string memory _currency,
string memory _uniqueReferenceID,
uint256 _initialPaymentAmountInCents,
uint256 _fiatAmountInCents,
uint256 _frequency,
uint256 _numberOfPayments,
uint256 _startTimestamp
)
public
isExecutor()
{
require(_ids[0].length > 0, "Payment ID is empty.");
require(_ids[1].length > 0, "Business ID is empty.");
require(bytes(_currency).length > 0, "Currency is empty.");
require(bytes(_uniqueReferenceID).length > 0, "Unique Reference ID is empty.");
require(_addresses[0] != address(0), "Customer Address is ZERO_ADDRESS.");
require(_addresses[1] != address(0), "Beneficiary Address is ZERO_ADDRESS.");
require(_addresses[2] != address(0), "Treasury Address is ZERO_ADDRESS.");
require(_fiatAmountInCents > 0, "Payment amount in fiat is zero.");
require(_frequency > 0, "Payment frequency is zero.");
require(_frequency < OVERFLOW_LIMITER_NUMBER, "Payment frequency is higher thant the overflow limit.");
require(_numberOfPayments > 0, "Payment number of payments is zero.");
require(_numberOfPayments < OVERFLOW_LIMITER_NUMBER, "Payment number of payments is higher thant the overflow limit.");
require(_startTimestamp > 0, "Payment start time is zero.");
require(_startTimestamp < OVERFLOW_LIMITER_NUMBER, "Payment start time is higher thant the overflow limit.");
pullPayments[_addresses[0]][_addresses[1]].currency = _currency;
pullPayments[_addresses[0]][_addresses[1]].initialPaymentAmountInCents = _initialPaymentAmountInCents;
pullPayments[_addresses[0]][_addresses[1]].fiatAmountInCents = _fiatAmountInCents;
pullPayments[_addresses[0]][_addresses[1]].frequency = _frequency;
pullPayments[_addresses[0]][_addresses[1]].startTimestamp = _startTimestamp;
pullPayments[_addresses[0]][_addresses[1]].numberOfPayments = _numberOfPayments;
pullPayments[_addresses[0]][_addresses[1]].paymentID = _ids[0];
pullPayments[_addresses[0]][_addresses[1]].businessID = _ids[1];
pullPayments[_addresses[0]][_addresses[1]].uniqueReferenceID = _uniqueReferenceID;
pullPayments[_addresses[0]][_addresses[1]].treasuryAddress = _addresses[2];
require(isValidRegistration(
v,
r,
s,
_addresses[0],
_addresses[1],
pullPayments[_addresses[0]][_addresses[1]]),
"Invalid pull payment registration - ECRECOVER_FAILED"
);
pullPayments[_addresses[0]][_addresses[1]].nextPaymentTimestamp = _startTimestamp;
pullPayments[_addresses[0]][_addresses[1]].lastPaymentTimestamp = 0;
pullPayments[_addresses[0]][_addresses[1]].cancelTimestamp = 0;
if (isFundingNeeded(msg.sender)) {
msg.sender.transfer(FUNDING_AMOUNT);
}
emit LogPullPaymentRegistered(
_addresses[0],
_ids[0],
_uniqueReferenceID
);
}
/// @dev Deletes a pull payment for a beneficiary - The deletion needs can be executed only by one of the
/// executors of the PumaPay Pull Payment Contract
/// and the PumaPay Pull Payment Contract checks that the beneficiary and the paymentID have
/// been singed by the customer of the account.
/// This method sets the cancellation of the pull payment in the pull payments array for this beneficiary specified.
/// The balance of the executor (msg.sender) is checked and if funding is needed 1 ETH is transferred.
/// Emits 'LogPullPaymentCancelled' with beneficiary address and paymentID.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the payment.
/// @param _customer - customer address that is linked to this pull payment.
/// @param _pullPaymentExecutor - address that is allowed to execute this pull payment.
function deletePullPayment(
uint8 v,
bytes32 r,
bytes32 s,
bytes32 _paymentID,
address _customer,
address _pullPaymentExecutor
)
public
isExecutor()
paymentExists(_customer, _pullPaymentExecutor)
paymentNotCancelled(_customer, _pullPaymentExecutor)
isValidDeletionRequest(_paymentID, _customer, _pullPaymentExecutor)
{
require(isValidDeletion(v, r, s, _paymentID, _customer, _pullPaymentExecutor), "Invalid deletion - ECRECOVER_FAILED.");
pullPayments[_customer][_pullPaymentExecutor].cancelTimestamp = now;
if (isFundingNeeded(msg.sender)) {
msg.sender.transfer(FUNDING_AMOUNT);
}
emit LogPullPaymentCancelled(
_customer,
_paymentID,
pullPayments[_customer][_pullPaymentExecutor].uniqueReferenceID
);
}
/// ===============================================================================================================
/// Public Functions
/// ===============================================================================================================
/// @dev Executes a pull payment for the msg.sender - The pull payment should exist and the payment request
/// should be valid in terms of when it can be executed.
/// Emits 'LogPullPaymentExecuted' with customer address, msg.sender as the beneficiary address and the paymentID.
/// Use Case 1: Single/Recurring Fixed Pull Payment (initialPaymentAmountInCents == 0 )
/// ------------------------------------------------
/// We calculate the amount in PMA using the rate for the currency specified in the pull payment
/// and the 'fiatAmountInCents' and we transfer from the customer account the amount in PMA.
/// After execution we set the last payment timestamp to NOW, the next payment timestamp is incremented by
/// the frequency and the number of payments is decreased by 1.
/// Use Case 2: Recurring Fixed Pull Payment with initial fee (initialPaymentAmountInCents > 0)
/// ------------------------------------------------------------------------------------------------
/// We calculate the amount in PMA using the rate for the currency specified in the pull payment
/// and the 'initialPaymentAmountInCents' and we transfer from the customer account the amount in PMA.
/// After execution we set the last payment timestamp to NOW and the 'initialPaymentAmountInCents to ZERO.
/// @param _customer - address of the customer from which the msg.sender requires to pull funds.
/// @param _paymentID - ID of the payment.
function executePullPayment(address _customer, bytes32 _paymentID)
public
paymentExists(_customer, msg.sender)
isValidPullPaymentExecutionRequest(_customer, msg.sender, _paymentID)
{
uint256 amountInPMA;
if (pullPayments[_customer][msg.sender].initialPaymentAmountInCents > 0) {
amountInPMA = calculatePMAFromFiat(
pullPayments[_customer][msg.sender].initialPaymentAmountInCents,
pullPayments[_customer][msg.sender].currency
);
pullPayments[_customer][msg.sender].initialPaymentAmountInCents = 0;
} else {
amountInPMA = calculatePMAFromFiat(
pullPayments[_customer][msg.sender].fiatAmountInCents,
pullPayments[_customer][msg.sender].currency
);
pullPayments[_customer][msg.sender].nextPaymentTimestamp =
pullPayments[_customer][msg.sender].nextPaymentTimestamp + pullPayments[_customer][msg.sender].frequency;
pullPayments[_customer][msg.sender].numberOfPayments = pullPayments[_customer][msg.sender].numberOfPayments - 1;
}
pullPayments[_customer][msg.sender].lastPaymentTimestamp = now;
token.transferFrom(
_customer,
pullPayments[_customer][msg.sender].treasuryAddress,
amountInPMA
);
emit LogPullPaymentExecuted(
_customer,
pullPayments[_customer][msg.sender].paymentID,
pullPayments[_customer][msg.sender].uniqueReferenceID,
amountInPMA
);
}
function getRate(string memory _currency) public view returns (uint256) {
return conversionRates[_currency];
}
/// ===============================================================================================================
/// Internal Functions
/// ===============================================================================================================
/// @dev Calculates the PMA Rate for the fiat currency specified - The rate is set every 10 minutes by our PMA server
/// for the currencies specified in the smart contract.
/// @param _fiatAmountInCents - payment amount in fiat CENTS so that is always integer
/// @param _currency - currency in which the payment needs to take place
/// RATE CALCULATION EXAMPLE
/// ------------------------
/// RATE ==> 1 PMA = 0.01 USD$
/// 1 USD$ = 1/0.01 PMA = 100 PMA
/// Start the calculation from one ether - PMA Token has 18 decimals
/// Multiply by the DECIMAL_FIXER (1e+10) to fix the multiplication of the rate
/// Multiply with the fiat amount in cents
/// Divide by the Rate of PMA to Fiat in cents
/// Divide by the FIAT_TO_CENT_FIXER to fix the _fiatAmountInCents
function calculatePMAFromFiat(uint256 _fiatAmountInCents, string memory _currency)
internal
view
validConversionRate(_currency)
validAmount(_fiatAmountInCents)
returns (uint256) {
return ONE_ETHER.mul(DECIMAL_FIXER).mul(_fiatAmountInCents).div(conversionRates[_currency]).div(FIAT_TO_CENT_FIXER);
}
/// @dev Checks if a registration request is valid by comparing the v, r, s params
/// and the hashed params with the customer address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _customer - customer address that is linked to this pull payment.
/// @param _pullPaymentExecutor - address that is allowed to execute this pull payment.
/// @param _pullPayment - pull payment to be validated.
/// @return bool - if the v, r, s params with the hashed params match the customer address
function isValidRegistration(
uint8 v,
bytes32 r,
bytes32 s,
address _customer,
address _pullPaymentExecutor,
PullPayment memory _pullPayment
)
internal
pure
returns (bool)
{
return ecrecover(
keccak256(
abi.encodePacked(
_pullPaymentExecutor,
_pullPayment.paymentID,
_pullPayment.businessID,
_pullPayment.uniqueReferenceID,
_pullPayment.treasuryAddress,
_pullPayment.currency,
_pullPayment.initialPaymentAmountInCents,
_pullPayment.fiatAmountInCents,
_pullPayment.frequency,
_pullPayment.numberOfPayments,
_pullPayment.startTimestamp
)
),
v, r, s) == _customer;
}
/// @dev Checks if a deletion request is valid by comparing the v, r, s params
/// and the hashed params with the customer address.
/// @param v - recovery ID of the ETH signature. - https://github.com/ethereum/EIPs/issues/155
/// @param r - R output of ECDSA signature.
/// @param s - S output of ECDSA signature.
/// @param _paymentID - ID of the payment.
/// @param _customer - customer address that is linked to this pull payment.
/// @param _pullPaymentExecutor - address that is allowed to execute this pull payment.
/// @return bool - if the v, r, s params with the hashed params match the customer address
function isValidDeletion(
uint8 v,
bytes32 r,
bytes32 s,
bytes32 _paymentID,
address _customer,
address _pullPaymentExecutor
)
internal
view
returns (bool)
{
return ecrecover(
keccak256(
abi.encodePacked(
_paymentID,
_pullPaymentExecutor
)
), v, r, s) == _customer
&& keccak256(
abi.encodePacked(pullPayments[_customer][_pullPaymentExecutor].paymentID)
) == keccak256(abi.encodePacked(_paymentID)
);
}
/// @dev Checks if a payment for a beneficiary of a customer exists.
/// @param _customer - customer address that is linked to this pull payment.
/// @param _pullPaymentExecutor - address to execute a pull payment.
/// @return bool - whether the beneficiary for this customer has a pull payment to execute.
function doesPaymentExist(address _customer, address _pullPaymentExecutor)
internal
view
returns (bool) {
return (
bytes(pullPayments[_customer][_pullPaymentExecutor].currency).length > 0 &&
pullPayments[_customer][_pullPaymentExecutor].fiatAmountInCents > 0 &&
pullPayments[_customer][_pullPaymentExecutor].frequency > 0 &&
pullPayments[_customer][_pullPaymentExecutor].startTimestamp > 0 &&
pullPayments[_customer][_pullPaymentExecutor].numberOfPayments > 0 &&
pullPayments[_customer][_pullPaymentExecutor].nextPaymentTimestamp > 0
);
}
/// @dev Checks if the address of an owner/executor needs to be funded.
/// The minimum amount the owner/executors should always have is 0.001 ETH
/// @param _address - address of owner/executors that the balance is checked against.
/// @return bool - whether the address needs more ETH.
function isFundingNeeded(address _address)
private
view
returns (bool) {
return address(_address).balance <= MINIMUM_AMOUNT_OF_ETH_FOR_OPERATORS;
}
}
{
"compilationTarget": {
"PumaPayPullPayment.sol": "PumaPayPullPayment"
},
"evmVersion": "petersburg",
"libraries": {},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"constant":false,"inputs":[{"name":"_executor","type":"address"}],"name":"addExecutor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_executor","type":"address"}],"name":"removeExecutor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_currency","type":"string"}],"name":"getRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"pullPayments","outputs":[{"name":"paymentID","type":"bytes32"},{"name":"businessID","type":"bytes32"},{"name":"uniqueReferenceID","type":"string"},{"name":"currency","type":"string"},{"name":"initialPaymentAmountInCents","type":"uint256"},{"name":"fiatAmountInCents","type":"uint256"},{"name":"frequency","type":"uint256"},{"name":"numberOfPayments","type":"uint256"},{"name":"startTimestamp","type":"uint256"},{"name":"nextPaymentTimestamp","type":"uint256"},{"name":"lastPaymentTimestamp","type":"uint256"},{"name":"cancelTimestamp","type":"uint256"},{"name":"treasuryAddress","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_customer","type":"address"},{"name":"_paymentID","type":"bytes32"}],"name":"executePullPayment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"executors","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"_ids","type":"bytes32[2]"},{"name":"_addresses","type":"address[3]"},{"name":"_currency","type":"string"},{"name":"_uniqueReferenceID","type":"string"},{"name":"_initialPaymentAmountInCents","type":"uint256"},{"name":"_fiatAmountInCents","type":"uint256"},{"name":"_frequency","type":"uint256"},{"name":"_numberOfPayments","type":"uint256"},{"name":"_startTimestamp","type":"uint256"}],"name":"registerPullPayment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_currency","type":"string"},{"name":"_rate","type":"uint256"}],"name":"setRate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"_paymentID","type":"bytes32"},{"name":"_customer","type":"address"},{"name":"_pullPaymentExecutor","type":"address"}],"name":"deletePullPayment","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_token","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"executor","type":"address"}],"name":"LogExecutorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"executor","type":"address"}],"name":"LogExecutorRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"currency","type":"string"},{"indexed":false,"name":"conversionRate","type":"uint256"}],"name":"LogSetConversionRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"customerAddress","type":"address"},{"indexed":false,"name":"paymentID","type":"bytes32"},{"indexed":false,"name":"uniqueReferenceID","type":"string"}],"name":"LogPullPaymentRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"customerAddress","type":"address"},{"indexed":false,"name":"paymentID","type":"bytes32"},{"indexed":false,"name":"uniqueReferenceID","type":"string"}],"name":"LogPullPaymentCancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"customerAddress","type":"address"},{"indexed":false,"name":"paymentID","type":"bytes32"},{"indexed":false,"name":"uniqueReferenceID","type":"string"},{"indexed":false,"name":"pmaAmountTransferred","type":"uint256"}],"name":"LogPullPaymentExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]