Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
pragma solidity ^0.4.23;
contract IERC20 {
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
pragma solidity ^0.4.23;
import "./IERC20.sol";
import "./SafeERC20.sol";
contract Invoice {
using SafeERC20 for IERC20;
address public owner;
address public quoteSigner;
mapping(bytes32 => bool) public isPaid;
event PaymentAccepted(bytes32 indexed hash, address indexed tokenContract, uint time, uint value);
constructor(address valueSigner) public {
owner = msg.sender;
quoteSigner = valueSigner;
}
function isValidPayment(
uint value,
uint gasPrice,
uint expiration,
bytes32 payload,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s,
address tokenContract
) public view returns(bool valid) {
bool isValid = !isPaid[payload];
isValid = isValid && block.timestamp <= expiration;
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 ourHash = keccak256(abi.encodePacked(value, gasPrice, expiration, payload, tokenContract));
bytes32 payloadHash = keccak256(abi.encodePacked(prefix, ourHash));
isValid = isValid && ourHash == hash;
isValid = isValid && (ecrecover(payloadHash, v, r, s) == quoteSigner);
return isValid;
}
function validatePayment(
uint value,
uint gasPrice,
uint expiration,
bytes32 payload,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s,
address tokenContract
) public view returns(bool valid) {
require(isPaid[payload] == false, "Already been paid");
require(block.timestamp <= expiration, "Payment is late");
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 ourHash = keccak256(abi.encodePacked(value, gasPrice, expiration, payload, tokenContract));
bytes32 payloadHash = keccak256(abi.encodePacked(prefix, ourHash));
require(ourHash == hash, "Hash mismatch");
require(ecrecover(payloadHash, v, r, s) == quoteSigner, "Signature mismatch for quote");
return true;
}
function pay(
uint value,
uint gasPrice,
uint expiration,
bytes32 payload,
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s,
address tokenContract
) public payable {
if(tokenContract == 0x0) {
require(validatePayment(msg.value, gasPrice, expiration, payload, hash, v, r, s, tokenContract), "Only accept valid payments");
} else {
IERC20 token = IERC20(tokenContract);
require(token.allowance(msg.sender, address(this)) >= value, "Must have enough tokens to pay");
require(validatePayment(value, gasPrice, expiration, payload, hash, v, r, s, tokenContract), "Only accept valid payments");
require(token.safeTransferFrom(msg.sender, address(this), value), "Transfer must succeed");
}
isPaid[payload] = true;
emit PaymentAccepted(hash, tokenContract, block.timestamp, value);
}
modifier isAdmin() {
require(msg.sender == owner, "Must be the contract owner");
_;
}
function withdraw(address tokenContract) public isAdmin {
if(tokenContract == 0x0) {
owner.transfer(address(this).balance);
} else {
IERC20 token = IERC20(tokenContract);
uint balance = token.balanceOf(address(this));
require(token.safeTransfer(owner, balance), "Must succeed withdrawing tokens");
}
}
function setSigner(address newQuoteSigner) public isAdmin {
quoteSigner = newQuoteSigner;
}
function setAdmin(address newAdmin) public isAdmin {
owner = newAdmin;
}
}
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
pragma solidity ^0.4.23;
import "./IERC20.sol";
/**
* @dev Library to perform safe calls to standard method for ERC20 tokens.
*
* Why Transfers: transfer methods could have a return value (bool), throw or revert for insufficient funds or
* unathorized value.
*
* Why Approve: approve method could has a return value (bool) or does not accept 0 as a valid value (BNB token).
* The common strategy used to clean approvals.
*
* We use the Solidity call instead of interface methods because in the case of transfer, it will fail
* for tokens with an implementation without returning a value.
* Since versions of Solidity 0.4.22 the EVM has a new opcode, called RETURNDATASIZE.
* This opcode stores the size of the returned data of an external call. The code checks the size of the return value
* after an external call and reverts the transaction in case the return data is shorter than expected
*/
library SafeERC20 {
/**
* @dev Transfer token for a specified address
* @param _token erc20 The address of the ERC20 contract
* @param _to address The address which you want to transfer to
* @param _value uint256 the _value of tokens to be transferred
* @return bool whether the transfer was successful or not
*/
function safeTransfer(IERC20 _token, address _to, uint256 _value) internal returns (bool) {
uint256 prevBalance = _token.balanceOf(address(this));
if (prevBalance < _value) {
// Insufficient funds
return false;
}
address(_token).call(
abi.encodeWithSignature("transfer(address,uint256)", _to, _value)
);
// Fail if the new balance its not equal than previous balance sub _value
return prevBalance - _value == _token.balanceOf(address(this));
}
/**
* @dev Transfer tokens from one address to another
* @param _token erc20 The address of the ERC20 contract
* @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 _value of tokens to be transferred
* @return bool whether the transfer was successful or not
*/
function safeTransferFrom(
IERC20 _token,
address _from,
address _to,
uint256 _value
) internal returns (bool)
{
uint256 prevBalance = _token.balanceOf(_from);
if (
prevBalance < _value || // Insufficient funds
_token.allowance(_from, address(this)) < _value // Insufficient allowance
) {
return false;
}
address(_token).call(
abi.encodeWithSignature("transferFrom(address,address,uint256)", _from, _to, _value)
);
// Fail if the new balance its not equal than previous balance sub _value
return prevBalance - _value == _token.balanceOf(_from);
}
/**
* @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 _token erc20 The address of the ERC20 contract
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
* @return bool whether the approve was successful or not
*/
function safeApprove(IERC20 _token, address _spender, uint256 _value) internal returns (bool) {
address(_token).call(
abi.encodeWithSignature("approve(address,uint256)",_spender, _value)
);
// Fail if the new allowance its not equal than _value
return _token.allowance(address(this), _spender) == _value;
}
/**
* @dev Clear approval
* Note that if 0 is not a valid value it will be set to 1.
* @param _token erc20 The address of the ERC20 contract
* @param _spender The address which will spend the funds.
*/
function clearApprove(IERC20 _token, address _spender) internal returns (bool) {
bool success = safeApprove(_token, _spender, 0);
if (!success) {
success = safeApprove(_token, _spender, 1);
}
return success;
}
}