// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
address constant ADDRESS_ZERO = address(0);
library AddressUtils {
function _isEmpty(address addr) internal pure returns (bool) {
return (addr == ADDRESS_ZERO);
}
function _isNotEmpty(address addr) internal pure returns (bool) {
return (addr != ADDRESS_ZERO);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
using {_sort} for ConvertedQuotePair global;
struct ConvertedQuotePair {
address cBase;
address cQuote;
}
struct SortedConvertedQuotePair {
address cToken0;
address cToken1;
}
function _sort(ConvertedQuotePair memory cqp) pure returns (SortedConvertedQuotePair memory) {
return (cqp.cBase > cqp.cQuote)
? SortedConvertedQuotePair({cToken0: cqp.cQuote, cToken1: cqp.cBase})
: SortedConvertedQuotePair({cToken0: cqp.cBase, cToken1: cqp.cQuote});
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {AddressUtils, ADDRESS_ZERO} from "splits-utils/AddressUtils.sol";
import {IOracle} from "splits-oracle/interfaces/IOracle.sol";
import {OracleImpl} from "splits-oracle/OracleImpl.sol";
import {ISplitMain} from "splits-utils/interfaces/ISplitMain.sol";
import {LibRecipients, PackedRecipient} from "splits-utils/LibRecipients.sol";
import {OracleParams} from "splits-oracle/peripherals/OracleParams.sol";
import {PassThroughWalletImpl} from "splits-pass-through-wallet/PassThroughWalletImpl.sol";
import {PassThroughWalletFactory} from "splits-pass-through-wallet/PassThroughWalletFactory.sol";
import {SwapperImpl} from "splits-swapper/SwapperImpl.sol";
import {SwapperFactory} from "splits-swapper/SwapperFactory.sol";
import {WalletImpl} from "splits-utils/WalletImpl.sol";
/// @title Diversifier Factory
/// @author 0xSplits
/// @notice Factory for creating Diversifiers.
/// A Diversifier is a PassThroughWallet on top of a Split on top of one or
/// more Swappers. With this structure, Diversifiers trustlessly & automatically
/// diversify onchain revenue.
/// Please be aware, owner has _FULL CONTROL_ of the deployment.
/// @dev This contract uses token = address(0) to refer to ETH.
contract DiversifierFactory {
using AddressUtils for address;
using LibRecipients for PackedRecipient[];
using {LibRecipients._pack} for address;
event CreateDiversifier(address indexed diversifier);
struct CreateDiversifierParams {
address owner;
bool paused;
OracleParams oracleParams;
RecipientParams[] recipientParams;
}
struct RecipientParams {
address account;
CreateSwapperParams createSwapperParams;
uint32 percentAllocation;
}
struct CreateSwapperParams {
address beneficiary;
address tokenToBeneficiary;
uint32 defaultScaledOfferFactor;
SwapperImpl.SetPairScaledOfferFactorParams[] pairScaledOfferFactors;
}
ISplitMain public immutable splitMain;
SwapperFactory public immutable swapperFactory;
PassThroughWalletFactory public immutable passThroughWalletFactory;
constructor(
ISplitMain splitMain_,
SwapperFactory swapperFactory_,
PassThroughWalletFactory passThroughWalletFactory_
) {
splitMain = splitMain_;
swapperFactory = swapperFactory_;
passThroughWalletFactory = passThroughWalletFactory_;
}
/// -----------------------------------------------------------------------
/// functions - public & external
/// -----------------------------------------------------------------------
function createDiversifier(CreateDiversifierParams calldata params_) external returns (address diversifier) {
// create pass-through wallet w {this} as owner & no passThrough
PassThroughWalletImpl passThroughWallet = passThroughWalletFactory.createPassThroughWallet(
PassThroughWalletImpl.InitParams({owner: address(this), paused: params_.paused, passThrough: ADDRESS_ZERO})
);
diversifier = address(passThroughWallet);
// parse oracle params for swapper-recipients
OracleImpl oracle = _parseOracleParams(diversifier, params_.oracleParams);
// create split w diversifier (pass-through wallet) as controller
(address[] memory sortedAccounts, uint32[] memory sortedPercentAllocations) =
_parseRecipientParams(diversifier, oracle, params_.recipientParams);
address passThroughSplit = splitMain.createSplit({
accounts: sortedAccounts,
percentAllocations: sortedPercentAllocations,
distributorFee: 0,
controller: diversifier
});
// set split address as passThrough & transfer ownership from factory
passThroughWallet.setPassThrough(passThroughSplit);
passThroughWallet.transferOwnership(params_.owner);
emit CreateDiversifier(diversifier);
}
/// -----------------------------------------------------------------------
/// functions - private & internal
/// -----------------------------------------------------------------------
function _parseRecipientParams(
address diversifier_,
OracleImpl oracle_,
RecipientParams[] calldata recipientParams_
) internal returns (address[] memory, uint32[] memory) {
OracleParams memory swapperOracleParams;
swapperOracleParams.oracle = oracle_;
// parse recipient params
uint256 length = recipientParams_.length;
PackedRecipient[] memory packedRecipients = new PackedRecipient[](length);
for (uint256 i; i < length;) {
RecipientParams calldata recipientParams = recipientParams_[i];
// use recipient account or, if empty, create a new swapper owned by diversifier using oracle & other args
address account = (recipientParams.account._isNotEmpty())
? recipientParams.account
: address(
swapperFactory.createSwapper(
SwapperFactory.CreateSwapperParams({
owner: diversifier_,
paused: false,
beneficiary: recipientParams.createSwapperParams.beneficiary,
tokenToBeneficiary: recipientParams.createSwapperParams.tokenToBeneficiary,
oracleParams: swapperOracleParams,
defaultScaledOfferFactor: recipientParams.createSwapperParams.defaultScaledOfferFactor,
pairScaledOfferFactors: recipientParams.createSwapperParams.pairScaledOfferFactors
})
)
);
packedRecipients[i] = account._pack(recipientParams.percentAllocation);
unchecked {
++i;
}
}
packedRecipients._sortInPlace();
return packedRecipients._unpackAccountsInPlace();
}
function _parseOracleParams(address diversifier_, OracleParams calldata oracleParams_)
internal
returns (OracleImpl oracle)
{
oracle = OracleImpl(address(oracleParams_._parseIntoOracle()));
// if oracle is new & {this} is owner, transfer ownership to diversifier
if ((address(oracleParams_.oracle)._isEmpty()) && oracle.owner() == address(this)) {
oracle.transferOwnership(diversifier_);
}
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 amount
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] amounts
);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
/*//////////////////////////////////////////////////////////////
ERC1155 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address => mapping(uint256 => uint256)) public balanceOf;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
METADATA LOGIC
//////////////////////////////////////////////////////////////*/
function uri(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC1155 LOGIC
//////////////////////////////////////////////////////////////*/
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) public virtual {
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, from, to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) public virtual {
require(ids.length == amounts.length, "LENGTH_MISMATCH");
require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");
// Storing these outside the loop saves ~15 gas per iteration.
uint256 id;
uint256 amount;
for (uint256 i = 0; i < ids.length; ) {
id = ids[i];
amount = amounts[i];
balanceOf[from][id] -= amount;
balanceOf[to][id] += amount;
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)
public
view
virtual
returns (uint256[] memory balances)
{
require(owners.length == ids.length, "LENGTH_MISMATCH");
balances = new uint256[](owners.length);
// Unchecked because the only math done is incrementing
// the array index counter which cannot possibly overflow.
unchecked {
for (uint256 i = 0; i < owners.length; ++i) {
balances[i] = balanceOf[owners[i]][ids[i]];
}
}
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
balanceOf[to][id] += amount;
emit TransferSingle(msg.sender, address(0), to, id, amount);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
ERC1155TokenReceiver.onERC1155Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchMint(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[to][ids[i]] += amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, address(0), to, ids, amounts);
require(
to.code.length == 0
? to != address(0)
: ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
ERC1155TokenReceiver.onERC1155BatchReceived.selector,
"UNSAFE_RECIPIENT"
);
}
function _batchBurn(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal virtual {
uint256 idsLength = ids.length; // Saves MLOADs.
require(idsLength == amounts.length, "LENGTH_MISMATCH");
for (uint256 i = 0; i < idsLength; ) {
balanceOf[from][ids[i]] -= amounts[i];
// An array can't have a total length
// larger than the max uint256 value.
unchecked {
++i;
}
}
emit TransferBatch(msg.sender, from, address(0), ids, amounts);
}
function _burn(
address from,
uint256 id,
uint256 amount
) internal virtual {
balanceOf[from][id] -= amount;
emit TransferSingle(msg.sender, from, address(0), id, amount);
}
}
/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiver {
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC1155TokenReceiver.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
) external virtual returns (bytes4) {
return ERC1155TokenReceiver.onERC1155BatchReceived.selector;
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 indexed id);
event Approval(address indexed owner, address indexed spender, uint256 indexed id);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE/LOGIC
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
function tokenURI(uint256 id) public view virtual returns (string memory);
/*//////////////////////////////////////////////////////////////
ERC721 BALANCE/OWNER STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) internal _ownerOf;
mapping(address => uint256) internal _balanceOf;
function ownerOf(uint256 id) public view virtual returns (address owner) {
require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
}
function balanceOf(address owner) public view virtual returns (uint256) {
require(owner != address(0), "ZERO_ADDRESS");
return _balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
ERC721 APPROVAL STORAGE
//////////////////////////////////////////////////////////////*/
mapping(uint256 => address) public getApproved;
mapping(address => mapping(address => bool)) public isApprovedForAll;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
}
/*//////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 id) public virtual {
address owner = _ownerOf[id];
require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll(address operator, bool approved) public virtual {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function transferFrom(
address from,
address to,
uint256 id
) public virtual {
require(from == _ownerOf[id], "WRONG_FROM");
require(to != address(0), "INVALID_RECIPIENT");
require(
msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
"NOT_AUTHORIZED"
);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint256 id
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public virtual {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
/*//////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////*/
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 id) internal virtual {
require(to != address(0), "INVALID_RECIPIENT");
require(_ownerOf[id] == address(0), "ALREADY_MINTED");
// Counter overflow is incredibly unrealistic.
unchecked {
_balanceOf[to]++;
}
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint256 id) internal virtual {
address owner = _ownerOf[id];
require(owner != address(0), "NOT_MINTED");
// Ownership check above ensures no underflow.
unchecked {
_balanceOf[owner]--;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address(0), id);
}
/*//////////////////////////////////////////////////////////////
INTERNAL SAFE MINT LOGIC
//////////////////////////////////////////////////////////////*/
function _safeMint(address to, uint256 id) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
function _safeMint(
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require(
to.code.length == 0 ||
ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
ERC721TokenReceiver.onERC721Received.selector,
"UNSAFE_RECIPIENT"
);
}
}
/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external virtual returns (bytes4) {
return ERC721TokenReceiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {QuoteParams} from "splits-utils/LibQuotes.sol";
/// @title Oracle Interface
/// @author 0xSplits
interface IOracle {
function getQuoteAmounts(QuoteParams[] calldata quoteParams_) external view returns (uint256[] memory);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {IOracle} from "./IOracle.sol";
/// @title Oracle factory interface
interface IOracleFactory {
function createOracle(bytes calldata data_) external returns (IOracle);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
interface ISplitMain {
/// -----------------------------------------------------------------------
/// events
/// -----------------------------------------------------------------------
event CreateSplit(
address indexed split,
address[] accounts,
uint32[] percentAllocations,
uint32 distributorFee,
address controller
);
event UpdateSplit(address indexed split, address[] accounts, uint32[] percentAllocations, uint32 distributorFee);
event InitiateControlTransfer(address indexed split, address indexed newPotentialController);
event CancelControlTransfer(address indexed split);
event ControlTransfer(address indexed split, address indexed previousController, address indexed newController);
event DistributeETH(address indexed split, uint256 amount, address indexed distributorAddress);
event DistributeERC20(
address indexed split, address indexed token, uint256 amount, address indexed distributorAddress
);
event Withdrawal(address indexed account, uint256 ethAmount, address[] tokens, uint256[] tokenAmounts);
/// -----------------------------------------------------------------------
/// functions
/// -----------------------------------------------------------------------
function walletImplementation() external returns (address);
function createSplit(
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee,
address controller
) external returns (address);
function predictImmutableSplitAddress(
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee
) external view returns (address);
function updateSplit(
address split,
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee
) external;
function transferControl(address split, address newController) external;
function cancelControlTransfer(address split) external;
function acceptControl(address split) external;
function makeSplitImmutable(address split) external;
function distributeETH(
address split,
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee,
address distributorAddress
) external;
function updateAndDistributeETH(
address split,
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee,
address distributorAddress
) external;
function distributeERC20(
address split,
address token,
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee,
address distributorAddress
) external;
function updateAndDistributeERC20(
address split,
address token,
address[] calldata accounts,
uint32[] calldata percentAllocations,
uint32 distributorFee,
address distributorAddress
) external;
function withdraw(address account, uint256 withdrawETH, address[] calldata tokens) external;
function getHash(address split) external view returns (bytes32);
function getController(address split) external view returns (address);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
/// @title Swapper Flash Callback
/// @author 0xSplits
/// @notice Callback for `Swapper#flash`
/// @dev any contract that calls `Swapper#flash` must implement this interface.
/// Inspired by IUniswapV3FlashCallback
interface ISwapperFlashCallback {
/// Called to `msg.sender` in `Swapper#flash` after transferring `quoteParams`.
/// @dev In the implementation you must complete the flash swap.
/// If `tokenToBeneficiary` is ETH, you must deposit `amountToBeneficiary` via `Swapper#payback`.
/// If `tokenToBeneficiary` is an ERC20, you must use approve Swapper to transfer `amountToBeneficiary`.
/// The caller of this method will use token = address(0) to refer to ETH.
/// @param tokenToBeneficiary The token due to the `beneficiary` by the end of `#flash`
/// @param amountToBeneficiary The amount of `tokenToBeneficiary` due to the `beneficiary` by the end of `#flash`
/// @param data Any `data` passed through by `msg.sender` of `Swapper#flash`
function swapperFlashCallback(address tokenToBeneficiary, uint256 amountToBeneficiary, bytes calldata data)
external;
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
/// @title Modified minimal proxy
/// @author 0xSplits
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol)
/// @dev Modified minimal proxy includes a `receive()` method that emits the
/// `ReceiveETH(uint256)` event to skip `DELEGATECALL` when there is no calldata.
/// Enables us to accept hard gas-capped `sends` & `transfers` for maximum backwards
/// composability.
library LibClone {
error DeploymentFailed();
uint256 private constant FREE_PTR = 0x40;
uint256 private constant ZERO_PTR = 0x60;
/// @dev Deploys a modified minimal proxy of `implementation`
function clone(address implementation) internal returns (address instance) {
assembly ("memory-safe") {
/**
* --------------------------------------------------------------------------+
* CREATION (9 bytes - 0x09) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* 60 runSize | PUSH1 runSize | r | |
* 3d | RETURNDATASIZE | 0 r | |
* 81 | DUP2 | r 0 r | |
* 60 offset | PUSH1 offset | o r 0 r | |
* 3d | RETURNDATASIZE | 0 o r 0 r | |
* 39 | CODECOPY | 0 r | [0..runSize): runtime code |
* f3 | RETURN | | [0..runSize): runtime code |
* --------------------------------------------------------------------------|
* RUNTIME (89 bytes - 0x59) |
* --------------------------------------------------------------------------|
* Opcode | Mnemonic | Stack | Memory |
* --------------------------------------------------------------------------|
* |
* 36 | CALLDATASIZE | cds | |
* 60 0x2c | PUSH1 0x2c | 0x2c cds | |
* 57 | JUMPI | | |
* 34 | CALLVALUE | cv | |
* 3d | RETURNDATASIZE | 0 cv | |
* 52 | MSTORE | | [0..0x20): callvalue |
* 7f sig | PUSH32 0x9e.. | sig | [0..0x20): callvalue |
* 59 | MSIZE | 0x20 sig | [0..0x20): callvalue |
* 3d | RETURNDATASIZE | 0 0x20 sig | [0..0x20): callvalue |
* a1 | LOG1 | | [0..0x20): callvalue |
* 00 | STOP | | [0..0x20): callvalue |
* 5b | JUMPDEST | | |
* |
* ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | 0 | |
* 3d | RETURNDATASIZE | 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 0 0 | |
* |
* ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | |
* 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | |
* 37 | CALLDATACOPY | 0 0 0 0 | [0..cds): calldata |
* |
* ::: delegate call to the implementation contract :::::::::::::::::::::::: |
* 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata |
* 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata |
* 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata |
* f4 | DELEGATECALL | success 0 0 | [0..cds): calldata |
* |
* ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: |
* 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata |
* 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata |
* 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata |
* 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata |
* 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata |
* |
* 60 0x57 | PUSH1 0x57 | 0x57 success 0 rds | [0..rds): returndata |
* 57 | JUMPI | 0 rds | [0..rds): returndata |
* |
* ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* fd | REVERT | | [0..rds): returndata |
* |
* ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
* 5b | JUMPDEST | 0 rds | [0..rds): returndata |
* f3 | RETURN | | [0..rds): returndata |
* --------------------------------------------------------------------------+
* TOTAL INIT (98 bytes - 0x62) |
* --------------------------------------------------------------------------|
*/
// save free pointer
let fp := mload(FREE_PTR)
mstore(0x51, 0x5af43d3d93803e605757fd5bf3) // 13 bytes
mstore(0x44, implementation) // 20 bytes
mstore(0x30, 0x593da1005b3d3d3d3d363d3d37363d73) // 16 bytes
// `keccak256("ReceiveETH(uint256)")`
mstore(0x20, 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff) // 32 bytes
mstore(0x00, 0x60593d8160093d39f336602c57343d527f) // 17 bytes
// total: 113 bytes = 0x71
// offset: 15 bytes = 0x0f
// data: 98 bytes = 0x62
instance := create(0, 0x0f, 0x71)
// restore free pointer, zero slot
mstore(FREE_PTR, fp)
mstore(ZERO_PTR, 0)
// If `instance` is zero, revert.
if iszero(instance) {
// Store the function selector of `DeploymentFailed()`.
mstore(0x00, 0x30116425)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {ConvertedQuotePair, SortedConvertedQuotePair} from "./ConvertedQuotePair.sol";
using {_sort, _convert, _convertAndSort} for QuotePair global;
struct QuoteParams {
QuotePair quotePair;
uint128 baseAmount;
bytes data;
}
struct QuotePair {
address base;
address quote;
}
struct SortedQuotePair {
address token0;
address token1;
}
function _sort(QuotePair memory qp) pure returns (SortedQuotePair memory) {
return (qp.base > qp.quote)
? SortedQuotePair({token0: qp.quote, token1: qp.base})
: SortedQuotePair({token0: qp.base, token1: qp.quote});
}
function _convert(QuotePair calldata qp, function (address) internal view returns (address) convert)
view
returns (ConvertedQuotePair memory)
{
return ConvertedQuotePair({cBase: convert(qp.base), cQuote: convert(qp.quote)});
}
function _convertAndSort(QuotePair calldata qp, function (address) internal view returns (address) convert)
view
returns (SortedConvertedQuotePair memory)
{
return _convert(qp, convert)._sort();
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {LibSort} from "solady/utils/LibSort.sol";
type PackedRecipient is uint256;
// Library for efficiently sorting splits' recipients onchain
library LibRecipients {
using LibSort for uint256[];
error InvalidRecipients_ArrayLengthMismatch();
uint256 constant UINT96_BITS = 96;
/// sorts accounts_; re-orders percentAllocations_ to maintain relation to accounts_
/// @dev accounts_ and percentAllocations_ must have the same length
function _sortRecipients(address[] calldata accounts_, uint32[] calldata percentAllocations_)
internal
pure
returns (address[] memory, uint32[] memory)
{
PackedRecipient[] memory packedRecipients = _packRecipients(accounts_, percentAllocations_);
_sortInPlace(packedRecipients);
return _unpackAccountsInPlace(packedRecipients);
}
/// sorts accounts_ in-place; re-orders percentAllocations_ in-place to maintain relation to accounts_
/// @dev accounts_ and percentAllocations_ must have the same length
function _sortRecipientsInPlace(address[] memory accounts_, uint32[] memory percentAllocations_) internal pure {
PackedRecipient[] memory packedRecipients = _packRecipientsIntoAccounts(accounts_, percentAllocations_);
_sortInPlace(packedRecipients);
_unpackAccountsAndAllocationsInPlace(packedRecipients, percentAllocations_);
}
/// packs accounts_ and percentAllocations_ into a single memory array
/// @dev accounts_ and percentAllocations_ must have the same length
function _packRecipients(address[] calldata accounts_, uint32[] calldata percentAllocations_)
internal
pure
returns (PackedRecipient[] memory packedRecipients)
{
if (accounts_.length != percentAllocations_.length) revert InvalidRecipients_ArrayLengthMismatch();
uint256 length = accounts_.length;
packedRecipients = new PackedRecipient[](length);
for (uint256 i; i < length;) {
packedRecipients[i] = _pack(accounts_[i], percentAllocations_[i]);
unchecked {
++i;
}
}
}
/// @dev packs accounts_ & percentAllocations_ into the memory allocated to accounts_ & returns a typed pointer
/// @dev accounts_ and percentAllocations_ must have the same length
function _packRecipientsIntoAccounts(address[] memory accounts_, uint32[] memory percentAllocations_)
internal
pure
returns (PackedRecipient[] memory packedRecipients)
{
if (accounts_.length != percentAllocations_.length) revert InvalidRecipients_ArrayLengthMismatch();
assembly ("memory-safe") {
packedRecipients := accounts_
}
uint256 length = accounts_.length;
for (uint256 i; i < length;) {
packedRecipients[i] = _pack(accounts_[i], percentAllocations_[i]);
unchecked {
++i;
}
}
}
/// @dev sorts in-place
function _sortInPlace(PackedRecipient[] memory packedRecipients_) internal pure {
uint256[] memory uintPackedRecipients;
assembly ("memory-safe") {
uintPackedRecipients := packedRecipients_
}
uintPackedRecipients.sort();
}
/// @dev unpacks in-place (re-uses packedRecipients_ memory for accounts)
function _unpackAccountsInPlace(PackedRecipient[] memory packedRecipients_)
internal
pure
returns (address[] memory accounts, uint32[] memory percentAllocations)
{
uint256 length = packedRecipients_.length;
assembly ("memory-safe") {
accounts := packedRecipients_
}
percentAllocations = new uint32[](length);
for (uint256 i; i < length;) {
(accounts[i], percentAllocations[i]) = _unpack(packedRecipients_[i]);
unchecked {
++i;
}
}
}
/// @dev unpacks in-place (re-uses packedRecipients_ memory for accounts & percentAllocations_ memory for itself)
/// @dev packedRecipients_ and percentAllocations_ must have the same length
function _unpackAccountsAndAllocationsInPlace(
PackedRecipient[] memory packedRecipients_,
uint32[] memory percentAllocations_
) internal pure {
if (packedRecipients_.length != percentAllocations_.length) revert InvalidRecipients_ArrayLengthMismatch();
address[] memory accounts;
assembly ("memory-safe") {
accounts := packedRecipients_
}
uint256 length = packedRecipients_.length;
for (uint256 i; i < length;) {
(accounts[i], percentAllocations_[i]) = _unpack(packedRecipients_[i]);
unchecked {
++i;
}
}
}
function _pack(address account_, uint32 percentAllocation_) internal pure returns (PackedRecipient) {
return PackedRecipient.wrap((uint256(uint160(account_)) << UINT96_BITS) | percentAllocation_);
}
function _unpack(PackedRecipient packedRecipient_)
internal
pure
returns (address account, uint32 percentAllocation)
{
uint256 packedRecipient = PackedRecipient.unwrap(packedRecipient_);
percentAllocation = uint32(packedRecipient);
account = address(uint160(packedRecipient >> UINT96_BITS));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Optimized sorts and operations for sorted arrays.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Sort.sol)
library LibSort {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INSERTION SORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on small arrays (32 or lesser elements).
// - Faster on almost sorted arrays.
// - Smaller bytecode.
// - May be suitable for view functions intended for off-chain querying.
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let n := mload(a) // Length of `a`.
mstore(a, 0) // For insertion sort's inner loop to terminate.
let h := add(a, shl(5, n)) // High slot.
let s := 0x20
let w := not(0x1f)
for { let i := add(a, s) } 1 {} {
i := add(i, s)
if gt(i, h) { break }
let k := mload(i) // Key.
let j := add(i, w) // The slot before the current slot.
let v := mload(j) // The value of `j`.
if iszero(gt(v, k)) { continue }
for {} 1 {} {
mstore(add(j, s), v)
j := add(j, w) // `sub(j, 0x20)`.
v := mload(j)
if iszero(gt(v, k)) { break }
}
mstore(add(j, s), k)
}
mstore(a, n) // Restore the length of `a`.
}
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(int256[] memory a) internal pure {
_convertTwosComplement(a);
insertionSort(_toUints(a));
_convertTwosComplement(a);
}
/// @dev Sorts the array in-place with insertion sort.
function insertionSort(address[] memory a) internal pure {
insertionSort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTRO-QUICKSORT */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// - Faster on larger arrays (more than 32 elements).
// - Robust performance.
// - Larger bytecode.
/// @dev Sorts the array in-place with intro-quicksort.
function sort(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
let w := not(0x1f)
let s := 0x20
let n := mload(a) // Length of `a`.
mstore(a, 0) // For insertion sort's inner loop to terminate.
// Let the stack be the start of the free memory.
let stack := mload(0x40)
for {} iszero(lt(n, 2)) {} {
// Push `l` and `h` to the stack.
// The `shl` by 5 is equivalent to multiplying by `0x20`.
let l := add(a, s)
let h := add(a, shl(5, n))
let j := l
// forgefmt: disable-next-item
for {} iszero(or(eq(j, h), gt(mload(j), mload(add(j, s))))) {} {
j := add(j, s)
}
// If the array is already sorted.
if eq(j, h) { break }
j := h
// forgefmt: disable-next-item
for {} iszero(gt(mload(j), mload(add(j, w)))) {} {
j := add(j, w) // `sub(j, 0x20)`.
}
// If the array is reversed sorted.
if eq(j, l) {
for {} 1 {} {
let t := mload(l)
mstore(l, mload(h))
mstore(h, t)
h := add(h, w) // `sub(h, 0x20)`.
l := add(l, s)
if iszero(lt(l, h)) { break }
}
break
}
// Push `l` and `h` onto the stack.
mstore(stack, l)
mstore(add(stack, s), h)
stack := add(stack, 0x40)
break
}
for { let stackBottom := mload(0x40) } iszero(eq(stack, stackBottom)) {} {
// Pop `l` and `h` from the stack.
stack := sub(stack, 0x40)
let l := mload(stack)
let h := mload(add(stack, s))
// Do insertion sort if `h - l <= 0x20 * 12`.
// Threshold is fine-tuned via trial and error.
if iszero(gt(sub(h, l), 0x180)) {
// Hardcode sort the first 2 elements.
let i := add(l, s)
if iszero(lt(mload(l), mload(i))) {
let t := mload(i)
mstore(i, mload(l))
mstore(l, t)
}
for {} 1 {} {
i := add(i, s)
if gt(i, h) { break }
let k := mload(i) // Key.
let j := add(i, w) // The slot before the current slot.
let v := mload(j) // The value of `j`.
if iszero(gt(v, k)) { continue }
for {} 1 {} {
mstore(add(j, s), v)
j := add(j, w)
v := mload(j)
if iszero(gt(v, k)) { break }
}
mstore(add(j, s), k)
}
continue
}
// Pivot slot is the average of `l` and `h`.
let p := add(shl(5, shr(6, add(l, h))), and(31, l))
// Median of 3 with sorting.
{
let e0 := mload(l)
let e2 := mload(h)
let e1 := mload(p)
if iszero(lt(e0, e1)) {
let t := e0
e0 := e1
e1 := t
}
if iszero(lt(e0, e2)) {
let t := e0
e0 := e2
e2 := t
}
if iszero(lt(e1, e2)) {
let t := e1
e1 := e2
e2 := t
}
mstore(p, e1)
mstore(h, e2)
mstore(l, e0)
}
// Hoare's partition.
{
// The value of the pivot slot.
let x := mload(p)
p := h
for { let i := l } 1 {} {
for {} 1 {} {
i := add(i, s)
if iszero(gt(x, mload(i))) { break }
}
let j := p
for {} 1 {} {
j := add(j, w)
if iszero(lt(x, mload(j))) { break }
}
p := j
if iszero(lt(i, p)) { break }
// Swap slots `i` and `p`.
let t := mload(i)
mstore(i, mload(p))
mstore(p, t)
}
}
// If slice on right of pivot is non-empty, push onto stack.
{
mstore(stack, add(p, s))
// Skip `mstore(add(stack, 0x20), h)`, as it is already on the stack.
stack := add(stack, shl(6, lt(add(p, s), h)))
}
// If slice on left of pivot is non-empty, push onto stack.
{
mstore(stack, l)
mstore(add(stack, s), p)
stack := add(stack, shl(6, gt(p, l)))
}
}
mstore(a, n) // Restore the length of `a`.
}
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(int256[] memory a) internal pure {
_convertTwosComplement(a);
sort(_toUints(a));
_convertTwosComplement(a);
}
/// @dev Sorts the array in-place with intro-quicksort.
function sort(address[] memory a) internal pure {
sort(_toUints(a));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* OTHER USEFUL OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// For performance, the `uniquifySorted` methods will not revert if the
// array is not sorted -- it will simply remove consecutive duplicate elements.
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
// If the length of `a` is greater than 1.
if iszero(lt(mload(a), 2)) {
let x := add(a, 0x20)
let y := add(a, 0x40)
let end := add(a, shl(5, add(mload(a), 1)))
for {} 1 {} {
if iszero(eq(mload(x), mload(y))) {
x := add(x, 0x20)
mstore(x, mload(y))
}
y := add(y, 0x20)
if eq(y, end) { break }
}
mstore(a, shr(5, sub(x, a)))
}
}
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(int256[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Removes duplicate elements from a ascendingly sorted memory array.
function uniquifySorted(address[] memory a) internal pure {
uniquifySorted(_toUints(a));
}
/// @dev Returns whether `a` contains `needle`,
/// and the index of the nearest element less than or equal to `needle`.
function searchSorted(uint256[] memory a, uint256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(a, needle, 0);
}
/// @dev Returns whether `a` contains `needle`,
/// and the index of the nearest element less than or equal to `needle`.
function searchSorted(int256[] memory a, int256 needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(needle), 1 << 255);
}
/// @dev Returns whether `a` contains `needle`,
/// and the index of the nearest element less than or equal to `needle`.
function searchSorted(address[] memory a, address needle)
internal
pure
returns (bool found, uint256 index)
{
(found, index) = _searchSorted(_toUints(a), uint256(uint160(needle)), 0);
}
/// @dev Reverses the array in-place.
function reverse(uint256[] memory a) internal pure {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(mload(a), 2)) {
let s := 0x20
let w := not(0x1f)
let h := add(a, shl(5, mload(a)))
for { a := add(a, s) } 1 {} {
let t := mload(a)
mstore(a, mload(h))
mstore(h, t)
h := add(h, w)
a := add(a, s)
if iszero(lt(a, h)) { break }
}
}
}
}
/// @dev Reverses the array in-place.
function reverse(int256[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Reverses the array in-place.
function reverse(address[] memory a) internal pure {
reverse(_toUints(a));
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(gt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := iszero(sgt(p, mload(a)))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is sorted in ascending order.
function isSorted(address[] memory a) internal pure returns (bool result) {
result = isSorted(_toUints(a));
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(uint256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := lt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(int256[] memory a) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := 1
if iszero(lt(mload(a), 2)) {
let end := add(a, shl(5, mload(a)))
for { a := add(a, 0x20) } 1 {} {
let p := mload(a)
a := add(a, 0x20)
result := slt(p, mload(a))
if iszero(mul(result, xor(a, end))) { break }
}
}
}
}
/// @dev Returns whether the array is strictly ascending (sorted and uniquified).
function isSortedAndUniquified(address[] memory a) internal pure returns (bool result) {
result = isSortedAndUniquified(_toUints(a));
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _difference(a, b, 0);
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_difference(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set difference between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function difference(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_difference(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _intersection(a, b, 0);
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_intersection(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function intersection(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_intersection(_toUints(a), _toUints(b), 0));
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(uint256[] memory a, uint256[] memory b)
internal
pure
returns (uint256[] memory c)
{
c = _union(a, b, 0);
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(int256[] memory a, int256[] memory b)
internal
pure
returns (int256[] memory c)
{
c = _toInts(_union(_toUints(a), _toUints(b), 1 << 255));
}
/// @dev Returns the sorted set union between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function union(address[] memory a, address[] memory b)
internal
pure
returns (address[] memory c)
{
c = _toAddresses(_union(_toUints(a), _toUints(b), 0));
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Reinterpret cast to an uint256 array.
function _toUints(int256[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an uint256 array.
function _toUints(address[] memory a) private pure returns (uint256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
// As any address written to memory will have the upper 96 bits
// of the word zeroized (as per Solidity spec), we can directly
// compare these addresses as if they are whole uint256 words.
casted := a
}
}
/// @dev Reinterpret cast to an int array.
function _toInts(uint256[] memory a) private pure returns (int256[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Reinterpret cast to an address array.
function _toAddresses(uint256[] memory a) private pure returns (address[] memory casted) {
/// @solidity memory-safe-assembly
assembly {
casted := a
}
}
/// @dev Converts an array of signed two-complement integers
/// to unsigned integers suitable for sorting.
function _convertTwosComplement(int256[] memory a) private pure {
/// @solidity memory-safe-assembly
assembly {
let w := shl(255, 1)
for { let end := add(a, shl(5, mload(a))) } iszero(eq(a, end)) {} {
a := add(a, 0x20)
mstore(a, add(mload(a), w))
}
}
}
/// @dev Returns whether `a` contains `needle`,
/// and the index of the nearest element less than or equal to `needle`.
function _searchSorted(uint256[] memory a, uint256 needle, uint256 signed)
private
pure
returns (bool found, uint256 index)
{
/// @solidity memory-safe-assembly
assembly {
let m := 0 // Middle slot.
let s := 0x20
let l := add(a, s) // Slot of the start of search.
let h := add(a, shl(5, mload(a))) // Slot of the end of search.
for { needle := add(signed, needle) } 1 {} {
// Average of `l` and `h`.
m := add(shl(5, shr(6, add(l, h))), and(31, l))
let t := add(signed, mload(m))
found := eq(t, needle)
if or(gt(l, h), found) { break }
// Decide whether to search the left or right half.
if iszero(gt(needle, t)) {
h := sub(m, s)
continue
}
l := add(m, s)
}
// `m` will be less than `add(a, 0x20)` in the case of an empty array,
// or when the value is less than the smallest value in the array.
let t := iszero(lt(m, add(a, s)))
index := shr(5, mul(sub(m, add(a, s)), t))
found := and(found, t)
}
}
/// @dev Returns the sorted set difference of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _difference(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set intersection between `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _intersection(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
b := add(b, s)
continue
}
a := add(a, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
/// @dev Returns the sorted set union of `a` and `b`.
/// Note: Behaviour is undefined if inputs are not sorted and uniquified.
function _union(uint256[] memory a, uint256[] memory b, uint256 signed)
private
pure
returns (uint256[] memory c)
{
/// @solidity memory-safe-assembly
assembly {
let s := 0x20
let aEnd := add(a, shl(5, mload(a)))
let bEnd := add(b, shl(5, mload(b)))
c := mload(0x40) // Set `c` to the free memory pointer.
a := add(a, s)
b := add(b, s)
let k := c
for {} iszero(or(gt(a, aEnd), gt(b, bEnd))) {} {
let u := mload(a)
let v := mload(b)
if iszero(xor(u, v)) {
k := add(k, s)
mstore(k, u)
a := add(a, s)
b := add(b, s)
continue
}
if iszero(lt(add(u, signed), add(v, signed))) {
k := add(k, s)
mstore(k, v)
b := add(b, s)
continue
}
k := add(k, s)
mstore(k, u)
a := add(a, s)
}
for {} iszero(gt(a, aEnd)) {} {
k := add(k, s)
mstore(k, mload(a))
a := add(a, s)
}
for {} iszero(gt(b, bEnd)) {} {
k := add(k, s)
mstore(k, mload(b))
b := add(b, s)
}
mstore(c, shr(5, sub(k, c))) // Store the length of `c`.
mstore(0x40, add(k, s)) // Allocate the memory for `c`.
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {PausableImpl} from "splits-utils/PausableImpl.sol";
import {IOracle} from "./interfaces/IOracle.sol";
/// @title Abstract Oracle Implementation
/// @author 0xSplits
/// @notice Abstract oracle clone-implementation
abstract contract OracleImpl is PausableImpl, IOracle {
/// slot 0 - 11 byte free
/// OwnableImpl storage
/// address internal $owner;
/// 20 bytes
/// PausableImpl storage
/// bool internal $paused;
/// 1 byte
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {AddressUtils} from "splits-utils/AddressUtils.sol";
import {IOracle} from "../interfaces/IOracle.sol";
import {IOracleFactory} from "../interfaces/IOracleFactory.sol";
using {_parseIntoOracle} for OracleParams global;
using AddressUtils for address;
struct OracleParams {
IOracle oracle;
CreateOracleParams createOracleParams;
}
struct CreateOracleParams {
IOracleFactory factory;
bytes data;
}
function _parseIntoOracle(OracleParams calldata oracleParams_) returns (IOracle) {
if (address(oracleParams_.oracle)._isNotEmpty()) {
return oracleParams_.oracle;
} else {
// if oracle not provided, create one with provided params
CreateOracleParams calldata createOracleParams = oracleParams_.createOracleParams;
return createOracleParams.factory.createOracle(createOracleParams.data);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
/// @title Ownable Implementation
/// @author 0xSplits
/// @notice Ownable clone-implementation
abstract contract OwnableImpl {
error Unauthorized();
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
/// slot 0 - 12 bytes free
address internal $owner;
/// 20 bytes
/// -----------------------------------------------------------------------
/// constructor & initializer
/// -----------------------------------------------------------------------
constructor() {}
function __initOwnable(address owner_) internal virtual {
emit OwnershipTransferred(address(0), owner_);
$owner = owner_;
}
/// -----------------------------------------------------------------------
/// modifiers
/// -----------------------------------------------------------------------
modifier onlyOwner() virtual {
if (msg.sender != owner()) revert Unauthorized();
_;
}
/// -----------------------------------------------------------------------
/// functions - public & external - onlyOwner
/// -----------------------------------------------------------------------
function transferOwnership(address owner_) public virtual onlyOwner {
$owner = owner_;
emit OwnershipTransferred(msg.sender, owner_);
}
/// -----------------------------------------------------------------------
/// functions - public & external - view
/// -----------------------------------------------------------------------
function owner() public view virtual returns (address) {
return $owner;
}
}
// 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 PairScaledOfferFactors 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];
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {LibClone} from "splits-utils/LibClone.sol";
import {PassThroughWalletImpl} from "./PassThroughWalletImpl.sol";
/// @title Pass-Through Wallet Factory
/// @author 0xSplits
/// @notice Factory for creating pass-through wallets.
/// @dev This contract uses token = address(0) to refer to ETH.
contract PassThroughWalletFactory {
using LibClone for address;
event CreatePassThroughWallet(
PassThroughWalletImpl indexed passThroughWallet, PassThroughWalletImpl.InitParams params
);
PassThroughWalletImpl public immutable passThroughWalletImpl;
constructor() {
passThroughWalletImpl = new PassThroughWalletImpl();
}
function createPassThroughWallet(PassThroughWalletImpl.InitParams calldata params_)
external
returns (PassThroughWalletImpl passThroughWallet)
{
passThroughWallet = PassThroughWalletImpl(payable(address(passThroughWalletImpl).clone()));
passThroughWallet.initializer(params_);
emit CreatePassThroughWallet({passThroughWallet: passThroughWallet, params: params_});
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {PausableImpl} from "splits-utils/PausableImpl.sol";
import {TokenUtils} from "splits-utils/TokenUtils.sol";
import {WalletImpl} from "splits-utils/WalletImpl.sol";
/// @title Pass-Through Wallet Implementation
/// @author 0xSplits
/// @notice A clone-implementation of a pass-through wallet.
/// Please be aware, owner has _FULL CONTROL_ of the deployment.
/// @dev This contract uses token = address(0) to refer to ETH.
contract PassThroughWalletImpl is WalletImpl, PausableImpl {
/// -----------------------------------------------------------------------
/// libraries
/// -----------------------------------------------------------------------
using TokenUtils for address;
/// -----------------------------------------------------------------------
/// structs
/// -----------------------------------------------------------------------
struct InitParams {
address owner;
bool paused;
address passThrough;
}
/// -----------------------------------------------------------------------
/// events
/// -----------------------------------------------------------------------
event SetPassThrough(address passThrough);
event PassThrough(address indexed passThrough, address[] tokens, uint256[] amounts);
// emitted in clone bytecode
event ReceiveETH(uint256 amount);
/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------
/// -----------------------------------------------------------------------
/// storage - constants & immutables
/// -----------------------------------------------------------------------
address public immutable passThroughWalletFactory;
/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
/// slot 0 - 11 bytes free
/// OwnableImpl storage
/// address internal $owner;
/// 20 bytes
/// PausableImpl storage
/// bool internal $paused;
/// 1 byte
/// slot 1 - 12 bytes free
/// address to pass-through funds to
address internal $passThrough;
/// 20 bytes
/// -----------------------------------------------------------------------
/// constructor & initializer
/// -----------------------------------------------------------------------
constructor() {
passThroughWalletFactory = msg.sender;
}
function initializer(InitParams calldata params_) external {
// only passThroughWalletFactory may call `initializer`
if (msg.sender != passThroughWalletFactory) revert Unauthorized();
// don't need to init wallet separately
__initPausable({owner_: params_.owner, paused_: params_.paused});
$passThrough = params_.passThrough;
}
/// -----------------------------------------------------------------------
/// functions
/// -----------------------------------------------------------------------
/// -----------------------------------------------------------------------
/// functions - public & external - onlyOwner
/// -----------------------------------------------------------------------
/// set passThrough
function setPassThrough(address passThrough_) external onlyOwner {
$passThrough = passThrough_;
emit SetPassThrough(passThrough_);
}
/// -----------------------------------------------------------------------
/// functions - public & external - view
/// -----------------------------------------------------------------------
function passThrough() external view returns (address) {
return $passThrough;
}
/// -----------------------------------------------------------------------
/// functions - public & external - permissionless
/// -----------------------------------------------------------------------
/// emit event when receiving ETH
/// @dev implemented w/i clone bytecode
/* receive() external payable { */
/* emit ReceiveETH(msg.value); */
/* } */
/// send `tokens_` to `$passThrough`
function passThroughTokens(address[] calldata tokens_) external pausable returns (uint256[] memory amounts) {
address _passThrough = $passThrough;
uint256 length = tokens_.length;
amounts = new uint256[](length);
for (uint256 i; i < length;) {
address token = tokens_[i];
uint256 amount = token._balanceOf(address(this));
amounts[i] = amount;
token._safeTransfer(_passThrough, amount);
unchecked {
++i;
}
}
emit PassThrough(_passThrough, tokens_, amounts);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {OwnableImpl} from "./OwnableImpl.sol";
/// @title Pausable Implementation
/// @author 0xSplits
/// @notice Pausable clone-implementation
abstract contract PausableImpl is OwnableImpl {
error Paused();
event SetPaused(bool paused);
/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
/// slot 0 - 11 bytes free
/// OwnableImpl storage
/// address internal $owner;
/// 20 bytes
bool internal $paused;
/// 1 byte
/// -----------------------------------------------------------------------
/// constructor & initializer
/// -----------------------------------------------------------------------
constructor() {}
function __initPausable(address owner_, bool paused_) internal virtual {
OwnableImpl.__initOwnable(owner_);
$paused = paused_;
}
/// -----------------------------------------------------------------------
/// modifiers
/// -----------------------------------------------------------------------
modifier pausable() virtual {
if (paused()) revert Paused();
_;
}
/// -----------------------------------------------------------------------
/// functions - public & external - onlyOwner
/// -----------------------------------------------------------------------
function setPaused(bool paused_) public virtual onlyOwner {
$paused = paused_;
emit SetPaused(paused_);
}
/// -----------------------------------------------------------------------
/// functions - public & external - view
/// -----------------------------------------------------------------------
function paused() public view virtual returns (bool) {
return $paused;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe integer casting library that reverts on overflow.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeCastLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
library SafeCastLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
error Overflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* UNSIGNED INTEGER SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toUint8(uint256 x) internal pure returns (uint8) {
if (x >= 1 << 8) _revertOverflow();
return uint8(x);
}
function toUint16(uint256 x) internal pure returns (uint16) {
if (x >= 1 << 16) _revertOverflow();
return uint16(x);
}
function toUint24(uint256 x) internal pure returns (uint24) {
if (x >= 1 << 24) _revertOverflow();
return uint24(x);
}
function toUint32(uint256 x) internal pure returns (uint32) {
if (x >= 1 << 32) _revertOverflow();
return uint32(x);
}
function toUint40(uint256 x) internal pure returns (uint40) {
if (x >= 1 << 40) _revertOverflow();
return uint40(x);
}
function toUint48(uint256 x) internal pure returns (uint48) {
if (x >= 1 << 48) _revertOverflow();
return uint48(x);
}
function toUint56(uint256 x) internal pure returns (uint56) {
if (x >= 1 << 56) _revertOverflow();
return uint56(x);
}
function toUint64(uint256 x) internal pure returns (uint64) {
if (x >= 1 << 64) _revertOverflow();
return uint64(x);
}
function toUint72(uint256 x) internal pure returns (uint72) {
if (x >= 1 << 72) _revertOverflow();
return uint72(x);
}
function toUint80(uint256 x) internal pure returns (uint80) {
if (x >= 1 << 80) _revertOverflow();
return uint80(x);
}
function toUint88(uint256 x) internal pure returns (uint88) {
if (x >= 1 << 88) _revertOverflow();
return uint88(x);
}
function toUint96(uint256 x) internal pure returns (uint96) {
if (x >= 1 << 96) _revertOverflow();
return uint96(x);
}
function toUint104(uint256 x) internal pure returns (uint104) {
if (x >= 1 << 104) _revertOverflow();
return uint104(x);
}
function toUint112(uint256 x) internal pure returns (uint112) {
if (x >= 1 << 112) _revertOverflow();
return uint112(x);
}
function toUint120(uint256 x) internal pure returns (uint120) {
if (x >= 1 << 120) _revertOverflow();
return uint120(x);
}
function toUint128(uint256 x) internal pure returns (uint128) {
if (x >= 1 << 128) _revertOverflow();
return uint128(x);
}
function toUint136(uint256 x) internal pure returns (uint136) {
if (x >= 1 << 136) _revertOverflow();
return uint136(x);
}
function toUint144(uint256 x) internal pure returns (uint144) {
if (x >= 1 << 144) _revertOverflow();
return uint144(x);
}
function toUint152(uint256 x) internal pure returns (uint152) {
if (x >= 1 << 152) _revertOverflow();
return uint152(x);
}
function toUint160(uint256 x) internal pure returns (uint160) {
if (x >= 1 << 160) _revertOverflow();
return uint160(x);
}
function toUint168(uint256 x) internal pure returns (uint168) {
if (x >= 1 << 168) _revertOverflow();
return uint168(x);
}
function toUint176(uint256 x) internal pure returns (uint176) {
if (x >= 1 << 176) _revertOverflow();
return uint176(x);
}
function toUint184(uint256 x) internal pure returns (uint184) {
if (x >= 1 << 184) _revertOverflow();
return uint184(x);
}
function toUint192(uint256 x) internal pure returns (uint192) {
if (x >= 1 << 192) _revertOverflow();
return uint192(x);
}
function toUint200(uint256 x) internal pure returns (uint200) {
if (x >= 1 << 200) _revertOverflow();
return uint200(x);
}
function toUint208(uint256 x) internal pure returns (uint208) {
if (x >= 1 << 208) _revertOverflow();
return uint208(x);
}
function toUint216(uint256 x) internal pure returns (uint216) {
if (x >= 1 << 216) _revertOverflow();
return uint216(x);
}
function toUint224(uint256 x) internal pure returns (uint224) {
if (x >= 1 << 224) _revertOverflow();
return uint224(x);
}
function toUint232(uint256 x) internal pure returns (uint232) {
if (x >= 1 << 232) _revertOverflow();
return uint232(x);
}
function toUint240(uint256 x) internal pure returns (uint240) {
if (x >= 1 << 240) _revertOverflow();
return uint240(x);
}
function toUint248(uint256 x) internal pure returns (uint248) {
if (x >= 1 << 248) _revertOverflow();
return uint248(x);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIGNED INTEGER SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toInt8(int256 x) internal pure returns (int8) {
int8 y = int8(x);
if (x != y) _revertOverflow();
return y;
}
function toInt16(int256 x) internal pure returns (int16) {
int16 y = int16(x);
if (x != y) _revertOverflow();
return y;
}
function toInt24(int256 x) internal pure returns (int24) {
int24 y = int24(x);
if (x != y) _revertOverflow();
return y;
}
function toInt32(int256 x) internal pure returns (int32) {
int32 y = int32(x);
if (x != y) _revertOverflow();
return y;
}
function toInt40(int256 x) internal pure returns (int40) {
int40 y = int40(x);
if (x != y) _revertOverflow();
return y;
}
function toInt48(int256 x) internal pure returns (int48) {
int48 y = int48(x);
if (x != y) _revertOverflow();
return y;
}
function toInt56(int256 x) internal pure returns (int56) {
int56 y = int56(x);
if (x != y) _revertOverflow();
return y;
}
function toInt64(int256 x) internal pure returns (int64) {
int64 y = int64(x);
if (x != y) _revertOverflow();
return y;
}
function toInt72(int256 x) internal pure returns (int72) {
int72 y = int72(x);
if (x != y) _revertOverflow();
return y;
}
function toInt80(int256 x) internal pure returns (int80) {
int80 y = int80(x);
if (x != y) _revertOverflow();
return y;
}
function toInt88(int256 x) internal pure returns (int88) {
int88 y = int88(x);
if (x != y) _revertOverflow();
return y;
}
function toInt96(int256 x) internal pure returns (int96) {
int96 y = int96(x);
if (x != y) _revertOverflow();
return y;
}
function toInt104(int256 x) internal pure returns (int104) {
int104 y = int104(x);
if (x != y) _revertOverflow();
return y;
}
function toInt112(int256 x) internal pure returns (int112) {
int112 y = int112(x);
if (x != y) _revertOverflow();
return y;
}
function toInt120(int256 x) internal pure returns (int120) {
int120 y = int120(x);
if (x != y) _revertOverflow();
return y;
}
function toInt128(int256 x) internal pure returns (int128) {
int128 y = int128(x);
if (x != y) _revertOverflow();
return y;
}
function toInt136(int256 x) internal pure returns (int136) {
int136 y = int136(x);
if (x != y) _revertOverflow();
return y;
}
function toInt144(int256 x) internal pure returns (int144) {
int144 y = int144(x);
if (x != y) _revertOverflow();
return y;
}
function toInt152(int256 x) internal pure returns (int152) {
int152 y = int152(x);
if (x != y) _revertOverflow();
return y;
}
function toInt160(int256 x) internal pure returns (int160) {
int160 y = int160(x);
if (x != y) _revertOverflow();
return y;
}
function toInt168(int256 x) internal pure returns (int168) {
int168 y = int168(x);
if (x != y) _revertOverflow();
return y;
}
function toInt176(int256 x) internal pure returns (int176) {
int176 y = int176(x);
if (x != y) _revertOverflow();
return y;
}
function toInt184(int256 x) internal pure returns (int184) {
int184 y = int184(x);
if (x != y) _revertOverflow();
return y;
}
function toInt192(int256 x) internal pure returns (int192) {
int192 y = int192(x);
if (x != y) _revertOverflow();
return y;
}
function toInt200(int256 x) internal pure returns (int200) {
int200 y = int200(x);
if (x != y) _revertOverflow();
return y;
}
function toInt208(int256 x) internal pure returns (int208) {
int208 y = int208(x);
if (x != y) _revertOverflow();
return y;
}
function toInt216(int256 x) internal pure returns (int216) {
int216 y = int216(x);
if (x != y) _revertOverflow();
return y;
}
function toInt224(int256 x) internal pure returns (int224) {
int224 y = int224(x);
if (x != y) _revertOverflow();
return y;
}
function toInt232(int256 x) internal pure returns (int232) {
int232 y = int232(x);
if (x != y) _revertOverflow();
return y;
}
function toInt240(int256 x) internal pure returns (int240) {
int240 y = int240(x);
if (x != y) _revertOverflow();
return y;
}
function toInt248(int256 x) internal pure returns (int248) {
int248 y = int248(x);
if (x != y) _revertOverflow();
return y;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* UNSIGNED TO SIGNED SAFE CASTING OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function toInt256(uint256 x) internal pure returns (int256) {
if (x >= 1 << 255) _revertOverflow();
return int256(x);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PRIVATE HELPERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
function _revertOverflow() private pure {
/// @solidity memory-safe-assembly
assembly {
// Store the function selector of `Overflow()`.
mstore(0x00, 0x35278d12)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH
/// that disallows any storage writes.
uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
/// Multiply by a small constant (e.g. 2), if needed.
uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` (in wei) ETH to `to`.
/// Reverts upon failure.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and check if it succeeded or not.
if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
/// The `gasStipend` can be set to a low enough value to prevent
/// storage writes or gas griefing.
///
/// If sending via the normal procedure fails, force sends the ETH by
/// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
///
/// Reverts if the current contract has insufficient balance.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
// If insufficient balance, revert.
if lt(selfbalance(), amount) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Transfer the ETH and check if it succeeded or not.
if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// We can directly use `SELFDESTRUCT` in the contract creation.
// Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
if iszero(create(amount, 0x0b, 0x16)) {
// For better gas estimation.
if iszero(gt(gas(), 1000000)) { revert(0, 0) }
}
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
/// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
/// for 99% of cases and can be overriden with the three-argument version of this
/// function if necessary.
///
/// If sending via the normal procedure fails, force sends the ETH by
/// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
///
/// Reverts if the current contract has insufficient balance.
function forceSafeTransferETH(address to, uint256 amount) internal {
// Manually inlined because the compiler doesn't inline functions with branches.
/// @solidity memory-safe-assembly
assembly {
// If insufficient balance, revert.
if lt(selfbalance(), amount) {
// Store the function selector of `ETHTransferFailed()`.
mstore(0x00, 0xb12d13eb)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Transfer the ETH and check if it succeeded or not.
if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
// We can directly use `SELFDESTRUCT` in the contract creation.
// Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
if iszero(create(amount, 0x0b, 0x16)) {
// For better gas estimation.
if iszero(gt(gas(), 1000000)) { revert(0, 0) }
}
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
/// The `gasStipend` can be set to a low enough value to prevent
/// storage writes or gas griefing.
///
/// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
///
/// Note: Does NOT revert upon failure.
/// Returns whether the transfer of ETH is successful instead.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
// Transfer the ETH and check if it succeeded or not.
success := call(gasStipend, to, amount, 0, 0, 0, 0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
// Store the function selector of `transferFrom(address,address,uint256)`.
mstore(0x0c, 0x23b872dd000000000000000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
// Store the function selector of `balanceOf(address)`.
mstore(0x0c, 0x70a08231000000000000000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Store the function selector of `transferFrom(address,address,uint256)`.
mstore(0x00, 0x23b872dd)
// The `amount` argument is already written to the memory word at 0x6c.
amount := mload(0x60)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFromFailed()`.
mstore(0x00, 0x7939f424)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
// Store the function selector of `transfer(address,uint256)`.
mstore(0x00, 0xa9059cbb000000000000000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten.
mstore(0x34, 0)
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
// The `amount` argument is already written to the memory word at 0x34.
amount := mload(0x34)
// Store the function selector of `transfer(address,uint256)`.
mstore(0x00, 0xa9059cbb000000000000000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `TransferFailed()`.
mstore(0x00, 0x90b8ec18)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten.
mstore(0x34, 0)
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
// Store the function selector of `approve(address,uint256)`.
mstore(0x00, 0x095ea7b3000000000000000000000000)
if iszero(
and( // The arguments of `and` are evaluated from right to left.
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(eq(mload(0x00), 1), iszero(returndatasize())),
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
// Store the function selector of `ApproveFailed()`.
mstore(0x00, 0x3e3f8f73)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
// Restore the part of the free memory pointer that was overwritten.
mstore(0x34, 0)
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
// Store the function selector of `balanceOf(address)`.
mstore(0x00, 0x70a08231000000000000000000000000)
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {IOracle} from "splits-oracle/interfaces/IOracle.sol";
import {OracleParams} from "splits-oracle/peripherals/OracleParams.sol";
import {LibClone} from "splits-utils/LibClone.sol";
import {SwapperImpl} from "./SwapperImpl.sol";
/// @title Swapper Factory
/// @author 0xSplits
/// @notice Factory for creating Swappers
/// @dev This contract uses token = address(0) to refer to ETH.
contract SwapperFactory {
using LibClone for address;
event CreateSwapper(SwapperImpl indexed swapper, SwapperImpl.InitParams params);
struct CreateSwapperParams {
address owner;
bool paused;
address beneficiary;
address tokenToBeneficiary;
OracleParams oracleParams;
uint32 defaultScaledOfferFactor;
SwapperImpl.SetPairScaledOfferFactorParams[] pairScaledOfferFactors;
}
SwapperImpl public immutable swapperImpl;
constructor() {
swapperImpl = new SwapperImpl();
}
/// -----------------------------------------------------------------------
/// functions - public & external
/// -----------------------------------------------------------------------
function createSwapper(CreateSwapperParams calldata params_) external returns (SwapperImpl swapper) {
IOracle oracle = params_.oracleParams._parseIntoOracle();
swapper = SwapperImpl(payable(address(swapperImpl).clone()));
SwapperImpl.InitParams memory swapperInitParams = SwapperImpl.InitParams({
owner: params_.owner,
paused: params_.paused,
beneficiary: params_.beneficiary,
tokenToBeneficiary: params_.tokenToBeneficiary,
oracle: oracle,
defaultScaledOfferFactor: params_.defaultScaledOfferFactor,
pairScaledOfferFactors: params_.pairScaledOfferFactors
});
swapper.initializer(swapperInitParams);
emit CreateSwapper({swapper: swapper, params: swapperInitParams});
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
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 particular output 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 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 {
/// -----------------------------------------------------------------------
/// libraries
/// -----------------------------------------------------------------------
using SafeTransferLib for address;
using SafeCastLib for uint256;
using TokenUtils for address;
using PairScaledOfferFactors for mapping(address => mapping(address => uint32));
/// -----------------------------------------------------------------------
/// errors
/// -----------------------------------------------------------------------
error Invalid_AmountsToBeneficiary();
error Invalid_QuoteToken();
error InsufficientFunds_InContract();
error InsufficientFunds_FromTrader();
/// -----------------------------------------------------------------------
/// structs
/// -----------------------------------------------------------------------
struct InitParams {
address owner;
bool paused;
address beneficiary;
address tokenToBeneficiary;
IOracle oracle;
uint32 defaultScaledOfferFactor;
SetPairScaledOfferFactorParams[] pairScaledOfferFactors;
}
struct SetPairScaledOfferFactorParams {
QuotePair quotePair;
uint32 scaledOfferFactor;
}
/// -----------------------------------------------------------------------
/// events
/// -----------------------------------------------------------------------
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 beneficiary,
address indexed trader,
QuoteParams[] quoteParams,
address tokenToBeneficiary,
uint256[] amountsToBeneficiary,
uint256 excessToBeneficiary
);
/// -----------------------------------------------------------------------
/// storage
/// -----------------------------------------------------------------------
/// -----------------------------------------------------------------------
/// storage - constants & immutables
/// -----------------------------------------------------------------------
address public immutable swapperFactory;
/// @dev percentages measured in hundredths of basis points
uint32 internal constant PERCENTAGE_SCALE = 100_00_00; // = 100%
/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
/// slot 0 - 11 bytes free
/// OwnableImpl storage
/// address internal $owner;
/// 20 bytes
/// PausableImpl storage
/// bool internal $paused;
/// 1 byte
/// slot 1 - 0 bytes free
/// address to receive post-swap tokens
address internal $beneficiary;
/// 20 bytes
/// used to track ETH payback in flash
uint96 internal $_payback;
/// 12 bytes
/// slot 2 - 8 bytes free
/// token type to send beneficiary
/// @dev 0x0 used for ETH
address internal $tokenToBeneficiary;
/// 20 bytes
/// default oracle 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
/// -----------------------------------------------------------------------
constructor() {
swapperFactory = msg.sender;
}
function initializer(InitParams calldata params_) external {
// only swapperFactory may call `initializer`
if (msg.sender != swapperFactory) revert Unauthorized();
// don't need to init wallet separately
__initPausable({owner_: params_.owner, paused_: params_.paused});
$beneficiary = params_.beneficiary;
$tokenToBeneficiary = params_.tokenToBeneficiary;
$oracle = params_.oracle;
$defaultScaledOfferFactor = params_.defaultScaledOfferFactor;
$_pairScaledOfferFactors._set(params_.pairScaledOfferFactors);
}
/// -----------------------------------------------------------------------
/// functions
/// -----------------------------------------------------------------------
/// -----------------------------------------------------------------------
/// functions - public & external
/// -----------------------------------------------------------------------
/// -----------------------------------------------------------------------
/// functions - public & external - onlyOwner
/// -----------------------------------------------------------------------
/// set beneficiary
function setBeneficiary(address beneficiary_) external onlyOwner {
$beneficiary = beneficiary_;
emit SetBeneficiary(beneficiary_);
}
/// set tokenToBeneficiary
function setTokenToBeneficiary(address tokenToBeneficiary_) external onlyOwner {
$tokenToBeneficiary = tokenToBeneficiary_;
emit SetTokenToBeneficiary(tokenToBeneficiary_);
}
/// set oracle
function setOracle(IOracle oracle_) external onlyOwner {
$oracle = oracle_;
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
/// -----------------------------------------------------------------------
function beneficiary() external view returns (address) {
return $beneficiary;
}
function tokenToBeneficiary() external view returns (address) {
return $tokenToBeneficiary;
}
function oracle() external view returns (IOracle) {
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
/// -----------------------------------------------------------------------
/// emit event when receiving ETH
/// @dev implemented w/i clone bytecode
/* receive() external payable { */
/* emit ReceiveETH(msg.value); */
/* } */
/// allows `#flash` to track ETH payback to `beneficiary`
/// @dev if used outside `#swapperFlashCallback`, msg.sender may lose funds.
/// Accumulates until next flash call
function payback() external payable {
$_payback += msg.value.toUint96();
emit Payback(msg.sender, msg.value);
}
/// allow third parties to withdraw tokens in return for sending `tokenToBeneficiary` to `beneficiary`
function flash(QuoteParams[] calldata quoteParams_, bytes calldata callbackData_)
external
pausable
returns (uint256)
{
address _tokenToBeneficiary = $tokenToBeneficiary;
(uint256 amountToBeneficiary, uint256[] memory amountsToBeneficiary) =
_transferToTrader(_tokenToBeneficiary, quoteParams_);
ISwapperFlashCallback(msg.sender).swapperFlashCallback({
tokenToBeneficiary: _tokenToBeneficiary,
amountToBeneficiary: amountToBeneficiary,
data: callbackData_
});
address _beneficiary = $beneficiary;
uint256 excessToBeneficiary = _transferToBeneficiary(_beneficiary, _tokenToBeneficiary, amountToBeneficiary);
emit Flash(
_beneficiary, msg.sender, quoteParams_, _tokenToBeneficiary, amountsToBeneficiary, excessToBeneficiary
);
return amountToBeneficiary + excessToBeneficiary;
}
/// -----------------------------------------------------------------------
/// functions - private & internal
/// -----------------------------------------------------------------------
function _transferToTrader(address tokenToBeneficiary_, QuoteParams[] calldata quoteParams_)
internal
returns (uint256 amountToBeneficiary, uint256[] memory amountsToBeneficiary)
{
uint256[] memory unscaledAmountsToBeneficiary = $oracle.getQuoteAmounts(quoteParams_);
uint256 length = quoteParams_.length;
if (unscaledAmountsToBeneficiary.length != length) revert Invalid_AmountsToBeneficiary();
amountsToBeneficiary = new uint256[](length);
uint256 scaledAmountToBeneficiary;
uint128 amountToTrader;
address tokenToTrader;
for (uint256 i; i < length;) {
QuoteParams calldata qp = quoteParams_[i];
if (tokenToBeneficiary_ != qp.quotePair.quote) revert Invalid_QuoteToken();
tokenToTrader = qp.quotePair.base;
amountToTrader = qp.baseAmount;
if (amountToTrader > tokenToTrader._balanceOf(address(this))) {
revert InsufficientFunds_InContract();
}
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 {
++i;
}
}
}
function _transferToBeneficiary(address beneficiary_, address tokenToBeneficiary_, uint256 amountToBeneficiary_)
internal
returns (uint256 excessToBeneficiary)
{
if (tokenToBeneficiary_._isETH()) {
if ($_payback < amountToBeneficiary_) {
revert InsufficientFunds_FromTrader();
}
$_payback = 0;
// send ETH to `beneficiary`
uint256 ethBalance = address(this).balance;
excessToBeneficiary = ethBalance - amountToBeneficiary_;
beneficiary_.safeTransferETH(ethBalance);
} else {
tokenToBeneficiary_.safeTransferFrom(msg.sender, beneficiary_, amountToBeneficiary_);
// flush excess `tokenToBeneficiary` to `beneficiary`
excessToBeneficiary = ERC20(tokenToBeneficiary_).balanceOf(address(this));
if (excessToBeneficiary > 0) {
tokenToBeneficiary_.safeTransfer(beneficiary_, excessToBeneficiary);
}
}
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
/// Library to handle basic token functions for ERC20s & ETH (represented by 0x0)
library TokenUtils {
using SafeTransferLib for address;
address internal constant ETH_ADDRESS = address(0);
function _isETH(address token) internal pure returns (bool) {
return (token == ETH_ADDRESS);
}
function _decimals(address token) internal view returns (uint8) {
return _isETH(token) ? 18 : ERC20(token).decimals();
}
function _balanceOf(address token, address addr) internal view returns (uint256) {
return _isETH(token) ? addr.balance : ERC20(token).balanceOf(addr);
}
function _safeTransfer(address token, address addr, uint256 amount) internal {
if (_isETH(token)) addr.safeTransferETH(amount);
else token.safeTransfer(addr, amount);
}
}
interface ERC20 {
function decimals() external view returns (uint8);
function balanceOf(address addr) external view returns (uint256);
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;
import {ERC1155TokenReceiver} from "solmate/tokens/ERC1155.sol";
import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol";
import {OwnableImpl} from "./OwnableImpl.sol";
/// @title Wallet Implementation
/// @author 0xSplits
/// @notice Minimal smart wallet clone-implementation
abstract contract WalletImpl is OwnableImpl, ERC721TokenReceiver, ERC1155TokenReceiver {
struct Call {
address to;
uint256 value;
bytes data;
}
event ExecCalls(Call[] calls);
/// -----------------------------------------------------------------------
/// storage - mutables
/// -----------------------------------------------------------------------
/// slot 0 - 12 bytes free
/// OwnableImpl storage
/// address internal $owner;
/// 20 bytes
/// -----------------------------------------------------------------------
/// constructor & initializer
/// -----------------------------------------------------------------------
constructor() {}
function __initWallet(address owner_) internal {
OwnableImpl.__initOwnable(owner_);
}
/// -----------------------------------------------------------------------
/// functions - external & public - onlyOwner
/// -----------------------------------------------------------------------
/// allow owner to execute arbitrary calls
function execCalls(Call[] calldata calls_)
external
payable
onlyOwner
returns (uint256 blockNumber, bytes[] memory returnData)
{
blockNumber = block.number;
uint256 length = calls_.length;
returnData = new bytes[](length);
bool success;
for (uint256 i; i < length;) {
Call calldata calli = calls_[i];
(success, returnData[i]) = calli.to.call{value: calli.value}(calli.data);
require(success, string(returnData[i]));
unchecked {
++i;
}
}
emit ExecCalls(calls_);
}
}
{
"compilationTarget": {
"src/DiversifierFactory.sol": "DiversifierFactory"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": [
":@uniswap/v3-core/=lib/splits-oracle/lib/v3-core/",
":@uniswap/v3-periphery/=lib/splits-oracle/lib/v3-periphery/",
":chainlink/=lib/splits-swapper/lib/splits-oracle/lib/chainlink/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/splits-swapper/lib/splits-oracle/lib/chainlink/contracts/foundry-lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":solady/=lib/solady/src/",
":solmate/=lib/splits-oracle/lib/solmate/src/",
":splits-oracle/=lib/splits-oracle/src/",
":splits-pass-through-wallet/=lib/splits-pass-through-wallet/src/",
":splits-swapper/=lib/splits-swapper/src/",
":splits-tests/=lib/splits-utils/test/",
":splits-utils/=lib/splits-utils/src/",
":v3-core/=lib/splits-oracle/lib/v3-core/contracts/",
":v3-periphery/=lib/splits-oracle/lib/v3-periphery/contracts/"
]
}
[{"inputs":[{"internalType":"contract ISplitMain","name":"splitMain_","type":"address"},{"internalType":"contract SwapperFactory","name":"swapperFactory_","type":"address"},{"internalType":"contract PassThroughWalletFactory","name":"passThroughWalletFactory_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"diversifier","type":"address"}],"name":"CreateDiversifier","type":"event"},{"inputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"paused","type":"bool"},{"components":[{"internalType":"contract IOracle","name":"oracle","type":"address"},{"components":[{"internalType":"contract IOracleFactory","name":"factory","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct CreateOracleParams","name":"createOracleParams","type":"tuple"}],"internalType":"struct OracleParams","name":"oracleParams","type":"tuple"},{"components":[{"internalType":"address","name":"account","type":"address"},{"components":[{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"address","name":"tokenToBeneficiary","type":"address"},{"internalType":"uint32","name":"defaultScaledOfferFactor","type":"uint32"},{"components":[{"components":[{"internalType":"address","name":"base","type":"address"},{"internalType":"address","name":"quote","type":"address"}],"internalType":"struct QuotePair","name":"quotePair","type":"tuple"},{"internalType":"uint32","name":"scaledOfferFactor","type":"uint32"}],"internalType":"struct SwapperImpl.SetPairScaledOfferFactorParams[]","name":"pairScaledOfferFactors","type":"tuple[]"}],"internalType":"struct DiversifierFactory.CreateSwapperParams","name":"createSwapperParams","type":"tuple"},{"internalType":"uint32","name":"percentAllocation","type":"uint32"}],"internalType":"struct DiversifierFactory.RecipientParams[]","name":"recipientParams","type":"tuple[]"}],"internalType":"struct DiversifierFactory.CreateDiversifierParams","name":"params_","type":"tuple"}],"name":"createDiversifier","outputs":[{"internalType":"address","name":"diversifier","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"passThroughWalletFactory","outputs":[{"internalType":"contract PassThroughWalletFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"splitMain","outputs":[{"internalType":"contract ISplitMain","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"swapperFactory","outputs":[{"internalType":"contract SwapperFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"}]