Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Connolly authored and Alex Connolly committed Mar 18, 2024
1 parent 9d34bf4 commit ae133ab
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 0 deletions.
55 changes: 55 additions & 0 deletions contracts/names/ENSDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
pragma solidity >=0.8.4;

Check failure on line 1 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Compiler version >=0.8.4 does not satisfy the 0.8.19 semver requirement

import { INameWrapper, PublicResolver } from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol";

Check failure on line 3 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Replace ·INameWrapper,·PublicResolver· with INameWrapper,·PublicResolver
import { ENSRegistry } from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol";

Check failure on line 4 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Replace ·ENSRegistry· with ENSRegistry
import { FIFSRegistrar } from "@ensdomains/ens-contracts/contracts/registry/FIFSRegistrar.sol";

Check failure on line 5 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Replace ·FIFSRegistrar· with FIFSRegistrar
import {NameResolver, ReverseRegistrar} from "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol";

// Construct a set of test ENS contracts.
contract ENSDeployer {

Check failure on line 10 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Delete ··⏎
bytes32 public constant TLD_LABEL = keccak256("eth");
bytes32 public constant RESOLVER_LABEL = keccak256("resolver");

Check failure on line 12 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ··
bytes32 public constant REVERSE_REGISTRAR_LABEL = keccak256("reverse");

Check failure on line 13 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ··
bytes32 public constant ADDR_LABEL = keccak256("addr");

Check failure on line 14 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ··

ENSRegistry public ens;

Check failure on line 16 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ··
FIFSRegistrar public fifsRegistrar;

Check failure on line 17 in contracts/names/ENSDeployer.sol

View workflow job for this annotation

GitHub Actions / Run solhint

Insert ··
ReverseRegistrar public reverseRegistrar;
PublicResolver public publicResolver;

function namehash(bytes32 node, bytes32 label) public pure returns (bytes32) {
return keccak256(abi.encodePacked(node, label));
}

constructor() public {
ens = new ENSRegistry();
publicResolver = new PublicResolver(ens, INameWrapper(address(0)));

// Set up the resolver
bytes32 resolverNode = namehash(bytes32(0), RESOLVER_LABEL);

ens.setSubnodeOwner(bytes32(0), RESOLVER_LABEL, address(this));
ens.setResolver(resolverNode, address(publicResolver));
publicResolver.setAddr(resolverNode, address(publicResolver));

// Create a FIFS registrar for the TLD
fifsRegistrar = new FIFSRegistrar(ens, namehash(bytes32(0), TLD_LABEL));

ens.setSubnodeOwner(bytes32(0), TLD_LABEL, address(fifsRegistrar));

// Construct a new reverse registrar and point it at the public resolver
reverseRegistrar = new ReverseRegistrar(
ens,
NameResolver(address(publicResolver))
);

// Set up the reverse registrar
ens.setSubnodeOwner(bytes32(0), REVERSE_REGISTRAR_LABEL, address(this));
ens.setSubnodeOwner(
namehash(bytes32(0), REVERSE_REGISTRAR_LABEL),
ADDR_LABEL,
address(reverseRegistrar)
);
}
}
8 changes: 8 additions & 0 deletions contracts/pay/IReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pragma solidity ^0.8.19;

import { Receipt } from "./Processor.sol";

interface IReceiver {

function onPaymentProcessed(Receipt memory receipt) external;
}
21 changes: 21 additions & 0 deletions contracts/pay/IRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Immutable Pty Ltd 2018 - 2024
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

interface IRouter {
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);

function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
}
8 changes: 8 additions & 0 deletions contracts/pay/IWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright Immutable Pty Ltd 2018 - 2024
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.19;

