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

hook submission #103

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
119 changes: 119 additions & 0 deletions packages/foundry/contracts/hooks/CommitMinerHook.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "Balancer/BaseHooks.sol";
import "Balancer/VaultGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract CommitMinerHook is BaseHooks, VaultGuard, ReentrancyGuard {
struct Commit {
bytes32 hash; // The commit hash
uint256 blockNumber; // The block number when the commit was created
address pool; // The pool that generated the commit
address swapper; // Address of the user who performed the swap
}

IERC20 public immutable paymentToken; // The ERC20 token used for both payments and rewards
address private immutable hookDeployer; // Address of the hook deployer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but could also derive from Ownable and use owner(), which does the same thing in a standardized way, and also enables permissioned functions (e.g., maybe the owner can change the fee percentages later).

Commit[] public commitBacklog; // Array storing all commits
uint256 public feePercentage = 10; // 10% fee to hook deployer, 30% swappers, 60% pool LPs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be more consistent with the rest of the system to use FixedPoint math (e.g., 10% = 10e16).


event CommitGenerated(address indexed pool, address indexed swapper, bytes32 commitHash, uint256 blockNumber);
event RandomnessRequested(address indexed requester, uint256 amountPaid, uint256 commitCount);
event FeeDistributed(address indexed pool, address indexed recipient, uint256 amount);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to have events for each fee type, for ease of tracking. But looking at the code below, I don't think you can track at this level of detail unless the # of commits were very low. Probably just a single event for the total fee, and an event on deployment that shows the split.


constructor(IVault vault, IERC20 _paymentToken) VaultGuard(vault) {
hookDeployer = msg.sender;
paymentToken = _paymentToken; // The specified ERC20 token for all payments and rewards
}

function getHookFlags() public pure override returns (IHooks.HookFlags memory hookFlags) {
hookFlags.shouldCallBeforeSwap = true;
}

/// @inheritdoc IHooks
function onRegister(address factory, address pool, ...) public override onlyVault returns (bool) {
return true;
}

// Hook triggered before a swap to create a commit
function onBeforeSwap(
address pool,
uint256 amountIn,
uint256 amountOut,
address tokenIn,
address tokenOut,
address user
) external override onlyVault {
bytes32 commitHash = keccak256(abi.encodePacked(block.timestamp, user, amountIn, amountOut, pool, gasleft()));
commitBacklog.push(Commit(commitHash, block.number, pool, user));

emit CommitGenerated(pool, user, commitHash, block.number);
}

// Randomness requester pays for a batch of commits using the specified paymentToken
function requestRandomness(uint256 commitCount, uint256 amountPaid) external nonReentrant {
require(commitCount <= commitBacklog.length, "Not enough commits available");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This grows without bound, so there would have to be some kind of validation here in production. Like using a circular buffer instead of a simple array (1024 or 2048), and limiting the commitCount, or it would run out of gas computing it. The writes would also get cheaper once the buffer was full, vs. always being expensive.


// Transfer payment from requester
require(paymentToken.transferFrom(msg.sender, address(this), amountPaid), "Payment transfer failed");

// Generate randomness based on the selected number of commits
bytes32 finalRandomness = aggregateCommits(commitCount);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this value go? Shouldn't it be returned or something?


// Emit event for randomness request
emit RandomnessRequested(msg.sender, amountPaid, commitCount);

// Distribute fees
distributeFees(amountPaid, commitCount);
}

// Function to aggregate commits into randomness
function aggregateCommits(uint256 commitCount) internal view returns (bytes32) {
bytes32 randomness;
for (uint256 i = commitBacklog.length - commitCount; i < commitBacklog.length; i++) {
randomness = keccak256(abi.encodePacked(randomness, commitBacklog[i].hash, block.number, gasleft()));
}
return randomness;
}

// Function to distribute fees among swappers, LPs, and the hook deployer
function distributeFees(uint256 amountPaid, uint256 commitCount) internal nonReentrant {
uint256 hookFee = (amountPaid * 10) / 100; // 10% to hook deployer
uint256 swapperFee = (amountPaid * 30) / 100; // 30% to swappers
uint256 lpFee = amountPaid - hookFee - swapperFee; // 60% to pool LPs

// Transfer 10% to hook deployer
require(paymentToken.transfer(hookDeployer, hookFee), "Hook deployer fee transfer failed");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would use safeTransfer if it's an arbitrary token.


// Distribute 30% to the swappers
uint256 perSwapperFee = swapperFee / commitCount;
for (uint256 i = commitBacklog.length - commitCount; i < commitBacklog.length; i++) {
require(paymentToken.transfer(commitBacklog[i].swapper, perSwapperFee), "Swapper fee transfer failed");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of transfers! If you're going to try to do this, consider a different data structure that aggregates swappers, since active users might have 20-30 swaps, and doing 30 transfers is prohibitive (not to mention all the event emission).

emit FeeDistributed(commitBacklog[i].pool, commitBacklog[i].swapper, perSwapperFee);
}

// Distribute 60% to the LPs
uint256 perPoolFee = lpFee / commitCount;
for (uint256 i = commitBacklog.length - commitCount; i < commitBacklog.length; i++) {
require(paymentToken.transfer(commitBacklog[i].pool, perPoolFee), "LP fee transfer failed"); // Simplified; could route to LPs via pool logic

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tokens would be locked/lost. You can't just transfer tokens to the pool address (since v1, anyway). You'd have to use the DONATION add liquidity method - and check on registration that the pool supports it. same comment about aggregation transfers.

emit FeeDistributed(commitBacklog[i].pool, msg.sender, perPoolFee);
}
}

