Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callback: Fixed Fee on Proceeds #14

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ g-uni-v1-core = { version = "0.9.9", git = "[email protected]:Axis-Fi/g-uni-v1-core
solmate = { version = "6.7.0", git = "[email protected]:transmissions11/solmate.git", rev = "c892309933b25c03d32b1b0d674df7ae292ba925" }
clones-with-immutable-args = { version = "1.1.1", git = "[email protected]:wighawag/clones-with-immutable-args.git", rev = "f5ca191afea933d50a36d101009b5644dc28bc99" }
solady = { version = "0.0.124" }
splits-waterfall = { version = "1.0.0", git = "[email protected]:0xSplits/splits-waterfall.git", rev = "1491f8b9454f22cf8a7bf5b384f5cdb75c9105b1" }
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
@uniswap/v3-core/contracts=dependencies/@uniswap-v3-core-1.0.1-solc-0.8-simulate/contracts
@openzeppelin/contracts=dependencies/@openzeppelin-contracts-4.9.2
@openzeppelin/contracts-upgradeable=dependencies/@openzeppelin-contracts-upgradeable-4.9.2
@solady-0.0.124=dependencies/solady-0.0.124/src
@solady-0.0.124=dependencies/solady-0.0.124/src
@splits-waterfall-1.0.0=dependencies/splits-waterfall-1.0.0/src
7 changes: 7 additions & 0 deletions script/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ echo "*** Installing forge dependencies"
forge install
echo " Done"

echo ""
echo "*** Restoring submodule commits"

echo ""
echo "*** Installing soldeer dependencies"
rm -rf dependencies/* && forge soldeer update
echo " Done"

echo ""
echo "*** Applying patch to splits-waterfall"
patch -d dependencies/splits-waterfall-1.0.0/ -p1 < script/patch/splits_waterfall.patch
30 changes: 30 additions & 0 deletions script/patch/splits_waterfall.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
diff --git a/src/WaterfallModule.sol b/src/WaterfallModule.sol
index deb5c09..0429e4d 100644
--- a/src/WaterfallModule.sol
+++ b/src/WaterfallModule.sol
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.17;

-import {Clone} from "solady/utils/Clone.sol";
-import {ERC20} from "solmate/tokens/ERC20.sol";
-import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
+import {Clone} from "@solady-0.0.124/utils/Clone.sol";
+import {ERC20} from "@solmate-6.7.0/tokens/ERC20.sol";
+import {SafeTransferLib} from "@solady-0.0.124/utils/SafeTransferLib.sol";

/// @title WaterfallModule
/// @author 0xSplits
diff --git a/src/WaterfallModuleFactory.sol b/src/WaterfallModuleFactory.sol
index 9fd7bd0..c9ac86a 100644
--- a/src/WaterfallModuleFactory.sol
+++ b/src/WaterfallModuleFactory.sol
@@ -2,7 +2,7 @@
pragma solidity ^0.8.17;

import {WaterfallModule} from "./WaterfallModule.sol";
-import {LibClone} from "solady/utils/LibClone.sol";
+import {LibClone} from "@solady-0.0.124/utils/LibClone.sol";

/// @title WaterfallModuleFactory
/// @author 0xSplits
9 changes: 9 additions & 0 deletions script/patch/splits_waterfall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

# Move into the right directory
cd dependencies/splits-waterfall-1.0.0/

# Generate the diff
git diff . > ../../script/patch/splits_waterfall.patch

echo "Done!"
6 changes: 6 additions & 0 deletions soldeer.lock
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,9 @@ name = "solady"
version = "0.0.124"
source = "https://soldeer-revisions.s3.amazonaws.com/solady/0_0_124_22-01-2024_13:28:04_solady.zip"
checksum = "9342385eaad08f9bb5408be0b41b241dd2b974c001f7da8c3b1ac552b52ce16b"

[[dependencies]]
name = "splits-waterfall"
version = "1.0.0"
source = "[email protected]:0xSplits/splits-waterfall.git"
checksum = "1491f8b9454f22cf8a7bf5b384f5cdb75c9105b1"
202 changes: 202 additions & 0 deletions src/callbacks/splits/FixedFeePayment.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Libraries
import {ERC20} from "@solmate-6.7.0/tokens/ERC20.sol";
import {SafeTransferLib} from "@solmate-6.7.0/utils/SafeTransferLib.sol";

// Axis callback contracts
import {BaseCallback} from "@axis-core-1.0.0/bases/BaseCallback.sol";
import {Callbacks} from "@axis-core-1.0.0/lib/Callbacks.sol";

// Splits contracts
import {WaterfallModuleFactory} from "@splits-waterfall-1.0.0/WaterfallModuleFactory.sol";
import {WaterfallModule} from "@splits-waterfall-1.0.0/WaterfallModule.sol";

/// @title FixedFeePayment
/// @notice A callback that makes fixed fee payments to the specified recipients, after which the remaining balance is sent to the last recipient.
/// @dev This contract uses the Splits Waterfall contract, instead of reproducing the functionality.
contract FixedFeePayment is BaseCallback {
using SafeTransferLib for ERC20;

// ========== ERRORS ========== //

// ========== DATA STRUCTURES ========== //

/// @notice Parameters for the WaterfallModule contract
/// @param recipients Addresses to waterfall payments to. The array should be one longer than `thresholds`, as residual payments are sent to the last recipient
/// @param thresholds Absolute payment thresholds for waterfall recipients
struct WaterfallParams {
address[] recipients;
uint256[] thresholds;
}

// ========== STATE VARIABLES ========== //

/// @notice The factory contract for deploying WaterfallModule clones
WaterfallModuleFactory public immutable factory;

/// @notice Stores the WaterfallModule contract for each lot
mapping(uint96 lotId => WaterfallModule) public lotModules;

/// @notice Stores the quote token for each lot
mapping(uint96 lotId => ERC20) public lotQuoteTokens;

// ========== CONSTRUCTOR ========== //

/// @dev This function reverts if:
/// - An unsupported permission is specified
/// - The callback is not configured to receive quote tokens
constructor(
address auctionHouse_,
Callbacks.Permissions memory permissions_,
address factory_
) BaseCallback(auctionHouse_, permissions_) {
// Validate that no unsupported permissions are specified
if (permissions_.onCancel || permissions_.onCurate || permissions_.onBid) {
revert Callback_InvalidParams();
}

// Validate that the callback is configured to receive proceeds
if (!permissions_.receiveQuoteTokens) {
revert Callback_InvalidParams();
}

// Validate that the factory contract is not the zero address
if (factory_ == address(0)) {
revert Callback_InvalidParams();
}

// Set the factory contract
factory = WaterfallModuleFactory(factory_);
}

// ========== CALLBACK FUNCTIONS ========== //

/// @inheritdoc BaseCallback
/// @dev This function performs the following:
/// - Validates the configuration parameters
/// - Creates a new Splits Waterfall contract
///
/// This function reverts if:
/// - A Splits contract already exists for the lot
/// - The callback data cannot be decoded into a WaterfallParams struct
/// - Validation of the parameters by WaterfallModuleFactory fails
///
/// Notes:
/// - The Splits Waterfall contract supports a fallback address for any other tokens that are sent to the contract. This is set to the seller's address.
function _onCreate(
uint96 lotId_,
address seller_,
address,
address quoteToken_,
uint256,
bool,
bytes calldata callbackData_
) internal virtual override {
// Validate if the Splits contract already exists for the lot
if (lotModules[lotId_] != WaterfallModule(address(0))) {
revert Callback_InvalidParams();
}

// Decode the callback data
WaterfallParams memory params = abi.decode(callbackData_, (WaterfallParams));

// Create the WaterfallModule
WaterfallModule wm = factory.createWaterfallModule(
quoteToken_, seller_, params.recipients, params.thresholds
);

// Store configuration
lotModules[lotId_] = wm;
lotQuoteTokens[lotId_] = ERC20(quoteToken_);
}

/// @inheritdoc BaseCallback
/// @dev Not implemented
function _onCancel(uint96, uint256, bool, bytes calldata) internal virtual override {
revert Callback_NotImplemented();
}

/// @inheritdoc BaseCallback
/// @dev Not implemented
function _onCurate(uint96, uint256, bool, bytes calldata) internal virtual override {
revert Callback_NotImplemented();
}

/// @inheritdoc BaseCallback
/// @dev This function performs the following:
/// - Performs validation
/// - Transfers the proceeds to the Splits contract
/// - Calls the Splits contract to allocate the tokens to the recipients
///
/// This function reverts if:
/// - The Splits contract for the lot does not exist
///
/// Notes:
/// - The `WaterfallModule.waterfallFundsPull()` function is called to allocate the tokens to the recipients but not transfer them. This is to avoid errors.
/// - Funds can be withdrawn by calling `WaterfallModule.withdraw()` on the Splits contract.
function _onPurchase(
uint96 lotId_,
address,
uint256 amount_,
uint256,
bool,
bytes calldata
) internal virtual override hasModule(lotId_) {
WaterfallModule wm = lotModules[lotId_];

// Transfer to the Splits contract
lotQuoteTokens[lotId_].safeTransfer(address(wm), amount_);

// Allocate tokens
wm.waterfallFundsPull();
}

/// @inheritdoc BaseCallback
/// @dev Not implemented
function _onBid(uint96, uint64, address, uint256, bytes calldata) internal virtual override {
revert Callback_NotImplemented();
}

/// @inheritdoc BaseCallback
/// @dev This function performs the following:
/// - Performs validation
/// - Transfers the proceeds to the Splits contract
/// - Calls the Splits contract to allocate the tokens to the recipients
///
/// This function reverts if:
/// - The Splits contract for the lot does not exist
///
/// Notes:
/// - The `WaterfallModule.waterfallFundsPull()` function is called to allocate the tokens to the recipients but not transfer them. This is to avoid errors.
/// - Funds can be withdrawn by calling `WaterfallModule.withdraw()` on the Splits contract.
function _onSettle(
uint96 lotId_,
uint256 proceeds_,
uint256,
bytes calldata
) internal virtual override hasModule(lotId_) {
// Transfer to the Splits contract
lotQuoteTokens[lotId_].safeTransfer(address(lotModules[lotId_]), proceeds_);

// Allocate tokens
lotModules[lotId_].waterfallFundsPull();
}

// ========== MODIFIERS ========== //

/// @notice Modifier to check if a WaterfallModule exists for the lot
modifier hasModule(uint96 lotId_) {
// Validate the module
if (lotModules[lotId_] == WaterfallModule(address(0))) {
revert Callback_InvalidParams();
}

// Validate the quote token
if (lotQuoteTokens[lotId_] == ERC20(address(0))) {
revert Callback_InvalidParams();
}
_;
}
}
Loading