forked from sudoswap/lssvm
-
Notifications
You must be signed in to change notification settings - Fork 0
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
Owen
committed
Aug 12, 2022
1 parent
2ef9876
commit c0961a4
Showing
1 changed file
with
288 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,288 @@ | ||
// SPDX-License-Identifier: AGPL-3.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | ||
import {ERC20} from "solmate/tokens/ERC20.sol"; | ||
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; | ||
import {LSSVMPair} from "./LSSVMPair.sol"; | ||
import {ILSSVMPairFactoryLike} from "./ILSSVMPairFactoryLike.sol"; | ||
import {CurveErrorCodes} from "./bonding-curves/CurveErrorCodes.sol"; | ||
|
||
contract LSSVMRouter2 { | ||
using SafeTransferLib for address payable; | ||
using SafeTransferLib for ERC20; | ||
|
||
struct PairSwapSpecific { | ||
LSSVMPair pair; | ||
uint256[] nftIds; | ||
} | ||
|
||
struct RobustPairSwapSpecific { | ||
PairSwapSpecific swapInfo; | ||
uint256 maxCost; | ||
} | ||
|
||
struct RobustPairSwapSpecificForToken { | ||
PairSwapSpecific swapInfo; | ||
uint256 minOutput; | ||
} | ||
|
||
struct PairSwapSpecificPartialFill { | ||
PairSwapSpecific swapInfo; | ||
uint256 minNFTBalance; | ||
uint256[] maxCostPerNumNFTs; | ||
} | ||
|
||
struct PairSwapSpecificPartialFillForToken { | ||
PairSwapSpecific swapInfo; | ||
uint256[] minOutputPerNumNFTs; | ||
} | ||
|
||
struct RobustPairNFTsFoTokenAndTokenforNFTsTrade { | ||
RobustPairSwapSpecific[] tokenToNFTTrades; | ||
RobustPairSwapSpecificForToken[] nftToTokenTrades; | ||
uint256 inputAmount; | ||
address payable tokenRecipient; | ||
address nftRecipient; | ||
} | ||
|
||
ILSSVMPairFactoryLike public immutable factory; | ||
|
||
constructor(ILSSVMPairFactoryLike _factory) { | ||
factory = _factory; | ||
} | ||
|
||
/** | ||
@dev Allows a pair contract to transfer ERC721 NFTs directly from | ||
the sender, in order to minimize the number of token transfers. Only callable by a pair. | ||
@param nft The ERC721 NFT to transfer | ||
@param from The address to transfer tokens from | ||
@param to The address to transfer tokens to | ||
@param id The ID of the NFT to transfer | ||
@param variant The pair variant of the pair contract | ||
*/ | ||
function pairTransferNFTFrom( | ||
IERC721 nft, | ||
address from, | ||
address to, | ||
uint256 id, | ||
ILSSVMPairFactoryLike.PairVariant variant | ||
) external { | ||
// verify caller is a trusted pair contract | ||
require(factory.isPair(msg.sender, variant), "Not pair"); | ||
|
||
// transfer NFTs to pair | ||
nft.safeTransferFrom(from, to, id); | ||
} | ||
|
||
/** | ||
@dev Performs a batch of buys and sells, avoids performing swaps where the price is beyond | ||
*/ | ||
function robustBuySellWithETHAndPartialFill() external payable { | ||
|
||
// Go through each buy item | ||
// Check to see if the quote is as expected | ||
// If it is, then send that amt over to buy | ||
// If the quote is more, then check the number of NFTs (presumably less than expected) | ||
// Take the difference and figure out which ones are still buyable | ||
// Look up the max cost we're willing to pay | ||
// Look up the getBuyNFTQuote for the new amount | ||
// If it is within our bounds, still go ahead and buy | ||
// Send excess funds back to caller | ||
|
||
// Go through each sell item | ||
// Check to see if the quote is as expected | ||
// If it is, then do the NFT->ETH swap | ||
// (if selling multiple items? --> do the same thing as above for buys?) | ||
// Otherwise, move on to the next sell attempt | ||
} | ||
|
||
/** | ||
@dev Buys the NFTs first, then sells them. Intended to be used for arbitrage. | ||
*/ | ||
function buyNFTsThenSellWithETH( | ||
RobustPairSwapSpecific[] calldata buyList, | ||
RobustPairSwapSpecificForToken[] calldata sellList | ||
) external payable { | ||
// Locally scope the buys | ||
{ | ||
// Start with all of the ETH sent | ||
uint256 remainingValue = msg.value; | ||
uint256 numBuys = buyList.length; | ||
|
||
// Do all buy swaps | ||
for (uint256 i; i < numBuys; ) { | ||
// Total ETH taken from sender cannot msg.value | ||
// because otherwise the deduction from remainingValue will fail | ||
remainingValue -= buyList[i] | ||
.swapInfo | ||
.pair | ||
.swapTokenForSpecificNFTs{value: buyList[i].maxCost}( | ||
buyList[i].swapInfo.nftIds, | ||
remainingValue, | ||
msg.sender, | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
// Return remaining value to sender | ||
if (remainingValue > 0) { | ||
payable(msg.sender).safeTransferETH(remainingValue); | ||
} | ||
} | ||
// Locally scope the sells | ||
{ | ||
// Do all sell swaps | ||
uint256 numSwaps = sellList.length; | ||
for (uint256 i; i < numSwaps; ) { | ||
// Do the swap for token and then update outputAmount | ||
sellList[i].swapInfo.pair.swapNFTsForToken( | ||
sellList[i].swapInfo.nftIds, | ||
sellList[i].minOutput, | ||
payable(msg.sender), | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
@dev Intended for reducing upfront capital costs, e.g. swapping NFTs and then using proceeds to buy other NFTs | ||
*/ | ||
function sellNFTsThenBuyWithETH( | ||
RobustPairSwapSpecific[] calldata buyList, | ||
RobustPairSwapSpecificForToken[] calldata sellList | ||
) external payable { | ||
uint256 outputAmount = 0; | ||
|
||
// Locally scope the sells | ||
{ | ||
// Do all sell swaps | ||
uint256 numSwaps = sellList.length; | ||
for (uint256 i; i < numSwaps; ) { | ||
// Do the swap for token and then update outputAmount | ||
outputAmount += sellList[i].swapInfo.pair.swapNFTsForToken( | ||
sellList[i].swapInfo.nftIds, | ||
sellList[i].minOutput, | ||
payable(address(this)), // Send funds here first | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
// Start with all of the ETH sent plus the ETH gained from the sells | ||
uint256 remainingValue = msg.value + outputAmount; | ||
|
||
// Locally scope the buys | ||
{ | ||
uint256 numBuys = buyList.length; | ||
|
||
// Do all buy swaps | ||
for (uint256 i; i < numBuys; ) { | ||
// Total ETH taken from sender cannot the starting remainingValue | ||
// because otherwise the deduction from remainingValue will fail | ||
remainingValue -= buyList[i] | ||
.swapInfo | ||
.pair | ||
.swapTokenForSpecificNFTs{value: buyList[i].maxCost}( | ||
buyList[i].swapInfo.nftIds, | ||
remainingValue, | ||
msg.sender, | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
// Return remaining value to sender | ||
if (remainingValue > 0) { | ||
payable(msg.sender).safeTransferETH(remainingValue); | ||
} | ||
} | ||
|
||
/** | ||
@dev Does no price checking, this is assumed to be done off-chain | ||
@param swapList The list of pairs and swap calldata | ||
@return remainingValue The unspent token amount | ||
*/ | ||
function swapETHForSpecificNFTs(RobustPairSwapSpecific[] calldata swapList) | ||
external | ||
payable | ||
returns (uint256 remainingValue) | ||
{ | ||
// Start with all of the ETH sent | ||
remainingValue = msg.value; | ||
|
||
// Do swaps | ||
uint256 numSwaps = swapList.length; | ||
for (uint256 i; i < numSwaps; ) { | ||
// Total ETH taken from sender cannot exceed inputAmount | ||
// because otherwise the deduction from remainingValue will fail | ||
remainingValue -= swapList[i] | ||
.swapInfo | ||
.pair | ||
.swapTokenForSpecificNFTs{value: swapList[i].maxCost}( | ||
swapList[i].swapInfo.nftIds, | ||
remainingValue, | ||
msg.sender, | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
|
||
// Return remaining value to sender | ||
if (remainingValue > 0) { | ||
payable(msg.sender).safeTransferETH(remainingValue); | ||
} | ||
} | ||
|
||
/** | ||
@notice Swaps NFTs for tokens, designed to be used for 1 token at a time | ||
@dev Calling with multiple tokens is permitted, BUT minOutput will be | ||
far from enough of a safety check because different tokens almost certainly have different unit prices. | ||
@param swapList The list of pairs and swap calldata | ||
@return outputAmount The number of tokens to be received | ||
*/ | ||
function swapNFTsForToken( | ||
RobustPairSwapSpecificForToken[] calldata swapList | ||
) external returns (uint256 outputAmount) { | ||
// Do swaps | ||
uint256 numSwaps = swapList.length; | ||
for (uint256 i; i < numSwaps; ) { | ||
// Do the swap for token and then update outputAmount | ||
outputAmount += swapList[i].swapInfo.pair.swapNFTsForToken( | ||
swapList[i].swapInfo.nftIds, | ||
swapList[i].minOutput, | ||
payable(msg.sender), | ||
true, | ||
msg.sender | ||
); | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
} |