Skip to content

Commit

Permalink
Merge pull request #4 from 0xSplits/move-discount-to-swapper
Browse files Browse the repository at this point in the history
add discount to swapper
  • Loading branch information
wminshew authored May 10, 2023
2 parents ad265dc + 2052260 commit 9f67781
Show file tree
Hide file tree
Showing 11 changed files with 567 additions and 155 deletions.
2 changes: 2 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
[submodule "lib/splits-oracle"]
path = lib/splits-oracle
url = https://github.com/0xSplits/splits-oracle
branch = move-discount-to-swapper
[submodule "lib/v3-periphery"]
path = lib/v3-periphery
url = https://github.com/Uniswap/v3-periphery
branch = 0.8
[submodule "lib/splits-utils"]
path = lib/splits-utils
url = https://github.com/0xsplits/splits-utils
branch = move-discount-to-swapper
2 changes: 1 addition & 1 deletion lib/forge-std
2 changes: 1 addition & 1 deletion lib/splits-utils
6 changes: 5 additions & 1 deletion src/SwapperFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ contract SwapperFactory {
address beneficiary;
address tokenToBeneficiary;
OracleParams oracleParams;
uint32 defaultScaledOfferFactor;
SwapperImpl.SetPairScaledOfferFactorParams[] pairScaledOfferFactors;
}

SwapperImpl public immutable swapperImpl;
Expand All @@ -43,7 +45,9 @@ contract SwapperFactory {
paused: params_.paused,
beneficiary: params_.beneficiary,
tokenToBeneficiary: params_.tokenToBeneficiary,
oracle: oracle
oracle: oracle,
defaultScaledOfferFactor: params_.defaultScaledOfferFactor,
pairScaledOfferFactors: params_.pairScaledOfferFactors
});
swapper.initializer(swapperInitParams);

Expand Down
95 changes: 81 additions & 14 deletions src/SwapperImpl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ pragma solidity ^0.8.17;
import {ERC20} from "solmate/tokens/ERC20.sol";
import {IOracle} from "splits-oracle/interfaces/IOracle.sol";
import {PausableImpl} from "splits-utils/PausableImpl.sol";
import {QuotePair, QuoteParams, SortedQuotePair} from "splits-utils/LibQuotes.sol";
import {SafeCastLib} from "solady/utils/SafeCastLib.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {TokenUtils} from "splits-utils/TokenUtils.sol";
import {WalletImpl} from "splits-utils/WalletImpl.sol";

import {ISwapperFlashCallback} from "./interfaces/ISwapperFlashCallback.sol";
import {PairScaledOfferFactors} from "./libraries/PairScaledOfferFactors.sol";

