Skip to content

Commit

Permalink
fix compilation, fees, add gog example, zero testing
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Connolly authored and Alex Connolly committed Mar 19, 2024
1 parent 71a160b commit a200d78
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 35 deletions.
9 changes: 9 additions & 0 deletions contracts/pay/IProcessor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright Immutable Pty Ltd 2018 - 2024
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

interface IProcessor {

Check failure on line 6 in contracts/pay/IProcessor.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Delete ⏎
function getQuote(address from, address to, uint256 exactAmountOut) external returns (uint256 quote);

Check failure on line 8 in contracts/pay/IProcessor.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Replace ⏎} with }⏎
}
2 changes: 2 additions & 0 deletions contracts/pay/IRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ interface IRouter {
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function getAmountsOut(uint256 amountIn, address[] memory path) external view returns (uint256[] memory amounts);
}

Check failure on line 23 in contracts/pay/IRouter.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ⏎
82 changes: 54 additions & 28 deletions contracts/pay/Processor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct Order {
struct Receipt {
uint256 id;
Order order;
uint256 paidToken;
address paidToken;
uint256 paidAmount;
}

Expand All @@ -29,14 +29,24 @@ contract Processor {
uint256 public counter;
IRouter public router;
IWrapper public wrappedNativeToken;
uint8 public feePercentage;
address public feeRecipient;

event PaymentProcessed(uint256 indexed id, Order order);
error IncorrectValue(uint256 msgValue, uint256 paymentAmount);
error InsufficientPaymentAmount(uint256 required, uint256 received);
error FailedNativeTokenTransfer();

constructor(IRouter _router, IWrapper _wrappedNativeToken) {
constructor(
IRouter _router,
IWrapper _wrappedNativeToken,
uint8 _feePercentage,
address _feeRecipient
) {
router = _router;
wrappedNativeToken = _wrappedNativeToken;
feePercentage = _feePercentage;
feeRecipient = _feeRecipient;
}

function process(Order calldata order) external {
Expand All @@ -51,7 +61,7 @@ contract Processor {
}

// Receiver can be left blank if no on-chain actions are required
if (order.receiver) {
if (order.receiver != address(0)) {
IReceiver(order.receiver).onPaymentProcessed(receipt);
}
}
Expand All @@ -62,38 +72,39 @@ contract Processor {
}
if (order.pricingToken == order.paymentToken) {
// priced in native currency
_send(order.receiver, msg.value);


_payFeeAndTransferNativeToken(msg.value, order.recipient);

return Receipt({
amount: msg.value,
pricingToken: order.pricingToken,
recipient: order.recipient
id: id,
order: order,
paidToken: order.pricingToken,
paidAmount: msg.value
});
} else {
if (order.allowSwap) {
// wrap token, then swap
wrappedNativeToken.deposit.call{value: msg.value}();
wrappedNativeToken.deposit{value: msg.value}();
uint256 paymentAmount = _executeSwap(

Check warning on line 88 in contracts/pay/Processor.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Variable "paymentAmount" is unused
wrappedNativeToken,
address(wrappedNativeToken),
order.paymentAmount,
order.pricingToken,
order.pricingAmount
);

IERC20(order.pricingAmount).transfer(order.recipient, order.pricingAmount);
_payFeeAndTransferERC20(order.pricingToken, order.pricingAmount, order.recipient);

return Receipt({
id: id,
paymentToken: order.paymentToken,
paymentAmount: paymentAmount,
order: order,
paidToken: order.pricingToken,
paidAmount: order.pricingAmount
});

} else {
// the user is relying on the sale contract to accept their payment currency
uint256 quote = _getQuote(
wrappedNativeToken,
uint256 quote = getQuote(
address(wrappedNativeToken),
order.pricingToken,
order.pricingAmount
);
Expand All @@ -107,10 +118,7 @@ contract Processor {
_send(msg.sender, msg.value - quote);
}

(bool sent,) = order.receiver.call{value: quote}("");
if (!sent) {
revert FailedNativeTokenTransfer();
}
_payFeeAndTransferNativeToken(quote, order.recipient);

// the user is relying on the sale contract to accept their payment currency
return Receipt({
Expand All @@ -124,10 +132,12 @@ contract Processor {
}
}

function _handleERC2OPayment(uint256 id, Order memory order) internal returns (Receipt memory) {
function _handleERC20Payment(uint256 id, Order memory order) internal returns (Receipt memory) {

if (order.pricingToken == order.paymentToken) {
IERC20(order.paymentToken).transferFrom(msg.sender, recipient, order.paymentAmount);

// TODO: fees
IERC20(order.paymentToken).transferFrom(msg.sender, order.recipient, order.paymentAmount);
return Receipt({
id: id,
order: order,
Expand All @@ -144,7 +154,7 @@ contract Processor {
order.pricingAmount
);

IERC20(order.pricingToken).transfer(order.recipient, order.pricingAmount);
_payFeeAndTransferERC20(order.pricingToken, order.pricingAmount, order.recipient);

return Receipt({
id: id,
Expand All @@ -155,7 +165,7 @@ contract Processor {

} else {

uint256 quote = _getQuote(
uint256 quote = getQuote(
order.paymentToken,
order.pricingToken,
order.pricingAmount
Expand All @@ -165,7 +175,7 @@ contract Processor {
revert InsufficientPaymentAmount(quote, order.paymentAmount);
}

IERC20(order.paymentToken).transfer(order.recipient, quote);
_payFeeAndTransferERC20(order.paymentToken, quote, order.recipient);

// the user is relying on the sale contract to accept their payment currency
return Receipt({
Expand All @@ -180,8 +190,8 @@ contract Processor {

function _executeSwap(address from, uint256 amountInMax, address to, uint256 exactAmountOut) internal returns (uint256 amountSwapped) {

from.transferFrom(msg.sender, address(this), amountInMax);
from.approve(address(router), amountInMax);
IERC20(from).transferFrom(msg.sender, address(this), amountInMax);
IERC20(from).approve(address(router), amountInMax);

address[] memory path = new address[](2);
path[0] = from;
Expand All @@ -193,13 +203,13 @@ contract Processor {

// Refund WETH to msg.sender
if (amounts[0] < amountInMax) {
from.transfer(msg.sender, amountInMax - amounts[0]);
IERC20(from).transfer(msg.sender, amountInMax - amounts[0]);
}

return amounts[0];
}

function _getQuote(address from, address to, uint256 exactAmountOut) internal returns (uint256 quote) {
function getQuote(address from, address to, uint256 exactAmountOut) public returns (uint256 quote) {

address[] memory path = new address[](2);
path[0] = from;
Expand All @@ -217,5 +227,21 @@ contract Processor {
}
}

function _payFeeAndTransferERC20(address token, uint256 amount, address recipient) internal {
uint256 fee = _calculateFee(amount);
IERC20(token).transfer(feeRecipient, fee);
IERC20(token).transfer(recipient, amount - fee);
}

function _payFeeAndTransferNativeToken(uint256 amount, address recipient) internal {
uint256 fee = _calculateFee(amount);
_send(feeRecipient, fee);
_send(recipient, amount - fee);
}

function _calculateFee(uint256 value) internal view returns (uint256 fee) {
return (value / 100) * feePercentage;
}


}
78 changes: 78 additions & 0 deletions contracts/pay/test/GOGReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright Immutable Pty Ltd 2018 - 2024
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

import { IReceiver, Receipt } from "../IReceiver.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IProcessor } from "../IProcessor.sol";

/*
GOG wishes to sell packs for fixed amount of USDC.
GOG is happy to receive either USDC or GOG, where the GOG must be equal to USDC at the current exchange rate.
They might consider also accepting ETH and IMX in future.
All other currencies must be swapped to USDC before payment is made.
*/

contract GOGReceiver is IReceiver, Ownable {

IERC721 public erc721;
address public pricingCurrency;
uint256 public price;
IProcessor public processor;

error Placeholder();

mapping(address currency => bool supported) public supportedCurrencies;

constructor(IProcessor _processor, IERC721 _erc721, address _pricingCurrency, uint256 _price) {
processor = _processor;
erc721 = _erc721;
pricingCurrency = _pricingCurrency;
price = _price;
}

function setCurrencySupported(address currency, bool supported) external onlyOwner {
supportedCurrencies[currency] = supported;
}

function onPaymentProcessed(Receipt memory receipt) external {

uint256 quantity = _decodeQuantity(receipt.order.extraData);

if (msg.sender != address(processor)) {
revert Placeholder();
}

if (!supportedCurrencies[receipt.paidToken]) {
revert Placeholder();
}

uint256 totalCost = quantity * price;

if (receipt.paidToken == pricingCurrency) {
// the user directly paid in USDC
if (totalCost > receipt.paidAmount) {
revert Placeholder();
}
} else {
// the user paid in another supported currency
uint256 quote = processor.getQuote(receipt.paidToken, pricingCurrency, totalCost);
if (quote > receipt.paidAmount) {
revert Placeholder();
}
}

// erc721.mint(1, receipt.order.recipient);
}

function _decodeQuantity(bytes memory data) internal view returns (uint256) {
uint256 quantity;
assembly {
// skip first 32 bytes (stores the length of the bytes array)
quantity := mload(add(data, 32))
}
return quantity;
}

}
2 changes: 1 addition & 1 deletion contracts/pay/test/SignatureValidatingReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

import { IReceiver } from "../IReceiver.sol";
import { IReceiver, Receipt } from "../IReceiver.sol";

contract SignatureValidatingReceiver is IReceiver {

Expand Down
13 changes: 7 additions & 6 deletions contracts/pay/test/StrictCurrencyReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

import { IReceiver } from "../IReceiver.sol";
import { IReceiver, Receipt } from "../IReceiver.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract StrictCurrencyReceiver is IReceiver {

Expand All @@ -19,15 +20,15 @@ contract StrictCurrencyReceiver is IReceiver {

function onPaymentProcessed(Receipt memory receipt) external {

if (receipt.paymentToken != address(0)) {
revert OnlyNativeTokenPayments(receipt.paymentToken);
if (receipt.paidToken != address(0)) {
revert OnlyNativeTokenPayments(receipt.paidToken);
}

if (receipt.paymentAmount != purchasePrice) {
revert OnlyExactPayments(receipt.paymentAmount);
if (receipt.paidAmount != purchasePrice) {
revert OnlyExactPayments(receipt.paidAmount);
}

erc721.mint(1, receipt.purchasedFor);
// erc721.mint(1, receipt.order.recipient);
}

}

0 comments on commit a200d78

Please sign in to comment.