// Use quadratic pricing model for commit pricing
function calculateFeeForCommits(uint256 commitCount) internal view returns (uint256) {
uint256 basePrice = 1e18; // 1 token as base price per commit

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assumes 18 decimals. Would either need to constrain it to 18-decimal tokens only, or make some provision for scaling.

return commitCount ** 2 * basePrice; // Quadratic pricing
}

// Allow the user to pay more for more commits to improve randomness quality
function payMoreForMoreCommits(uint256 commitWindowSize, uint256 additionalAmount) external nonReentrant {
uint256 amountRequired = calculateFeeForCommits(commitWindowSize);
require(additionalAmount >= amountRequired, "Insufficient payment");

// Proceed with larger batch of commits
requestRandomness(commitWindowSize, additionalAmount);
}
}
144 changes: 144 additions & 0 deletions packages/foundry/contracts/hooks/CommitMinerHookTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/CommitMinerHook.sol";
import "openzeppelin-contracts/token/ERC20/IERC20.sol";

contract CommitMinerHookTest is Test {
CommitMinerHook public commitMinerHook;
IERC20 public paymentToken;
address public pool;
address public swapper;
address public lps;
address public deployer;

function setUp() public {
// Mock addresses
pool = address(0x123);
swapper = address(0x456);
lps = address(0x789);
deployer = address(this);

// Deploy a mock ERC20 payment token
paymentToken = IERC20(address(new MockERC20()));

// Deploy the CommitMinerHook contract
IVault vault = IVault(address(0x111));
commitMinerHook = new CommitMinerHook(vault, paymentToken);
}

// Test commit generation on a swap
function testCommitGeneratedOnSwap() public {
// Set up mock swap details
uint256 amountIn = 1000 * 1e18;
uint256 amountOut = 900 * 1e18;
address tokenIn = address(0x222);
address tokenOut = address(0x333);

// Simulate a swap and trigger the onBeforeSwap hook
vm.prank(swapper);
commitMinerHook.onBeforeSwap(pool, amountIn, amountOut, tokenIn, tokenOut, swapper);

// Verify that a commit has been generated
(bytes32 hash, uint256 blockNumber, address poolAddress, address swapperAddress) = commitMinerHook.commitBacklog(0);
assertEq(poolAddress, pool);
assertEq(swapperAddress, swapper);
}

// Test randomness request and fee transfer
function testRandomnessRequestAndFeeTransfer() public {
// Set up mock commits
createMockCommits(3);

// Set up fee for requesting randomness
uint256 commitCount = 2;
uint256 fee = commitMinerHook.calculateFeeForCommits(commitCount);

// Mint tokens to the randomness requester
paymentToken.mint(address(this), fee);

// Approve the contract to spend the tokens
paymentToken.approve(address(commitMinerHook), fee);

// Request randomness
commitMinerHook.requestRandomness(commitCount, fee);

// Verify the fee has been transferred and distributed
assertEq(paymentToken.balanceOf(deployer), fee / 10); // 10% to deployer
// Further checks for swapper and LP distribution can be done here
}

// Test quadratic pricing model for commits
function testQuadraticPricingForCommits() public {
uint256 commitCount = 4;
uint256 expectedFee = 16 * 1e18; // 4^2 * 1e18
uint256 calculatedFee = commitMinerHook.calculateFeeForCommits(commitCount);

// Verify the calculated fee matches the quadratic pricing model
assertEq(calculatedFee, expectedFee);
}

// Test fee distribution to swappers, LPs, and deployer
function testFeeDistribution() public {
// Set up mock commits and fees
createMockCommits(3);
uint256 commitCount = 2;
uint256 fee = commitMinerHook.calculateFeeForCommits(commitCount);
paymentToken.mint(address(this), fee);
paymentToken.approve(address(commitMinerHook), fee);

// Request randomness to trigger fee distribution
commitMinerHook.requestRandomness(commitCount, fee);

// Verify fee distribution to hook deployer, swappers, and LPs
uint256 hookDeployerFee = (fee * 10) / 100;
uint256 swapperFee = (fee * 30) / 100;
uint256 lpFee = fee - hookDeployerFee - swapperFee;

assertEq(paymentToken.balanceOf(deployer), hookDeployerFee);
assertEq(paymentToken.balanceOf(swapper), swapperFee / commitCount); // 30% split between swappers
assertEq(paymentToken.balanceOf(pool), lpFee / commitCount); // 60% to LPs
}

// Helper function to create mock commits
function createMockCommits(uint256 numCommits) internal {
for (uint256 i = 0; i < numCommits; i++) {
vm.prank(swapper);
commitMinerHook.onBeforeSwap(pool, 1000 * 1e18, 900 * 1e18, address(0x111), address(0x222), swapper);
}
}
}