/// @title Swapper Implementation
/// @author 0xSplits
/// @notice A contract to trustlessly & automatically convert multi-token
/// onchain revenue into a single token
/// Please be aware, owner has _FULL CONTROL_ of the deployment.
/// @dev This contract uses a modular oracle. Be very careful to use a secure
/// oracle with sensible defaults & overrides for desired behavior. Otherwise
/// may result in catastrophic loss of funds.
/// oracle with sensible settings for the desired behavior. Insecure oracles
/// will result in catastrophic loss of funds.
/// This contract uses token = address(0) to refer to ETH.
contract SwapperImpl is WalletImpl, PausableImpl {
/// -----------------------------------------------------------------------
Expand All @@ -28,6 +30,7 @@ contract SwapperImpl is WalletImpl, PausableImpl {
using SafeTransferLib for address;
using SafeCastLib for uint256;
using TokenUtils for address;
using PairScaledOfferFactors for mapping(address => mapping(address => uint32));

/// -----------------------------------------------------------------------
/// errors
Expand All @@ -48,6 +51,13 @@ contract SwapperImpl is WalletImpl, PausableImpl {
address beneficiary;
address tokenToBeneficiary;
IOracle oracle;
uint32 defaultScaledOfferFactor;
SetPairScaledOfferFactorParams[] pairScaledOfferFactors;
}

struct SetPairScaledOfferFactorParams {
QuotePair quotePair;
uint32 scaledOfferFactor;
}

/// -----------------------------------------------------------------------
Expand All @@ -57,12 +67,14 @@ contract SwapperImpl is WalletImpl, PausableImpl {
event SetBeneficiary(address beneficiary);
event SetTokenToBeneficiary(address tokenToBeneficiary);
event SetOracle(IOracle oracle);
event SetDefaultScaledOfferFactor(uint32 defaultScaledOfferFactor);
event SetPairScaledOfferFactors(SetPairScaledOfferFactorParams[] params);

event ReceiveETH(uint256 amount);
event Payback(address indexed payer, uint256 amount);
event Flash(
address indexed trader,
IOracle.QuoteParams[] quoteParams,
QuoteParams[] quoteParams,
address tokenToBeneficiary,
uint256[] amountsToBeneficiary,
uint256 excessToBeneficiary
Expand All @@ -78,6 +90,9 @@ contract SwapperImpl is WalletImpl, PausableImpl {

address public immutable swapperFactory;

/// @dev percentages measured in hundredths of basis points
uint32 internal constant PERCENTAGE_SCALE = 100_00_00; // = 100%

/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
Expand All @@ -102,19 +117,31 @@ contract SwapperImpl is WalletImpl, PausableImpl {
uint96 internal $_payback;
/// 12 bytes

/// slot 2 - 12 bytes free
/// slot 2 - 8 bytes free

/// token type to send beneficiary
/// @dev 0x0 used for ETH
address internal $tokenToBeneficiary;
/// 20 bytes

/// default price scaling factor
/// @dev PERCENTAGE_SCALE = 1e6 = 100_00_00 = 100% = no discount or premium
/// 99_00_00 = 99% = 1% discount to oracle; 101_00_00 = 101% = 1% premium to oracle
/// 4 bytes
uint32 internal $defaultScaledOfferFactor;

/// slot 3 - 12 bytes free

/// price oracle for flash
IOracle internal $oracle;
/// 20 bytes

/// slot 4 - 0 bytes free

/// scaledOfferFactors for specific quote pairs
/// 32 bytes
mapping(address => mapping(address => uint32)) internal $_pairScaledOfferFactors;

/// -----------------------------------------------------------------------
/// constructor & initializer
/// -----------------------------------------------------------------------
Expand All @@ -133,6 +160,9 @@ contract SwapperImpl is WalletImpl, PausableImpl {
$beneficiary = params_.beneficiary;
$tokenToBeneficiary = params_.tokenToBeneficiary;
$oracle = params_.oracle;
$defaultScaledOfferFactor = params_.defaultScaledOfferFactor;

$_pairScaledOfferFactors._set(params_.pairScaledOfferFactors);
}

/// -----------------------------------------------------------------------
Expand Down Expand Up @@ -165,6 +195,18 @@ contract SwapperImpl is WalletImpl, PausableImpl {
emit SetOracle(oracle_);
}

/// set defaultScaledOfferFactor
function setDefaultScaledOfferFactor(uint32 defaultScaledOfferFactor_) external onlyOwner {
$defaultScaledOfferFactor = defaultScaledOfferFactor_;
emit SetDefaultScaledOfferFactor(defaultScaledOfferFactor_);
}

/// set pair scaled offer factors
function setPairScaledOfferFactors(SetPairScaledOfferFactorParams[] calldata params_) external onlyOwner {
$_pairScaledOfferFactors._set(params_);
emit SetPairScaledOfferFactors(params_);
}

/// -----------------------------------------------------------------------
/// functions - public & external - view
/// -----------------------------------------------------------------------
Expand All @@ -181,6 +223,26 @@ contract SwapperImpl is WalletImpl, PausableImpl {
return $oracle;
}

function defaultScaledOfferFactor() external view returns (uint32) {
return $defaultScaledOfferFactor;
}

/// get pair scaled offer factors for an array of quote pairs
function getPairScaledOfferFactors(QuotePair[] calldata quotePairs_)
external
view
returns (uint32[] memory pairScaledOfferFactors)
{
uint256 length = quotePairs_.length;
pairScaledOfferFactors = new uint32[](length);
for (uint256 i; i < length;) {
pairScaledOfferFactors[i] = $_pairScaledOfferFactors._get(quotePairs_[i]);
unchecked {
++i;
}
}
}

/// -----------------------------------------------------------------------
/// functions - public & external - permissionless
/// -----------------------------------------------------------------------
Expand All @@ -200,11 +262,7 @@ contract SwapperImpl is WalletImpl, PausableImpl {
}

/// allow third parties to withdraw tokens in return for sending tokenToBeneficiary to beneficiary
function flash(IOracle.QuoteParams[] calldata quoteParams_, bytes calldata callbackData_)
external
payable
pausable
{
function flash(QuoteParams[] calldata quoteParams_, bytes calldata callbackData_) external pausable {
address _tokenToBeneficiary = $tokenToBeneficiary;
(uint256 amountToBeneficiary, uint256[] memory amountsToBeneficiary) =
_transferToTrader(_tokenToBeneficiary, quoteParams_);
Expand All @@ -224,18 +282,20 @@ contract SwapperImpl is WalletImpl, PausableImpl {
/// functions - private & internal
/// -----------------------------------------------------------------------

function _transferToTrader(address tokenToBeneficiary_, IOracle.QuoteParams[] calldata quoteParams_)
function _transferToTrader(address tokenToBeneficiary_, QuoteParams[] calldata quoteParams_)
internal
returns (uint256 amountToBeneficiary, uint256[] memory amountsToBeneficiary)
{
amountsToBeneficiary = $oracle.getQuoteAmounts(quoteParams_);
uint256[] memory unscaledAmountsToBeneficiary = $oracle.getQuoteAmounts(quoteParams_);
uint256 length = quoteParams_.length;
if (amountsToBeneficiary.length != length) revert Invalid_AmountsToBeneficiary();
if (unscaledAmountsToBeneficiary.length != length) revert Invalid_AmountsToBeneficiary();

amountsToBeneficiary = new uint256[](length);
uint256 scaledAmountToBeneficiary;
uint128 amountToTrader;
address tokenToTrader;
for (uint256 i; i < length;) {
IOracle.QuoteParams calldata qp = quoteParams_[i];
QuoteParams calldata qp = quoteParams_[i];

if (tokenToBeneficiary_ != qp.quotePair.quote) revert Invalid_QuoteToken();
tokenToTrader = qp.quotePair.base;
Expand All @@ -245,7 +305,14 @@ contract SwapperImpl is WalletImpl, PausableImpl {
revert InsufficientFunds_InContract();
}

amountToBeneficiary += amountsToBeneficiary[i];
uint32 scaledOfferFactor = $_pairScaledOfferFactors._get(qp.quotePair._sort());
if (scaledOfferFactor == 0) {
scaledOfferFactor = $defaultScaledOfferFactor;
}

scaledAmountToBeneficiary = unscaledAmountsToBeneficiary[i] * scaledOfferFactor / PERCENTAGE_SCALE;
amountsToBeneficiary[i] = scaledAmountToBeneficiary;
amountToBeneficiary += scaledAmountToBeneficiary;
tokenToTrader._safeTransfer(msg.sender, amountToTrader);

unchecked {
Expand Down
3 changes: 2 additions & 1 deletion src/integrations/UniV3Swap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ERC20} from "solmate/tokens/ERC20.sol";
import {IOracle} from "splits-oracle/interfaces/IOracle.sol";
import {ISwapRouter} from "v3-periphery/interfaces/ISwapRouter.sol";
import {IWETH9} from "splits-utils/interfaces/external/IWETH9.sol";
import {QuoteParams} from "splits-utils/LibQuotes.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {TokenUtils} from "splits-utils/TokenUtils.sol";

Expand All @@ -24,7 +25,7 @@ contract UniV3Swap is ISwapperFlashCallback {
error InsufficientFunds();

struct InitFlashParams {
IOracle.QuoteParams[] quoteParams;
QuoteParams[] quoteParams;
FlashCallbackData flashCallbackData;
}

Expand Down
52 changes: 52 additions & 0 deletions src/libraries/PairScaledOfferFactors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

import {QuotePair, SortedQuotePair} from "splits-utils/LibQuotes.sol";

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

/// @title PairScaledOfferFactor Library
/// @author 0xSplits
/// @notice Setters & getters for quote pairs' scaledOfferFactors
library PairScaledOfferFactors {
/// set pairs' scaled offer factors
function _set(
mapping(address => mapping(address => uint32)) storage self,
SwapperImpl.SetPairScaledOfferFactorParams[] calldata params_
) internal {
uint256 length = params_.length;
for (uint256 i; i < length;) {
_set(self, params_[i]);
unchecked {
++i;
}
}
}

/// set pair's scaled offer factor
function _set(
mapping(address => mapping(address => uint32)) storage self,
SwapperImpl.SetPairScaledOfferFactorParams calldata params_
) internal {
SortedQuotePair memory sqp = params_.quotePair._sort();
self[sqp.token0][sqp.token1] = params_.scaledOfferFactor;
}

/// get pair's scaled offer factor
function _get(mapping(address => mapping(address => uint32)) storage self, QuotePair calldata quotePair_)
internal
view
returns (uint32)
{
return _get(self, quotePair_._sort());
}

/// get pair's scaled offer factor
function _get(mapping(address => mapping(address => uint32)) storage self, SortedQuotePair memory sqp_)
internal
view
returns (uint32)
{
return self[sqp_.token0][sqp_.token1];
}
}
Loading

0 comments on commit 9f67781

Please sign in to comment.