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

Update to v3 deploy 8 #77

Merged
merged 8 commits into from
Sep 17, 2024
Merged
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,277 changes: 0 additions & 1,277 deletions packages/foundry/broadcast/Deploy.s.sol/11155111/run-latest.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";
import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol";
import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol";
import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";

/**
* @notice Impose an "exit fee" on a pool. The value of the fee is returned to the LPs.
Expand All @@ -36,49 +36,77 @@ import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/I
* Finally, since the only way to deposit fee tokens back into the pool balance (without minting new BPT) is through
* the special "donation" add liquidity type, this hook also requires that the pool support donation.
*/
contract ExitFeeHook is BaseHooks, Ownable {
contract ExitFeeHookExample is BaseHooks, VaultGuard, Ownable {
using FixedPoint for uint256;

// Percentages are represented as 18-decimal FP numbers, which have a maximum value of FixedPoint.ONE (100%),
// so 60 bits are sufficient.
uint64 public exitFeePercentage;
// Only pools deployed by the allowed factory may register
address private immutable _allowedFactory;

// Maximum exit fee of 10%
uint64 public constant MAX_EXIT_FEE_PERCENTAGE = 10e16;

/**
* @dev The exit fee cannot exceed the maximum allowed percentage.
* @notice A new `ExitFeeHookExample` contract has been registered successfully for a given factory and pool.
* @dev If the registration fails the call will revert, so there will be no event.
* @param hooksContract This contract
* @param pool The pool on which the hook was registered
*/
event ExitFeeHookExampleRegistered(address indexed hooksContract, address indexed pool);

/**
* @notice An exit fee has been charged on a pool.
* @param pool The pool that was charged
* @param token The address of the fee token
* @param feeAmount The amount of the fee (in native decimals)
*/
event ExitFeeCharged(address indexed pool, IERC20 indexed token, uint256 feeAmount);

/**
* @notice The exit fee has been changed in an `ExitFeeHookExample` contract.
* @dev Note that the initial fee will be zero, and no event is emitted on deployment.
* @param hookContract The contract whose fee changed
* @param exitFeePercentage The new exit fee percentage
*/
event ExitFeePercentageChanged(address indexed hookContract, uint256 exitFeePercentage);

/**
* @notice The exit fee cannot exceed the maximum allowed percentage.
* @param feePercentage The fee percentage exceeding the limit
* @param limit The maximum exit fee percentage
*/
error ExitFeeAboveLimit(uint256 feePercentage, uint256 limit);

/**
* @dev The pool does not support adding liquidity through donation.
* There is an existing similar error (IVaultErrors.DoesNotSupportDonation), but hooks should not throw
* @notice The pool does not support adding liquidity through donation.
* @dev There is an existing similar error (IVaultErrors.DoesNotSupportDonation), but hooks should not throw
* "Vault" errors.
*/
error PoolDoesNotSupportDonation();

constructor(IVault vault, address allowedFactory) BaseHooks(vault) Ownable(msg.sender) {
_allowedFactory = allowedFactory;
constructor(IVault vault) VaultGuard(vault) Ownable(msg.sender) {
// solhint-disable-previous-line no-empty-blocks
}

/// @inheritdoc IHooks
function onRegister(
address factory,
address,
address pool,
TokenConfig[] memory,
LiquidityManagement calldata liquidityManagement
) public view override onlyVault returns (bool) {
) public override onlyVault returns (bool) {
// NOTICE: In real hooks, make sure this function is properly implemented (e.g. check the factory, and check
// that the given pool is from the factory). Returning true unconditionally allows any pool, with any
// configuration, to use this hook.

// This hook requires donation support to work (see above).
if (liquidityManagement.enableDonation == false) {
revert PoolDoesNotSupportDonation();
}

return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool);
emit ExitFeeHookExampleRegistered(address(this), pool);

return true;
}

/// @inheritdoc IHooks
Expand Down Expand Up @@ -121,7 +149,9 @@ contract ExitFeeHook is BaseHooks, Ownable {
uint256 exitFee = amountsOutRaw[i].mulDown(exitFeePercentage);
accruedFees[i] = exitFee;
hookAdjustedAmountsOutRaw[i] -= exitFee;
// Fees don't need to be transferred to the hook, because donation will redeposit them in the vault.

emit ExitFeeCharged(pool, tokens[i], exitFee);
// Fees don't need to be transferred to the hook, because donation will redeposit them in the Vault.
// In effect, we will transfer a reduced amount of tokensOut to the caller, and leave the remainder
// in the pool balance.
}
Expand Down Expand Up @@ -153,5 +183,7 @@ contract ExitFeeHook is BaseHooks, Ownable {
revert ExitFeeAboveLimit(newExitFeePercentage, MAX_EXIT_FEE_PERCENTAGE);
}
exitFeePercentage = newExitFeePercentage;

emit ExitFeePercentageChanged(address(this), newExitFeePercentage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import {
TokenConfig,
HookFlags
} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { IBasePoolFactory } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePoolFactory.sol";

import { EnumerableMap } from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/EnumerableMap.sol";
import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";

import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol";
import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol";

/**
Expand All @@ -29,14 +28,13 @@ import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol";
* If the drawn number is not equal to the LUCKY_NUMBER, the user will pay fees to the hook contract. But, if the
* drawn number is equal to LUCKY_NUMBER, the user won't pay hook fees and will receive all fees accrued by the hook.
*/
contract LotteryHook is BaseHooks, Ownable {
contract LotteryHookExample is BaseHooks, VaultGuard, Ownable {
using FixedPoint for uint256;
using EnumerableMap for EnumerableMap.IERC20ToUint256Map;
using SafeERC20 for IERC20;

// Trusted router is needed since we rely on `getSender` to know which user should receive the prize.
address private immutable _trustedRouter;
// Only pools deployed by the allowed factory may register
address private immutable _allowedFactory;

// When calling `onAfterSwap`, a random number is generated. If the number is equal to LUCKY_NUMBER, the user will
Expand All @@ -54,20 +52,63 @@ contract LotteryHook is BaseHooks, Ownable {

uint256 private _counter = 0;

constructor(IVault vault, address allowedFactory, address router) BaseHooks(vault) Ownable(msg.sender) {
_allowedFactory = allowedFactory;
/**
* @notice A new `LotteryHookExample` contract has been registered successfully for a given factory and pool.
* @dev If the registration fails the call will revert, so there will be no event.
* @param hooksContract This contract
* @param pool The pool on which the hook was registered
*/
event LotteryHookExampleRegistered(address indexed hooksContract, address indexed pool);

/**
* @notice The swap hook fee percentage has been changed.
* @dev Note that the initial fee will be zero, and no event is emitted on deployment.
* @param hooksContract The hooks contract charging the fee
* @param hookFeePercentage The new hook swap fee percentage
*/
event HookSwapFeePercentageChanged(address indexed hooksContract, uint256 hookFeePercentage);

/**
* @notice Fee collected and added to the lottery pot.
* @dev The current user did not win the lottery.
* @param hooksContract This contract
* @param token The token in which the fee was collected
* @param feeAmount The amount of the fee collected
*/
event LotteryFeeCollected(address indexed hooksContract, IERC20 indexed token, uint256 feeAmount);

/**
* @notice Lottery proceeds were paid to a lottery winner.
* @param hooksContract This contract
* @param winner Address of the lottery winner
* @param token The token in which winnings were paid
* @param amountWon The amount of tokens won
*/
event LotteryWinningsPaid(
address indexed hooksContract,
address indexed winner,
IERC20 indexed token,
uint256 amountWon
);

constructor(IVault vault, address router) VaultGuard(vault) Ownable(msg.sender) {
_trustedRouter = router;
}

/// @inheritdoc IHooks
function onRegister(
address factory,
address,
address pool,
TokenConfig[] memory,
LiquidityManagement calldata
) public view override onlyVault returns (bool) {
// Only pools deployed by an allowed factory may register
return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool);
) public override onlyVault returns (bool) {
// NOTICE: In real hooks, make sure this function is properly implemented (e.g. check the factory, and check
// that the given pool is from the factory). Returning true unconditionally allows any pool, with any
// configuration, to use this hook.

emit LotteryHookExampleRegistered(address(this), pool);

return true;
}

/// @inheritdoc IHooks
Expand Down Expand Up @@ -138,6 +179,8 @@ contract LotteryHook is BaseHooks, Ownable {
*/
function setHookSwapFeePercentage(uint64 swapFeePercentage) external onlyOwner {
hookSwapFeePercentage = swapFeePercentage;

emit HookSwapFeePercentageChanged(address(this), swapFeePercentage);
}

/**
Expand Down Expand Up @@ -168,24 +211,35 @@ contract LotteryHook is BaseHooks, Ownable {
(IERC20 feeToken, ) = _tokensWithAccruedFees.at(i - 1);
_tokensWithAccruedFees.remove(feeToken);

// There are multiple reasons to use a direct transfer of hook fees to the user instead of hook
// adjusted amounts:
//
// * We can transfer all fees from all tokens
// * For EXACT_OUT transactions, the maximum prize we might give is amountsIn, because the maximum
// discount is 100%
// * We don't need to send tokens to the vault and then settle, which would be more expensive than
// transferring tokens to the user directly
feeToken.safeTransfer(user, feeToken.balanceOf(address(this)));
uint256 amountWon = feeToken.balanceOf(address(this));

if (amountWon > 0) {
// There are multiple reasons to use a direct transfer of hook fees to the user instead of hook
// adjusted amounts:
//
// * We can transfer all fees from all tokens.
// * For EXACT_OUT transactions, the maximum prize we might give is amountsIn, because the maximum
// discount is 100%.
// * We don't need to send tokens to the Vault and then settle, which would be more expensive than
// transferring tokens to the user directly.
feeToken.safeTransfer(user, amountWon);

emit LotteryWinningsPaid(address(this), user, feeToken, amountWon);
}
}
// Winner pays no fees.
return 0;
} else {
// Add token to the map of tokens with accrued fees.
_tokensWithAccruedFees.set(token, 1);

// Collect fees from the vault; user will pay them when the router settles the swap.
_vault.sendTo(token, address(this), hookFee);
if (hookFee > 0) {
// Collect fees from the Vault; the user will pay them when the router settles the swap.
_vault.sendTo(token, address(this), hookFee);

emit LotteryFeeCollected(address(this), token, hookFee);
}

return hookFee;
}
}
Expand Down
71 changes: 0 additions & 71 deletions packages/foundry/contracts/hooks/VeBALFeeDiscountHook.sol

This file was deleted.

Loading
Loading