-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alex Connolly
authored and
Alex Connolly
committed
Mar 18, 2024
1 parent
9d34bf4
commit ae133ab
Showing
7 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
pragma solidity >=0.8.4; | ||
|
||
import { INameWrapper, PublicResolver } from "@ensdomains/ens-contracts/contracts/resolvers/PublicResolver.sol"; | ||
import { ENSRegistry } from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol"; | ||
import { FIFSRegistrar } from "@ensdomains/ens-contracts/contracts/registry/FIFSRegistrar.sol"; | ||
import {NameResolver, ReverseRegistrar} from "@ensdomains/ens-contracts/contracts/registry/ReverseRegistrar.sol"; | ||
|
||
// Construct a set of test ENS contracts. | ||
contract ENSDeployer { | ||
|
||
bytes32 public constant TLD_LABEL = keccak256("eth"); | ||
bytes32 public constant RESOLVER_LABEL = keccak256("resolver"); | ||
bytes32 public constant REVERSE_REGISTRAR_LABEL = keccak256("reverse"); | ||
bytes32 public constant ADDR_LABEL = keccak256("addr"); | ||
|
||
ENSRegistry public ens; | ||
FIFSRegistrar public fifsRegistrar; | ||
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) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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( | ||
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 | ||
); | ||
|
||
// 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(); | ||
} | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |