/** * Source Code first verified at https://etherscan.io on Friday, March 22, 2019 (UTC) */ pragma solidity ^0.4.23; /** * @title SafeMath * @dev Math operations with safety checks that throw on error * @dev SafeMath adapted for uint96 */ library SafeMathUint96 { function mul(uint96 a, uint96 b) internal pure returns (uint96) { uint96 c = a * b; assert(a == 0 || c / a == b); return c; } function div(uint96 a, uint96 b) internal pure returns (uint96) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint96 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint96 a, uint96 b) internal pure returns (uint96) { assert(b <= a); return a - b; } function add(uint96 a, uint96 b) internal pure returns (uint96) { uint96 c = a + b; assert(c >= a); return c; } } /** * @title SafeMath * @dev Math operations with safety checks that throw on error * @dev SafeMath adapted for uint8 */ library SafeMathUint8 { function mul(uint8 a, uint8 b) internal pure returns (uint8) { uint8 c = a * b; assert(a == 0 || c / a == b); return c; } function div(uint8 a, uint8 b) internal pure returns (uint8) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint8 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(uint8 a, uint8 b) internal pure returns (uint8) { assert(b <= a); return a - b; } function add(uint8 a, uint8 b) internal pure returns (uint8) { uint8 c = a + b; assert(c >= a); return c; } } /** * @title SafeMathInt * @dev Math operations with safety checks that throw on error * @dev SafeMath adapted for int256 */ library SafeMathInt { function mul(int256 a, int256 b) internal pure returns (int256) { // Prevent overflow when multiplying INT256_MIN with -1 // https://github.com/RequestNetwork/requestNetwork/issues/43 assert(!(a == - 2**255 && b == -1) && !(b == - 2**255 && a == -1)); int256 c = a * b; assert((b == 0) || (c / b == a)); return c; } function div(int256 a, int256 b) internal pure returns (int256) { // Prevent overflow when dividing INT256_MIN by -1 // https://github.com/RequestNetwork/requestNetwork/issues/43 assert(!(a == - 2**255 && b == -1)); // assert(b > 0); // Solidity automatically throws when dividing by 0 int256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } function sub(int256 a, int256 b) internal pure returns (int256) { assert((b >= 0 && a - b <= a) || (b < 0 && a - b > a)); return a - b; } function add(int256 a, int256 b) internal pure returns (int256) { int256 c = a + b; assert((b >= 0 && c >= a) || (b < 0 && c < a)); return c; } function toUint256Safe(int256 a) internal pure returns (uint256) { assert(a>=0); return uint256(a); } } // From https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol /** * @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; assert(a == 0 || c / a == b); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 uint256 c = a / b; // assert(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) { assert(b <= a); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; assert(c >= a); return c; } function toInt256Safe(uint256 a) internal pure returns (int256) { int256 b = int256(a); assert(b >= 0); return b; } } /** * @title Bytes util library. * @notice Collection of utility functions to manipulate bytes for Request. */ library Bytes { /** * @notice Extracts an address in a bytes. * @param data bytes from where the address will be extract * @param offset position of the first byte of the address * @return address */ function extractAddress(bytes data, uint offset) internal pure returns (address m) { require(offset >= 0 && offset + 20 <= data.length, "offset value should be in the correct range"); // solium-disable-next-line security/no-inline-assembly assembly { m := and( mload(add(data, add(20, offset))), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ) } } /** * @notice Extract a bytes32 from a bytes. * @param data bytes from where the bytes32 will be extract * @param offset position of the first byte of the bytes32 * @return address */ function extractBytes32(bytes data, uint offset) internal pure returns (bytes32 bs) { require(offset >= 0 && offset + 32 <= data.length, "offset value should be in the correct range"); // solium-disable-next-line security/no-inline-assembly assembly { bs := mload(add(data, add(32, offset))) } } /** * @notice Modifies 20 bytes in a bytes. * @param data bytes to modify * @param offset position of the first byte to modify * @param b bytes20 to insert * @return address */ function updateBytes20inBytes(bytes data, uint offset, bytes20 b) internal pure { require(offset >= 0 && offset + 20 <= data.length, "offset value should be in the correct range"); // solium-disable-next-line security/no-inline-assembly assembly { let m := mload(add(data, add(20, offset))) m := and(m, 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000) m := or(m, div(b, 0x1000000000000000000000000)) mstore(add(data, add(20, offset)), m) } } /** * @notice Extracts a string from a bytes. Extracts a sub-part from the bytes and convert it to string. * @param data bytes from where the string will be extracted * @param size string size to extract * @param _offset position of the first byte of the string in bytes * @return string */ function extractString(bytes data, uint8 size, uint _offset) internal pure returns (string) { bytes memory bytesString = new bytes(size); for (uint j = 0; j < size; j++) { bytesString[j] = data[_offset+j]; } return string(bytesString); } } /** * @title Request Signature util library. * @notice Collection of utility functions to handle Request signatures. */ library Signature { using SafeMath for uint256; using SafeMathInt for int256; using SafeMathUint8 for uint8; /** * @notice Checks the validity of a signed request & the expiration date. * @param requestData bytes containing all the data packed : address(creator) address(payer) uint8(number_of_payees) [ address(main_payee_address) int256(main_payee_expected_amount) address(second_payee_address) int256(second_payee_expected_amount) ... ] uint8(data_string_size) size(data) * @param payeesPaymentAddress array of payees payment addresses (the index 0 will be the payee the others are subPayees) * @param expirationDate timestamp after that the signed request cannot be broadcasted * @param signature ECDSA signature containing v, r and s as bytes * * @return Validity of order signature. */ function checkRequestSignature( bytes requestData, address[] payeesPaymentAddress, uint256 expirationDate, bytes signature) internal view returns (bool) { bytes32 hash = getRequestHash(requestData, payeesPaymentAddress, expirationDate); // extract "v, r, s" from the signature uint8 v = uint8(signature[64]); v = v < 27 ? v.add(27) : v; bytes32 r = Bytes.extractBytes32(signature, 0); bytes32 s = Bytes.extractBytes32(signature, 32); // check signature of the hash with the creator address return isValidSignature( Bytes.extractAddress(requestData, 0), hash, v, r, s ); } /** * @notice Checks the validity of a Bitcoin signed request & the expiration date. * @param requestData bytes containing all the data packed : address(creator) address(payer) uint8(number_of_payees) [ address(main_payee_address) int256(main_payee_expected_amount) address(second_payee_address) int256(second_payee_expected_amount) ... ] uint8(data_string_size) size(data) * @param payeesPaymentAddress array of payees payment addresses (the index 0 will be the payee the others are subPayees) * @param expirationDate timestamp after that the signed request cannot be broadcasted * @param signature ECDSA signature containing v, r and s as bytes * * @return Validity of order signature. */ function checkBtcRequestSignature( bytes requestData, bytes payeesPaymentAddress, uint256 expirationDate, bytes signature) internal view returns (bool) { bytes32 hash = getBtcRequestHash(requestData, payeesPaymentAddress, expirationDate); // extract "v, r, s" from the signature uint8 v = uint8(signature[64]); v = v < 27 ? v.add(27) : v; bytes32 r = Bytes.extractBytes32(signature, 0); bytes32 s = Bytes.extractBytes32(signature, 32); // check signature of the hash with the creator address return isValidSignature( Bytes.extractAddress(requestData, 0), hash, v, r, s ); } /** * @notice Calculates the Keccak-256 hash of a BTC request with specified parameters. * * @param requestData bytes containing all the data packed * @param payeesPaymentAddress array of payees payment addresses * @param expirationDate timestamp after what the signed request cannot be broadcasted * * @return Keccak-256 hash of (this, requestData, payeesPaymentAddress, expirationDate) */ function getBtcRequestHash( bytes requestData, bytes payeesPaymentAddress, uint256 expirationDate) private view returns(bytes32) { return keccak256( abi.encodePacked( this, requestData, payeesPaymentAddress, expirationDate ) ); } /** * @dev Calculates the Keccak-256 hash of a (not BTC) request with specified parameters. * * @param requestData bytes containing all the data packed * @param payeesPaymentAddress array of payees payment addresses * @param expirationDate timestamp after what the signed request cannot be broadcasted * * @return Keccak-256 hash of (this, requestData, payeesPaymentAddress, expirationDate) */ function getRequestHash( bytes requestData, address[] payeesPaymentAddress, uint256 expirationDate) private view returns(bytes32) { return keccak256( abi.encodePacked( this, requestData, payeesPaymentAddress, expirationDate ) ); } /** * @notice Verifies that a hash signature is valid. 0x style. * @param signer address of signer. * @param hash Signed Keccak-256 hash. * @param v ECDSA signature parameter v. * @param r ECDSA signature parameters r. * @param s ECDSA signature parameters s. * @return Validity of order signature. */ function isValidSignature( address signer, bytes32 hash, uint8 v, bytes32 r, bytes32 s) private pure returns (bool) { return signer == ecrecover( keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)), v, r, s ); } } /** * @title ERC20Basic * @dev Simpler version of ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/179 */ contract ERC20Basic { uint256 public totalSupply; function balanceOf(address who) public constant returns (uint256); function transfer(address to, uint256 value) public returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); } /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ contract ERC20 is ERC20Basic { function allowance(address owner, address spender) public constant returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); } /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @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 newOwner) onlyOwner public { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } /** * @title FeeCollector * * @notice FeeCollector is a contract managing the fees for currency contracts */ contract FeeCollector is Ownable { using SafeMath for uint256; uint256 public rateFeesNumerator; uint256 public rateFeesDenominator; uint256 public maxFees; // address of the contract that will burn req token address public requestBurnerContract; event UpdateRateFees(uint256 rateFeesNumerator, uint256 rateFeesDenominator); event UpdateMaxFees(uint256 maxFees); /** * @param _requestBurnerContract Address of the contract where to send the ether. * This burner contract will have a function that can be called by anyone and will exchange ether to req via Kyber and burn the REQ */ constructor(address _requestBurnerContract) public { requestBurnerContract = _requestBurnerContract; } /** * @notice Sets the fees rate. * @dev if the _rateFeesDenominator is 0, it will be treated as 1. (in other words, the computation of the fees will not use it) * @param _rateFeesNumerator numerator rate * @param _rateFeesDenominator denominator rate */ function setRateFees(uint256 _rateFeesNumerator, uint256 _rateFeesDenominator) external onlyOwner { rateFeesNumerator = _rateFeesNumerator; rateFeesDenominator = _rateFeesDenominator; emit UpdateRateFees(rateFeesNumerator, rateFeesDenominator); } /** * @notice Sets the maximum fees in wei. * @param _newMaxFees new max */ function setMaxCollectable(uint256 _newMaxFees) external onlyOwner { maxFees = _newMaxFees; emit UpdateMaxFees(maxFees); } /** * @notice Set the request burner address. * @param _requestBurnerContract address of the contract that will burn req token (probably through Kyber) */ function setRequestBurnerContract(address _requestBurnerContract) external onlyOwner { requestBurnerContract = _requestBurnerContract; } /** * @notice Computes the fees. * @param _expectedAmount amount expected for the request * @return the expected amount of fees in wei */ function collectEstimation(int256 _expectedAmount) public view returns(uint256) { if (_expectedAmount<0) { return 0; } uint256 computedCollect = uint256(_expectedAmount).mul(rateFeesNumerator); if (rateFeesDenominator != 0) { computedCollect = computedCollect.div(rateFeesDenominator); } return computedCollect < maxFees ? computedCollect : maxFees; } /** * @notice Sends fees to the request burning address. * @param _amount amount to send to the burning address */ function collectForREQBurning(uint256 _amount) internal { // .transfer throws on failure requestBurnerContract.transfer(_amount); } } /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; Unpause(); } } /** * @title Administrable * @notice Base contract for the administration of Core. Handles whitelisting of currency contracts */ contract Administrable is Pausable { // mapping of address of trusted contract mapping(address => uint8) public trustedCurrencyContracts; // Events of the system event NewTrustedContract(address newContract); event RemoveTrustedContract(address oldContract); /** * @notice Adds a trusted currencyContract. * * @param _newContractAddress The address of the currencyContract */ function adminAddTrustedCurrencyContract(address _newContractAddress) external onlyOwner { trustedCurrencyContracts[_newContractAddress] = 1; //Using int instead of boolean in case we need several states in the future. emit NewTrustedContract(_newContractAddress); } /** * @notice Removes a trusted currencyContract. * * @param _oldTrustedContractAddress The address of the currencyContract */ function adminRemoveTrustedCurrencyContract(address _oldTrustedContractAddress) external onlyOwner { require(trustedCurrencyContracts[_oldTrustedContractAddress] != 0, "_oldTrustedContractAddress should not be 0"); trustedCurrencyContracts[_oldTrustedContractAddress] = 0; emit RemoveTrustedContract(_oldTrustedContractAddress); } /** * @notice Gets the status of a trusted currencyContract . * @dev Not used today, useful if we have several states in the future. * * @param _contractAddress The address of the currencyContract * @return The status of the currencyContract. If trusted 1, otherwise 0 */ function getStatusContract(address _contractAddress) external view returns(uint8) { return trustedCurrencyContracts[_contractAddress]; } /** * @notice Checks if a currencyContract is trusted. * * @param _contractAddress The address of the currencyContract * @return bool true if contract is trusted */ function isTrustedContract(address _contractAddress) public view returns(bool) { return trustedCurrencyContracts[_contractAddress] == 1; } } /** * @title RequestCore * * @notice The Core is the main contract which stores all the requests. * * @dev The Core philosophy is to be as much flexible as possible to adapt in the future to any new system * @dev All the important conditions and an important part of the business logic takes place in the currency contracts. * @dev Requests can only be created in the currency contracts * @dev Currency contracts have to be allowed by the Core and respect the business logic. * @dev Request Network will develop one currency contracts per currency and anyone can creates its own currency contracts. */ contract RequestCore is Administrable { using SafeMath for uint256; using SafeMathUint96 for uint96; using SafeMathInt for int256; using SafeMathUint8 for uint8; enum State { Created, Accepted, Canceled } struct Request { // ID address of the payer address payer; // Address of the contract managing the request address currencyContract; // State of the request State state; // Main payee Payee payee; } // Structure for the payees. A sub payee is an additional entity which will be paid during the processing of the invoice. // ex: can be used for routing taxes or fees at the moment of the payment. struct Payee { // ID address of the payee address addr; // amount expected for the payee. // Not uint for evolution (may need negative amounts one day), and simpler operations int256 expectedAmount; // balance of the payee int256 balance; } // Count of request in the mapping. A maximum of 2^96 requests can be created per Core contract. // Integer, incremented for each request of a Core contract, starting from 0 // RequestId (256bits) = contract address (160bits) + numRequest uint96 public numRequests; // Mapping of all the Requests. The key is the request ID. // not anymore public to avoid "UnimplementedFeatureError: Only in-memory reference type can be stored." // https://github.com/ethereum/solidity/issues/3577 mapping(bytes32 => Request) requests; // Mapping of subPayees of the requests. The key is the request ID. // This array is outside the Request structure to optimize the gas cost when there is only 1 payee. mapping(bytes32 => Payee[256]) public subPayees; /* * Events */ event Created(bytes32 indexed requestId, address indexed payee, address indexed payer, address creator, string data); event Accepted(bytes32 indexed requestId); event Canceled(bytes32 indexed requestId); // Event for Payee & subPayees // Separated from the Created Event to allow a 4th indexed parameter (subpayees) event NewSubPayee(bytes32 indexed requestId, address indexed payee); event UpdateExpectedAmount(bytes32 indexed requestId, uint8 payeeIndex, int256 deltaAmount); event UpdateBalance(bytes32 indexed requestId, uint8 payeeIndex, int256 deltaAmount); /** * @notice Function used by currency contracts to create a request in the Core. * * @dev _payees and _expectedAmounts must have the same size. * * @param _creator Request creator. The creator is the one who initiated the request (create or sign) and not necessarily the one who broadcasted it * @param _payees array of payees address (the index 0 will be the payee the others are subPayees). Size must be smaller than 256. * @param _expectedAmounts array of Expected amount to be received by each payees. Must be in same order than the payees. Size must be smaller than 256. * @param _payer Entity expected to pay * @param _data data of the request * @return Returns the id of the request */ function createRequest( address _creator, address[] _payees, int256[] _expectedAmounts, address _payer, string _data) external whenNotPaused returns (bytes32 requestId) { // creator must not be null require(_creator != 0, "creator should not be 0"); // not as modifier to lighten the stack // call must come from a trusted contract require(isTrustedContract(msg.sender), "caller should be a trusted contract"); // not as modifier to lighten the stack // Generate the requestId requestId = generateRequestId(); address mainPayee; int256 mainExpectedAmount; // extract the main payee if filled if (_payees.length!=0) { mainPayee = _payees[0]; mainExpectedAmount = _expectedAmounts[0]; } // Store the new request requests[requestId] = Request( _payer, msg.sender, State.Created, Payee( mainPayee, mainExpectedAmount, 0 ) ); // Declare the new request emit Created( requestId, mainPayee, _payer, _creator, _data ); // Store and declare the sub payees (needed in internal function to avoid "stack too deep") initSubPayees(requestId, _payees, _expectedAmounts); return requestId; } /** * @notice Function used by currency contracts to create a request in the Core from bytes. * @dev Used to avoid receiving a stack too deep error when called from a currency contract with too many parameters. * @dev Note that to optimize the stack size and the gas cost we do not extract the params and store them in the stack. As a result there is some code redundancy * @param _data bytes containing all the data packed : address(creator) address(payer) uint8(number_of_payees) [ address(main_payee_address) int256(main_payee_expected_amount) address(second_payee_address) int256(second_payee_expected_amount) ... ] uint8(data_string_size) size(data) * @return Returns the id of the request */ function createRequestFromBytes(bytes _data) external whenNotPaused returns (bytes32 requestId) { // call must come from a trusted contract require(isTrustedContract(msg.sender), "caller should be a trusted contract"); // not as modifier to lighten the stack // extract address creator & payer address creator = extractAddress(_data, 0); address payer = extractAddress(_data, 20); // creator must not be null require(creator!=0, "creator should not be 0"); // extract the number of payees uint8 payeesCount = uint8(_data[40]); // get the position of the dataSize in the byte (= number_of_payees * (address_payee_size + int256_payee_size) + address_creator_size + address_payer_size + payees_count_size // (= number_of_payees * (20+32) + 20 + 20 + 1 ) uint256 offsetDataSize = uint256(payeesCount).mul(52).add(41); // extract the data size and then the data itself uint8 dataSize = uint8(_data[offsetDataSize]); string memory dataStr = extractString(_data, dataSize, offsetDataSize.add(1)); address mainPayee; int256 mainExpectedAmount; // extract the main payee if possible if (payeesCount!=0) { mainPayee = extractAddress(_data, 41); mainExpectedAmount = int256(extractBytes32(_data, 61)); } // Generate the requestId requestId = generateRequestId(); // Store the new request requests[requestId] = Request( payer, msg.sender, State.Created, Payee( mainPayee, mainExpectedAmount, 0 ) ); // Declare the new request emit Created( requestId, mainPayee, payer, creator, dataStr ); // Store and declare the sub payees for (uint8 i = 1; i < payeesCount; i = i.add(1)) { address subPayeeAddress = extractAddress(_data, uint256(i).mul(52).add(41)); // payees address cannot be 0x0 require(subPayeeAddress != 0, "subpayee should not be 0"); subPayees[requestId][i-1] = Payee(subPayeeAddress, int256(extractBytes32(_data, uint256(i).mul(52).add(61))), 0); emit NewSubPayee(requestId, subPayeeAddress); } return requestId; } /** * @notice Function used by currency contracts to accept a request in the Core. * @dev callable only by the currency contract of the request * @param _requestId Request id */ function accept(bytes32 _requestId) external { Request storage r = requests[_requestId]; require(r.currencyContract == msg.sender, "caller should be the currency contract of the request"); r.state = State.Accepted; emit Accepted(_requestId); } /** * @notice Function used by currency contracts to cancel a request in the Core. Several reasons can lead to cancel a request, see request life cycle for more info. * @dev callable only by the currency contract of the request. * @param _requestId Request id */ function cancel(bytes32 _requestId) external { Request storage r = requests[_requestId]; require(r.currencyContract == msg.sender, "caller should be the currency contract of the request"); r.state = State.Canceled; emit Canceled(_requestId); } /** * @notice Function used to update the balance. * @dev callable only by the currency contract of the request. * @param _requestId Request id * @param _payeeIndex index of the payee (0 = main payee) * @param _deltaAmount modifier amount */ function updateBalance(bytes32 _requestId, uint8 _payeeIndex, int256 _deltaAmount) external { Request storage r = requests[_requestId]; require(r.currencyContract == msg.sender, "caller should be the currency contract of the request"); if ( _payeeIndex == 0 ) { // modify the main payee r.payee.balance = r.payee.balance.add(_deltaAmount); } else { // modify the sub payee Payee storage sp = subPayees[_requestId][_payeeIndex-1]; sp.balance = sp.balance.add(_deltaAmount); } emit UpdateBalance(_requestId, _payeeIndex, _deltaAmount); } /** * @notice Function update the expectedAmount adding additional or subtract. * @dev callable only by the currency contract of the request. * @param _requestId Request id * @param _payeeIndex index of the payee (0 = main payee) * @param _deltaAmount modifier amount */ function updateExpectedAmount(bytes32 _requestId, uint8 _payeeIndex, int256 _deltaAmount) external { Request storage r = requests[_requestId]; require(r.currencyContract == msg.sender, "caller should be the currency contract of the request"); if ( _payeeIndex == 0 ) { // modify the main payee r.payee.expectedAmount = r.payee.expectedAmount.add(_deltaAmount); } else { // modify the sub payee Payee storage sp = subPayees[_requestId][_payeeIndex-1]; sp.expectedAmount = sp.expectedAmount.add(_deltaAmount); } emit UpdateExpectedAmount(_requestId, _payeeIndex, _deltaAmount); } /** * @notice Gets a request. * @param _requestId Request id * @return request as a tuple : (address payer, address currencyContract, State state, address payeeAddr, int256 payeeExpectedAmount, int256 payeeBalance) */ function getRequest(bytes32 _requestId) external view returns(address payer, address currencyContract, State state, address payeeAddr, int256 payeeExpectedAmount, int256 payeeBalance) { Request storage r = requests[_requestId]; return ( r.payer, r.currencyContract, r.state, r.payee.addr, r.payee.expectedAmount, r.payee.balance ); } /** * @notice Gets address of a payee. * @param _requestId Request id * @param _payeeIndex payee index (0 = main payee) * @return payee address */ function getPayeeAddress(bytes32 _requestId, uint8 _payeeIndex) public view returns(address) { if (_payeeIndex == 0) { return requests[_requestId].payee.addr; } else { return subPayees[_requestId][_payeeIndex-1].addr; } } /** * @notice Gets payer of a request. * @param _requestId Request id * @return payer address */ function getPayer(bytes32 _requestId) public view returns(address) { return requests[_requestId].payer; } /** * @notice Gets amount expected of a payee. * @param _requestId Request id * @param _payeeIndex payee index (0 = main payee) * @return amount expected */ function getPayeeExpectedAmount(bytes32 _requestId, uint8 _payeeIndex) public view returns(int256) { if (_payeeIndex == 0) { return requests[_requestId].payee.expectedAmount; } else { return subPayees[_requestId][_payeeIndex-1].expectedAmount; } } /** * @notice Gets number of subPayees for a request. * @param _requestId Request id * @return number of subPayees */ function getSubPayeesCount(bytes32 _requestId) public view returns(uint8) { // solium-disable-next-line no-empty-blocks for (uint8 i = 0; subPayees[_requestId][i].addr != address(0); i = i.add(1)) {} return i; } /** * @notice Gets currencyContract of a request. * @param _requestId Request id * @return currencyContract address */ function getCurrencyContract(bytes32 _requestId) public view returns(address) { return requests[_requestId].currencyContract; } /** * @notice Gets balance of a payee. * @param _requestId Request id * @param _payeeIndex payee index (0 = main payee) * @return balance */ function getPayeeBalance(bytes32 _requestId, uint8 _payeeIndex) public view returns(int256) { if (_payeeIndex == 0) { return requests[_requestId].payee.balance; } else { return subPayees[_requestId][_payeeIndex-1].balance; } } /** * @notice Gets balance total of a request. * @param _requestId Request id * @return balance */ function getBalance(bytes32 _requestId) public view returns(int256) { int256 balance = requests[_requestId].payee.balance; for (uint8 i = 0; subPayees[_requestId][i].addr != address(0); i = i.add(1)) { balance = balance.add(subPayees[_requestId][i].balance); } return balance; } /** * @notice Checks if all the payees balances are null. * @param _requestId Request id * @return true if all the payees balances are equals to 0 */ function areAllBalanceNull(bytes32 _requestId) public view returns(bool isNull) { isNull = requests[_requestId].payee.balance == 0; for (uint8 i = 0; isNull && subPayees[_requestId][i].addr != address(0); i = i.add(1)) { isNull = subPayees[_requestId][i].balance == 0; } return isNull; } /** * @notice Gets total expectedAmount of a request. * @param _requestId Request id * @return balance */ function getExpectedAmount(bytes32 _requestId) public view returns(int256) { int256 expectedAmount = requests[_requestId].payee.expectedAmount; for (uint8 i = 0; subPayees[_requestId][i].addr != address(0); i = i.add(1)) { expectedAmount = expectedAmount.add(subPayees[_requestId][i].expectedAmount); } return expectedAmount; } /** * @notice Gets state of a request. * @param _requestId Request id * @return state */ function getState(bytes32 _requestId) public view returns(State) { return requests[_requestId].state; } /** * @notice Gets address of a payee. * @param _requestId Request id * @return payee index (0 = main payee) or -1 if not address not found */ function getPayeeIndex(bytes32 _requestId, address _address) public view returns(int16) { // return 0 if main payee if (requests[_requestId].payee.addr == _address) { return 0; } for (uint8 i = 0; subPayees[_requestId][i].addr != address(0); i = i.add(1)) { if (subPayees[_requestId][i].addr == _address) { // if found return subPayee index + 1 (0 is main payee) return i+1; } } return -1; } /** * @notice Extracts a bytes32 from a bytes. * @param _data bytes from where the bytes32 will be extract * @param offset position of the first byte of the bytes32 * @return address */ function extractBytes32(bytes _data, uint offset) public pure returns (bytes32 bs) { require(offset >= 0 && offset + 32 <= _data.length, "offset value should be in the correct range"); // solium-disable-next-line security/no-inline-assembly assembly { bs := mload(add(_data, add(32, offset))) } } /** * @notice Transfers to owner any tokens send by mistake on this contracts. * @param token The address of the token to transfer. * @param amount The amount to be transfered. */ function emergencyERC20Drain(ERC20 token, uint amount ) public onlyOwner { token.transfer(owner, amount); } /** * @notice Extracts an address from a bytes at a given position. * @param _data bytes from where the address will be extract * @param offset position of the first byte of the address * @return address */ function extractAddress(bytes _data, uint offset) internal pure returns (address m) { require(offset >= 0 && offset + 20 <= _data.length, "offset value should be in the correct range"); // solium-disable-next-line security/no-inline-assembly assembly { m := and( mload(add(_data, add(20, offset))), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) } } /** * @dev Internal: Init payees for a request (needed to avoid 'stack too deep' in createRequest()). * @param _requestId Request id * @param _payees array of payees address * @param _expectedAmounts array of payees initial expected amounts */ function initSubPayees(bytes32 _requestId, address[] _payees, int256[] _expectedAmounts) internal { require(_payees.length == _expectedAmounts.length, "payee length should equal expected amount length"); for (uint8 i = 1; i < _payees.length; i = i.add(1)) { // payees address cannot be 0x0 require(_payees[i] != 0, "payee should not be 0"); subPayees[_requestId][i-1] = Payee(_payees[i], _expectedAmounts[i], 0); emit NewSubPayee(_requestId, _payees[i]); } } /** * @notice Extracts a string from a bytes. Extracts a sub-part from tha bytes and convert it to string. * @param data bytes from where the string will be extracted * @param size string size to extract * @param _offset position of the first byte of the string in bytes * @return string */ function extractString(bytes data, uint8 size, uint _offset) internal pure returns (string) { bytes memory bytesString = new bytes(size); for (uint j = 0; j < size; j++) { bytesString[j] = data[_offset+j]; } return string(bytesString); } /** * @notice Generates a new unique requestId. * @return a bytes32 requestId */ function generateRequestId() internal returns (bytes32) { // Update numRequest numRequests = numRequests.add(1); // requestId = ADDRESS_CONTRACT_CORE + numRequests (0xADRRESSCONTRACT00000NUMREQUEST) return bytes32((uint256(this) << 96).add(numRequests)); } } /** * @title CurrencyContract * * @notice CurrencyContract is the base for currency contracts. To add a currency to the Request Protocol, create a new currencyContract that inherits from it. * @dev If currency contract is whitelisted by core & unpaused: All actions possible * @dev If currency contract is not Whitelisted by core & unpaused: Creation impossible, other actions possible * @dev If currency contract is paused: Nothing possible * * Functions that can be implemented by the currency contracts: * - createRequestAsPayeeAction * - createRequestAsPayerAction * - broadcastSignedRequestAsPayer * - paymentActionPayable * - paymentAction * - accept * - cancel * - refundAction * - subtractAction * - additionalAction */ contract CurrencyContract is Pausable, FeeCollector { using SafeMath for uint256; using SafeMathInt for int256; using SafeMathUint8 for uint8; RequestCore public requestCore; /** * @param _requestCoreAddress Request Core address */ constructor(address _requestCoreAddress, address _addressBurner) FeeCollector(_addressBurner) public { requestCore = RequestCore(_requestCoreAddress); } /** * @notice Function to accept a request. * * @notice msg.sender must be _payer, The request must be in the state CREATED (not CANCELED, not ACCEPTED). * * @param _requestId id of the request */ function acceptAction(bytes32 _requestId) public whenNotPaused onlyRequestPayer(_requestId) { // only a created request can be accepted require(requestCore.getState(_requestId) == RequestCore.State.Created, "request should be created"); // declare the acceptation in the core requestCore.accept(_requestId); } /** * @notice Function to cancel a request. * * @dev msg.sender must be the _payer or the _payee. * @dev only request with balance equals to zero can be cancel. * * @param _requestId id of the request */ function cancelAction(bytes32 _requestId) public whenNotPaused { // payer can cancel if request is just created // payee can cancel when request is not canceled yet require( // solium-disable-next-line indentation (requestCore.getPayer(_requestId) == msg.sender && requestCore.getState(_requestId) == RequestCore.State.Created) || (requestCore.getPayeeAddress(_requestId,0) == msg.sender && requestCore.getState(_requestId) != RequestCore.State.Canceled), "payer should cancel a newly created request, or payee should cancel a not cancel request" ); // impossible to cancel a Request with any payees balance != 0 require(requestCore.areAllBalanceNull(_requestId), "all balanaces should be = 0 to cancel"); // declare the cancellation in the core requestCore.cancel(_requestId); } /** * @notice Function to declare additionals. * * @dev msg.sender must be _payer. * @dev the request must be accepted or created. * * @param _requestId id of the request * @param _additionalAmounts amounts of additional to declare (index 0 is for main payee) */ function additionalAction(bytes32 _requestId, uint256[] _additionalAmounts) public whenNotPaused onlyRequestPayer(_requestId) { // impossible to make additional if request is canceled require(requestCore.getState(_requestId) != RequestCore.State.Canceled, "request should not be canceled"); // impossible to declare more additionals than the number of payees require( _additionalAmounts.length <= requestCore.getSubPayeesCount(_requestId).add(1), "number of amounts should be <= number of payees" ); for (uint8 i = 0; i < _additionalAmounts.length; i = i.add(1)) { // no need to declare a zero as additional if (_additionalAmounts[i] != 0) { // Store and declare the additional in the core requestCore.updateExpectedAmount(_requestId, i, _additionalAmounts[i].toInt256Safe()); } } } /** * @notice Function to declare subtracts. * * @dev msg.sender must be _payee. * @dev the request must be accepted or created. * * @param _requestId id of the request * @param _subtractAmounts amounts of subtract to declare (index 0 is for main payee) */ function subtractAction(bytes32 _requestId, uint256[] _subtractAmounts) public whenNotPaused onlyRequestPayee(_requestId) { // impossible to make subtracts if request is canceled require(requestCore.getState(_requestId) != RequestCore.State.Canceled, "request should not be canceled"); // impossible to declare more subtracts than the number of payees require( _subtractAmounts.length <= requestCore.getSubPayeesCount(_requestId).add(1), "number of amounts should be <= number of payees" ); for (uint8 i = 0; i < _subtractAmounts.length; i = i.add(1)) { // no need to declare a zero as subtracts if (_subtractAmounts[i] != 0) { // subtract must be equal or lower than amount expected require( requestCore.getPayeeExpectedAmount(_requestId,i) >= _subtractAmounts[i].toInt256Safe(), "subtract should equal or be lower than amount expected" ); // Store and declare the subtract in the core requestCore.updateExpectedAmount(_requestId, i, -_subtractAmounts[i].toInt256Safe()); } } } /** * @notice Base function for request creation. * * @dev msg.sender will be the creator. * * @param _payer Entity expected to pay * @param _payeesIdAddress array of payees address (the index 0 will be the payee - must be msg.sender - the others are subPayees) * @param _expectedAmounts array of Expected amount to be received by each payees * @param _data Hash linking to additional data on the Request stored on IPFS * * @return Returns the id of the request and the collected fees */ function createCoreRequestInternal( address _payer, address[] _payeesIdAddress, int256[] _expectedAmounts, string _data) internal whenNotPaused returns(bytes32 requestId, uint256 collectedFees) { int256 totalExpectedAmounts = 0; for (uint8 i = 0; i < _expectedAmounts.length; i = i.add(1)) { // all expected amounts must be positive require(_expectedAmounts[i] >= 0, "expected amounts should be positive"); // compute the total expected amount of the request totalExpectedAmounts = totalExpectedAmounts.add(_expectedAmounts[i]); } // store request in the core requestId = requestCore.createRequest( msg.sender, _payeesIdAddress, _expectedAmounts, _payer, _data ); // compute and send fees collectedFees = collectEstimation(totalExpectedAmounts); collectForREQBurning(collectedFees); } /** * @notice Modifier to check if msg.sender is the main payee. * @dev Revert if msg.sender is not the main payee. * @param _requestId id of the request */ modifier onlyRequestPayee(bytes32 _requestId) { require(requestCore.getPayeeAddress(_requestId, 0) == msg.sender, "only the payee should do this action"); _; } /** * @notice Modifier to check if msg.sender is payer. * @dev Revert if msg.sender is not payer. * @param _requestId id of the request */ modifier onlyRequestPayer(bytes32 _requestId) { require(requestCore.getPayer(_requestId) == msg.sender, "only the payer should do this action"); _; } } /** * @title RequestERC20 * @notice Currency contract managing the requests in ERC20 tokens. * @dev Requests can be created by the Payee with createRequestAsPayeeAction(), by the payer with createRequestAsPayerAction() or by the payer from a request signed offchain by the payee with broadcastSignedRequestAsPayer */ contract RequestERC20 is CurrencyContract { using SafeMath for uint256; using SafeMathInt for int256; using SafeMathUint8 for uint8; // payment addresses by requestId (optional). We separate the Identity of the payee/payer (in the core) and the wallet address in the currency contract mapping(bytes32 => address[256]) public payeesPaymentAddress; mapping(bytes32 => address) public payerRefundAddress; // token address ERC20 public erc20Token; /** * @param _requestCoreAddress Request Core address * @param _requestBurnerAddress Request Burner contract address * @param _erc20Token ERC20 token contract handled by this currency contract */ constructor (address _requestCoreAddress, address _requestBurnerAddress, ERC20 _erc20Token) CurrencyContract(_requestCoreAddress, _requestBurnerAddress) public { erc20Token = _erc20Token; } /** * @notice Function to create a request as payee. * * @dev msg.sender must be the main payee. * @dev if _payeesPaymentAddress.length > _payeesIdAddress.length, the extra addresses will be stored but never used. * * @param _payeesIdAddress array of payees address (the index 0 will be the payee - must be msg.sender - the others are subPayees) * @param _payeesPaymentAddress array of payees address for payment (optional) * @param _expectedAmounts array of Expected amount to be received by each payees * @param _payer Entity expected to pay * @param _payerRefundAddress Address of refund for the payer (optional) * @param _data Hash linking to additional data on the Request stored on IPFS * * @return Returns the id of the request */ function createRequestAsPayeeAction( address[] _payeesIdAddress, address[] _payeesPaymentAddress, int256[] _expectedAmounts, address _payer, address _payerRefundAddress, string _data) external payable whenNotPaused returns(bytes32 requestId) { require( msg.sender == _payeesIdAddress[0] && msg.sender != _payer && _payer != 0, "caller should be the payee" ); uint256 collectedFees; (requestId, collectedFees) = createCoreRequestInternal( _payer, _payeesIdAddress, _expectedAmounts, _data ); // Additional check on the fees: they should be equal to the about of ETH sent require(collectedFees == msg.value, "fees should be the correct amout"); // set payment addresses for payees for (uint8 j = 0; j < _payeesPaymentAddress.length; j = j.add(1)) { payeesPaymentAddress[requestId][j] = _payeesPaymentAddress[j]; } // set payment address for payer if (_payerRefundAddress != 0) { payerRefundAddress[requestId] = _payerRefundAddress; } return requestId; } /** * @notice Function to broadcast and accept an offchain signed request (the broadcaster can also pays and makes additionals). * * @dev msg.sender will be the _payer. * @dev only the _payer can make additionals. * @dev if _payeesPaymentAddress.length > _requestData.payeesIdAddress.length, the extra addresses will be stored but never used. * * @param _requestData nasty bytes containing : creator, payer, payees|expectedAmounts, data * @param _payeesPaymentAddress array of payees address for payment (optional) * @param _payeeAmounts array of amount repartition for the payment * @param _additionals array to increase the ExpectedAmount for payees * @param _expirationDate timestamp after that the signed request cannot be broadcasted * @param _signature ECDSA signature in bytes * * @return Returns the id of the request */ function broadcastSignedRequestAsPayerAction( bytes _requestData, // gather data to avoid "stack too deep" address[] _payeesPaymentAddress, uint256[] _payeeAmounts, uint256[] _additionals, uint256 _expirationDate, bytes _signature) external payable whenNotPaused returns(bytes32 requestId) { // check expiration date // solium-disable-next-line security/no-block-members require(_expirationDate >= block.timestamp, "expiration should be after current time"); // check the signature require( Signature.checkRequestSignature( _requestData, _payeesPaymentAddress, _expirationDate, _signature ), "signature should be correct" ); return createAcceptAndPayFromBytes( _requestData, _payeesPaymentAddress, _payeeAmounts, _additionals ); } /** * @notice Function to pay a request in ERC20 token. * * @dev msg.sender must have a balance of the token higher or equal to the sum of _payeeAmounts. * @dev msg.sender must have approved an amount of the token higher or equal to the sum of _payeeAmounts to the current contract. * @dev the request will be automatically accepted if msg.sender==payer. * * @param _requestId id of the request * @param _payeeAmounts Amount to pay to payees (sum must be equal to msg.value) in wei * @param _additionalAmounts amount of additionals per payee in wei to declare */ function paymentAction( bytes32 _requestId, uint256[] _payeeAmounts, uint256[] _additionalAmounts) external whenNotPaused { // automatically accept request if request is created and msg.sender is payer if (requestCore.getState(_requestId)==RequestCore.State.Created && msg.sender == requestCore.getPayer(_requestId)) { acceptAction(_requestId); } if (_additionalAmounts.length != 0) { additionalAction(_requestId, _additionalAmounts); } paymentInternal(_requestId, _payeeAmounts); } /** * @notice Function to pay back in ERC20 token a request to the payees. * * @dev msg.sender must have a balance of the token higher or equal to _amountToRefund. * @dev msg.sender must have approved an amount of the token higher or equal to _amountToRefund to the current contract. * @dev msg.sender must be one of the payees or one of the payees payment address. * @dev the request must be created or accepted. * * @param _requestId id of the request */ function refundAction(bytes32 _requestId, uint256 _amountToRefund) external whenNotPaused { refundInternal(_requestId, msg.sender, _amountToRefund); } /** * @notice Function to create a request as payer. The request is payed if _payeeAmounts > 0. * * @dev msg.sender will be the payer. * @dev If a contract is given as a payee make sure it is payable. Otherwise, the request will not be payable. * @dev Is public instead of external to avoid "Stack too deep" exception. * * @param _payeesIdAddress array of payees address (the index 0 will be the payee the others are subPayees) * @param _expectedAmounts array of Expected amount to be received by each payees * @param _payerRefundAddress Address of refund for the payer (optional) * @param _payeeAmounts array of amount repartition for the payment * @param _additionals array to increase the ExpectedAmount for payees * @param _data Hash linking to additional data on the Request stored on IPFS * * @return Returns the id of the request */ function createRequestAsPayerAction( address[] _payeesIdAddress, int256[] _expectedAmounts, address _payerRefundAddress, uint256[] _payeeAmounts, uint256[] _additionals, string _data) public payable whenNotPaused returns(bytes32 requestId) { require(msg.sender != _payeesIdAddress[0] && _payeesIdAddress[0] != 0, "caller should not be the main payee"); uint256 collectedFees; (requestId, collectedFees) = createCoreRequestInternal( msg.sender, _payeesIdAddress, _expectedAmounts, _data ); // Additional check on the fees: they should be equal to the about of ETH sent require(collectedFees == msg.value, "fees should be the correct amout"); // set payment address for payer if (_payerRefundAddress != 0) { payerRefundAddress[requestId] = _payerRefundAddress; } // compute the total expected amount of the request // this computation is also made in createCoreRequestInternal but we do it again here to have better decoupling int256 totalExpectedAmounts = 0; for (uint8 i = 0; i < _expectedAmounts.length; i = i.add(1)) { totalExpectedAmounts = totalExpectedAmounts.add(_expectedAmounts[i]); } // accept and pay the request with the value remaining after the fee collect acceptAndPay( requestId, _payeeAmounts, _additionals, totalExpectedAmounts ); return requestId; } /** * @dev Internal function to create, accept, add additionals and pay a request as Payer. * * @dev msg.sender must be _payer. * * @param _requestData nasty bytes containing : creator, payer, payees|expectedAmounts, data * @param _payeesPaymentAddress array of payees address for payment (optional) * @param _payeeAmounts array of amount repartition for the payment * @param _additionals Will increase the ExpectedAmount of the request right after its creation by adding additionals * * @return Returns the id of the request */ function createAcceptAndPayFromBytes( bytes _requestData, address[] _payeesPaymentAddress, uint256[] _payeeAmounts, uint256[] _additionals) internal returns(bytes32 requestId) { // extract main payee address mainPayee = Bytes.extractAddress(_requestData, 41); require(msg.sender != mainPayee && mainPayee != 0, "caller should not be the main payee"); // creator must be the main payee require(Bytes.extractAddress(_requestData, 0) == mainPayee, "creator should be the main payee"); // extract the number of payees uint8 payeesCount = uint8(_requestData[40]); int256 totalExpectedAmounts = 0; for (uint8 i = 0; i < payeesCount; i++) { // extract the expectedAmount for the payee[i] int256 expectedAmountTemp = int256(Bytes.extractBytes32(_requestData, uint256(i).mul(52).add(61))); // compute the total expected amount of the request totalExpectedAmounts = totalExpectedAmounts.add(expectedAmountTemp); // all expected amount must be positive require(expectedAmountTemp > 0, "expected amount should be > 0"); } // compute and send fees uint256 fees = collectEstimation(totalExpectedAmounts); require(fees == msg.value, "fees should be the correct amout"); collectForREQBurning(fees); // insert the msg.sender as the payer in the bytes Bytes.updateBytes20inBytes(_requestData, 20, bytes20(msg.sender)); // store request in the core requestId = requestCore.createRequestFromBytes(_requestData); // set payment addresses for payees for (uint8 j = 0; j < _payeesPaymentAddress.length; j = j.add(1)) { payeesPaymentAddress[requestId][j] = _payeesPaymentAddress[j]; } // accept and pay the request with the value remaining after the fee collect acceptAndPay( requestId, _payeeAmounts, _additionals, totalExpectedAmounts ); return requestId; } /** * @dev Internal function to manage payment declaration. * * @param _requestId id of the request * @param _payeeAmounts Amount to pay to payees (sum must be equals to msg.value) */ function paymentInternal( bytes32 _requestId, uint256[] _payeeAmounts) internal { require(requestCore.getState(_requestId) != RequestCore.State.Canceled, "request should not be canceled"); // we cannot have more amounts declared than actual payees require( _payeeAmounts.length <= requestCore.getSubPayeesCount(_requestId).add(1), "number of amounts should be <= number of payees" ); for (uint8 i = 0; i < _payeeAmounts.length; i = i.add(1)) { if (_payeeAmounts[i] != 0) { // Store and declare the payment to the core requestCore.updateBalance(_requestId, i, _payeeAmounts[i].toInt256Safe()); // pay the payment address if given, the id address otherwise address addressToPay; if (payeesPaymentAddress[_requestId][i] == 0) { addressToPay = requestCore.getPayeeAddress(_requestId, i); } else { addressToPay = payeesPaymentAddress[_requestId][i]; } // payment done, the token need to be sent fundOrderInternal(msg.sender, addressToPay, _payeeAmounts[i]); } } } /** * @dev Internal function to accept, add additionals and pay a request as Payer * * @param _requestId id of the request * @param _payeeAmounts Amount to pay to payees (sum must be equals to _amountPaid) * @param _additionals Will increase the ExpectedAmounts of payees * @param _payeeAmountsSum total of amount token send for this transaction * */ function acceptAndPay( bytes32 _requestId, uint256[] _payeeAmounts, uint256[] _additionals, int256 _payeeAmountsSum) internal { acceptAction(_requestId); additionalAction(_requestId, _additionals); if (_payeeAmountsSum > 0) { paymentInternal(_requestId, _payeeAmounts); } } /** * @dev Internal function to manage refund declaration * * @param _requestId id of the request * @param _address address from where the refund has been done * @param _amount amount of the refund in ERC20 token to declare */ function refundInternal( bytes32 _requestId, address _address, uint256 _amount) internal { require(requestCore.getState(_requestId) != RequestCore.State.Canceled, "request should not be canceled"); // Check if the _address is a payeesId int16 payeeIndex = requestCore.getPayeeIndex(_requestId, _address); // get the number of payees uint8 payeesCount = requestCore.getSubPayeesCount(_requestId).add(1); if (payeeIndex < 0) { // if not ID addresses maybe in the payee payments addresses for (uint8 i = 0; i < payeesCount && payeeIndex == -1; i = i.add(1)) { if (payeesPaymentAddress[_requestId][i] == _address) { // get the payeeIndex payeeIndex = int16(i); } } } // the address must be found somewhere require(payeeIndex >= 0, "fromAddress should be a payee"); // useless (subPayee size <256): require(payeeIndex < 265); requestCore.updateBalance(_requestId, uint8(payeeIndex), -_amount.toInt256Safe()); // refund to the payment address if given, the id address otherwise address addressToPay = payerRefundAddress[_requestId]; if (addressToPay == 0) { addressToPay = requestCore.getPayer(_requestId); } // refund declared, the money is ready to be sent to the payer fundOrderInternal(_address, addressToPay, _amount); } /** * @dev Internal function to manage fund mouvement. * * @param _from address where the token will get from * @param _recipient address where the token has to be sent to * @param _amount amount in ERC20 token to send */ function fundOrderInternal( address _from, address _recipient, uint256 _amount) internal { require(erc20Token.transferFrom(_from, _recipient, _amount), "erc20 transfer should succeed"); } }