// Mock ERC20 contract to simulate payment token behavior
contract MockERC20 is IERC20 {
string public name = "Mock ERC20";
string public symbol = "MERC20";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

function transfer(address recipient, uint256 amount) public override returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
return true;
}

function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
return true;
}

function approve(address spender, uint256 amount) public override returns (bool) {
allowance[msg.sender][spender] = amount;
return true;
}

function mint(address to, uint256 amount) public {
totalSupply += amount;
balanceOf[to] += amount;
}
}
75 changes: 75 additions & 0 deletions packages/foundry/contracts/hooks/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
Commit Miner Hook
Overview
The Commit Miner Hook is a custom hook designed for use within the Balancer V3 ecosystem. It aggregates commits (hashes) from swaps in different liquidity pools and allows users to request randomness based on these commits. This randomness generation is decentralized, and randomness requesters pay a fee for it. The fees collected are distributed between swappers, liquidity providers (LPs), and the hook deployer.

This system leverages the commit and reveal scheme to aggregate transaction-based data, providing randomness for use cases like gaming, lotteries, or decentralized oracle systems while generating additional yield for liquidity providers and participants.

What the Hook Does
Commit Generation:

Every time a swap occurs in a pool that uses the Commit Miner Hook, the hook generates a commit hash based on swap details (e.g., token amounts, user, block data) and stores it in the contract's commit backlog.
Randomness Request:

Users can request randomness by paying a fee in a specified ERC20 payment token. The fee scales quadratically based on the number of commits the user wants to use to generate randomness.
The contract aggregates a set number of commits from the backlog and generates a random value for the requester.
Fee Distribution:

The fee collected from randomness requests is distributed to three parties:
10% to the hook deployer.
30% to the swappers who generated the commits used in the randomness generation.
60% to the liquidity providers (LPs) of the pools from which the commits originated.
Quadratic Pricing Model:

The pricing for requesting randomness increases quadratically based on the number of commits used in the randomness generation (i.e., larger requests are progressively more expensive).
Example Use Case
Imagine you are running a decentralized lottery and need a source of randomness for drawing winners. You could use the Commit Miner Hook to generate randomness based on real-time swap data from various Balancer pools. Here’s how it could work:

Setup:

The hook is integrated into Balancer pools, and the system starts aggregating commits from swaps happening in these pools.
Randomness Request:

When the lottery system needs randomness to draw a winner, it calls the requestRandomness function on the Commit Miner Hook contract and pays a fee in the specified ERC20 token.
The contract bundles recent commits and generates a random number, which can then be used to draw a lottery winner.
Fee Distribution:

The fee paid by the lottery contract is split between the swappers who made the underlying transactions, the liquidity providers of the involved pools, and the hook deployer.
This system creates a mutually beneficial environment where randomness can be sourced in a decentralized manner, while participants (swappers, LPs) are rewarded for contributing to the underlying data used to generate that randomness.

Developer Experience (DevX) Feedback
Positives:
Easy Integration:

The hook integrates seamlessly with Balancer V3 pools, making it easy to add decentralized randomness generation to existing pools without disrupting core functionality.
The commit and reveal scheme ensures that randomness is based on real-world, on-chain events, which can improve transparency and trust.
Flexibility:

By allowing randomness requesters to select how many commits to use, the hook gives users control over the quality and cost of the randomness they are purchasing.
The quadratic pricing model is both flexible and efficient for handling multiple use cases, from small-scale randomness to more robust needs.
Areas for Improvement:
Commit Volume Dependency:

The randomness system depends on there being enough swaps in the connected pools. If swaps slow down, randomness generation might be delayed. Adding a way to prompt or incentivize more frequent swaps could improve the reliability of the randomness.
Gas Costs:

Depending on the number of commits aggregated, the process could get gas-intensive, especially when working with a large backlog of commits. There may be opportunities to optimize the gas usage or batch transactions more efficiently to reduce costs for randomness requesters.
LP and Swapper Interaction:

The way rewards are distributed between swappers and LPs could be made more transparent or configurable by pool managers, allowing custom fee distribution models based on specific pool needs or strategies.
How to Use
Setup:

Deploy the Commit Miner Hook contract and integrate it with any Balancer V3 pools.
Specify the ERC20 token for payments and rewards.
Generate Randomness:

Call the requestRandomness() function, specifying the number of commits and the amount of payment.
Fee Distribution:

Fees are automatically split among swappers, LPs, and the hook deployer based on the specified fee percentages.
License
This project is licensed under the MIT License.

By using the Commit Miner Hook, you’re contributing to a decentralized randomness generation system while providing yield opportunities to liquidity providers and participants. It's an elegant solution for applications requiring a fair, transparent, and decentralized source of randomness.

This README outlines the key features, potential use cases, and considerations for developers working with the Commit Miner Hook contract.