interface IWrapper {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
221 changes: 221 additions & 0 deletions contracts/pay/Processor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
pragma solidity ^0.8.19;

import { IReceiver } from "./IReceiver.sol";
import { IRouter } from "./IRouter.sol";
import { IWrapper } from "./IWrapper.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

struct Order {
address purchasedFor; // the user who should receive the purchase
uint256 paymentAmount; // the amount the user is paying (in paymentToken)
address paymentToken; // the token the user is paying in
uint256 pricingAmount;
address pricingToken; // the token the sale is priced in
address receiver; // address of the sale contract
address recipient;
bytes extraData;
bool allowSwap;
}

struct Receipt {
uint256 id;
Order order;
uint256 paidToken;
uint256 paidAmount;
}

contract Processor {

uint256 public counter;
IRouter public router;
IWrapper public wrappedNativeToken;

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

constructor(IRouter _router, IWrapper _wrappedNativeToken) {
router = _router;
wrappedNativeToken = _wrappedNativeToken;
}

function process(Order calldata order) external {

Receipt memory receipt;
uint256 id = counter++;

if (order.paymentToken == address(0)) {
receipt = _handleNativeTokenPayment(id, order);
} else {
receipt = _handleERC20Payment(id, order);
}

// Receiver can be left blank if no on-chain actions are required
if (order.receiver) {
IReceiver(order.receiver).onPaymentProcessed(receipt);
}
}

function _handleNativeTokenPayment(uint256 id, Order memory order) internal returns (Receipt memory) {
if (msg.value != order.paymentAmount) {
revert IncorrectValue(msg.value, order.paymentAmount);
}
if (order.pricingToken == order.paymentToken) {
// priced in native currency
_send(order.receiver, msg.value);

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

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

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

} else {
// the user is relying on the sale contract to accept their payment currency
uint256 quote = _getQuote(
wrappedNativeToken,
order.pricingToken,
order.pricingAmount
);

if (quote > order.paymentAmount) {
revert InsufficientPaymentAmount(quote, order.paymentAmount);
}

if (quote < msg.value) {
// return excess funds to the user
_send(msg.sender, msg.value - quote);
}

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

// the user is relying on the sale contract to accept their payment currency
return Receipt({
id: id,
order: order,
paidToken: order.paymentToken,
paidAmount: quote
});

}
}
}

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

if (order.pricingToken == order.paymentToken) {
IERC20(order.paymentToken).transferFrom(msg.sender, recipient, order.paymentAmount);
return Receipt({
id: id,
order: order,
paidToken: order.pricingToken,
paidAmount: order.paymentAmount
});
} else {
if (order.allowSwap) {

uint256 paymentAmount = _executeSwap(

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

View workflow job for this annotation

GitHub Actions / Run solhint

Variable "paymentAmount" is unused
order.paymentToken,
order.paymentAmount,
order.pricingToken,
order.pricingAmount
);

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

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

} else {

uint256 quote = _getQuote(
order.paymentToken,
order.pricingToken,
order.pricingAmount
);

if (quote > order.paymentAmount) {
revert InsufficientPaymentAmount(quote, order.paymentAmount);
}

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

// the user is relying on the sale contract to accept their payment currency
return Receipt({
id: id,
order: order,
paidToken: order.paymentToken,
paidAmount: quote
});
}
}
}

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);

address[] memory path = new address[](2);
path[0] = from;
path[1] = to;

uint256[] memory amounts = router.swapTokensForExactTokens(
exactAmountOut, amountInMax, path, address(this), block.timestamp

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

View workflow job for this annotation

GitHub Actions / Run solhint

Avoid making time-based decisions in your business logic
);

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

return amounts[0];
}

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

address[] memory path = new address[](2);
path[0] = from;
path[1] = to;

uint256[] memory amounts = router.getAmountsOut(exactAmountOut, path);

return amounts[0];
}

function _send(address to, uint256 value) internal {
(bool sent,) = to.call{value: value}("");
if (!sent) {
revert FailedNativeTokenTransfer();
}
}


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

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

contract SignatureValidatingReceiver is IReceiver {

mapping(address => bool) approvedSigners;

constructor() {

}

function onPaymentProcessed(Receipt memory receipt) external {

}

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

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

contract StrictCurrencyReceiver is IReceiver {

IERC721 public erc721;
uint256 public purchasePrice;

error OnlyNativeTokenPayments(address paymentToken);
error OnlyExactPayments(uint256 paymentAmount);

constructor(IERC721 _erc721, uint256 _purchasePrice) {
erc721 = _erc721;
purchasePrice = _purchasePrice;
}

function onPaymentProcessed(Receipt memory receipt) external {

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

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

erc721.mint(1, receipt.purchasedFor);
}

}

0 comments on commit ae133ab

Please sign in to comment.