From 4c1c4757c34ee9f951c41e51848d7736bce0eabd Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:03:35 +0530 Subject: [PATCH 01/20] Create DynamicLoyaltyBondHook.sol --- .../DynamicLoyaltyBondHook.sol | 411 ++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol new file mode 100644 index 00000000..2684e253 --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; +import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuard, ReentrancyGuard { + IVault public vault; + + enum BondTier { Explorer, Strategist, Veteran } + + struct TierInfo { + uint256 maturityPeriod; + uint256 swapFeeRefund; + uint256 multiplierBoost; + bool governanceAccess; + uint256 rewardRate; // Annual reward rate in basis points + string description; // Description of the tier benefits + } + + mapping(BondTier => TierInfo) public tierInfo; + mapping(address => BondTier) public userBondTier; + mapping(address => uint256) public userBondTimestamp; + mapping(address => uint256) public userBondAmount; + mapping(address => uint256) public userRewards; + + // Liquidity management variables + uint256 public penaltyPeriod = 7 days; + uint256 public baseFee = 100; // 1% in basis points + uint256 public maxFee = 300; // 3% + uint256 public earlyWithdrawalPenalty = 50; // 0.5% penalty for early removal + + event BondIssued(address indexed user, BondTier tier); + event LiquidityAdded(address indexed user, uint256 amount); + event LiquidityRemoved(address indexed user, uint256 amount, uint256 penalty); + event SwapFeeAdjusted(uint256 newFee); + event BondRegistered(address indexed pool); + event RewardsClaimed(address indexed user, uint256 amount); + event BondTierUpgraded(address indexed user, BondTier newTier); + event PenaltyApplied(address indexed user, uint256 penalty); + + constructor(IVault vaultInstance, string memory name, string memory symbol) + VaultGuard(vaultInstance) + ERC721(name, symbol) + { + vault = vaultInstance; + + // Initialize bond tiers + tierInfo[BondTier.Explorer] = TierInfo({ + maturityPeriod: 2 weeks, + swapFeeRefund: 5, // 5% + multiplierBoost: 0, + governanceAccess: false, + rewardRate: 100, // 1% + description: "Explorer Tier: Enjoy a modest reward and early access to new features." + }); + + tierInfo[BondTier.Strategist] = TierInfo({ + maturityPeriod: 2 months, + swapFeeRefund: 15, // 15% + multiplierBoost: 2, // 2x boost + governanceAccess: false, + rewardRate: 200, // 2% + description: "Strategist Tier: Higher rewards and enhanced multipliers." + }); + + tierInfo[BondTier.Veteran] = TierInfo({ + maturityPeriod: 6 months, + swapFeeRefund: 40, // 40% + multiplierBoost: 0, + governanceAccess: true, + rewardRate: 500, // 5% + description: "Veteran Tier: Full governance access and maximum rewards." + }); + } + + function issueBond(BondTier tier) external nonReentrant { + require(userBondTier[msg.sender] == BondTier.Explorer, "Bond already issued"); + userBondTier[msg.sender] = tier; + userBondTimestamp[msg.sender] = block.timestamp; + userBondAmount[msg.sender] = 1; // Set initial bond amount (1 token for simplicity) + + emit BondIssued(msg.sender, tier); + } + + function upgradeBondTier(BondTier newTier) external nonReentrant { + require(newTier > userBondTier[msg.sender], "New tier must be higher"); + require(block.timestamp >= userBondTimestamp[msg.sender] + tierInfo[userBondTier[msg.sender]].maturityPeriod, "Bond not matured"); + + userBondTier[msg.sender] = newTier; + userBondTimestamp[msg.sender] = block.timestamp; // Reset timestamp + emit BondTierUpgraded(msg.sender, newTier); + } + + function onRegister(address pool) external { + require(msg.sender == address(vault), "Unauthorized access"); + emit BondRegistered(pool); + } + + function onAfterAddLiquidity(address user, uint256 amount) external { + require(msg.sender == address(vault), "Only Vault can call"); + userBondTimestamp[user] = block.timestamp; + userBondAmount[user] += amount; // Increment bond amount + emit LiquidityAdded(user, amount); + } + + function onBeforeRemoveLiquidity(address user, uint256 amount) external { + require(msg.sender == address(vault), "Only Vault can call"); + + uint256 timeElapsed = block.timestamp - userBondTimestamp[user]; + uint256 penalty = 0; + + if (timeElapsed < penaltyPeriod) { + penalty = (amount * earlyWithdrawalPenalty) / 10000; // Apply penalty + userBondAmount[user] -= penalty; // Adjust bond amount + emit PenaltyApplied(user, penalty); + } + + emit LiquidityRemoved(user, amount, penalty); + } + + function claimRewards() external nonReentrant { + uint256 rewards = calculateRewards(msg.sender); + userRewards[msg.sender] += rewards; // Update user rewards + emit RewardsClaimed(msg.sender, rewards); + } + + function calculateRewards(address user) internal view returns (uint256) { + uint256 timeHeld = block.timestamp - userBondTimestamp[user]; + uint256 rate = tierInfo[userBondTier[user]].rewardRate; + + // Calculate rewards based on time held and rate + return (userBondAmount[user] * rate * timeHeld) / (365 days * 10000); + } + + function onComputeDynamicSwapFeePercentage() external view returns (uint256) { + uint256 poolUtilization = _getPoolUtilization(); + uint256 volatilityFactor = _getMarketVolatilityFactor(); + + // Example logic: increase fees during high utilization and volatility + uint256 newFee = baseFee + (poolUtilization / 10) + volatilityFactor; + + if (newFee > maxFee) { + newFee = maxFee; + } + + emit SwapFeeAdjusted(newFee); + return newFee; + } + + function _getMarketVolatilityFactor() internal view returns (uint256) { + // Mock logic to simulate volatility based on some metrics. + uint256 marketSentiment = getMarketSentiment(); // Replace with actual data source + uint256 tradingVolume = getTradingVolume(); // Replace with actual data source + + // Increase factor based on market sentiment and trading volume + if (marketSentiment > 70) { + return 50; // 0.5% increase + } else if (tradingVolume > 1000 ether) { + return 30; // 0.3% increase + } else { + return 10; // 0.1% increase + } + } + + function getMarketSentiment() internal view returns (uint256) { + // Mock sentiment retrieval logic. Replace with real market data. + return 80; // Example: 80% positive sentiment + } + + function getTradingVolume() internal view returns (uint256) { + // Mock trading volume retrieval logic. Replace with real market data. + return 1500 ether; // Example: 1500 Ether traded + } + + function _getPoolUtilization() internal view returns (uint256) { + // Example pool metrics. Replace with actual logic. + uint256 totalLiquidity = vault.getTotalLiquidity(); // Total assets in the pool + uint256 totalAssets = vault.getTotalAssets(); // Total assets under management + + if (totalLiquidity == 0) { + return 0; // Prevent division by zero + } + + uint256 utilization = (totalAssets * 10000) / totalLiquidity; // Return in basis points (0-10000) + return utilization; + } + + function getBondPerks(address user) external view returns (TierInfo memory) { + return tierInfo[userBondTier[user]]; + } + + function isGovernanceEligible(address user) external view returns (bool, string memory) { + TierInfo memory tier = tierInfo[userBondTier[user]]; + return (tier.governanceAccess, tier.description); // Return governance eligibility and description + } +} +``Here's the complete `DynamicLoyaltyBondHook` contract with enhanced functionality. The improvements to the specified functions provide a more robust structure, allowing for future scalability and integration with real-world data sources: + +### Enhanced `DynamicLoyaltyBondHook` Contract + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; +import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; +import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuard, ReentrancyGuard { + IVault public vault; + + enum BondTier { Explorer, Strategist, Veteran } + + struct TierInfo { + uint256 maturityPeriod; + uint256 swapFeeRefund; + uint256 multiplierBoost; + bool governanceAccess; + uint256 rewardRate; // Annual reward rate in basis points + string description; // Description of the tier benefits + } + + mapping(BondTier => TierInfo) public tierInfo; + mapping(address => BondTier) public userBondTier; + mapping(address => uint256) public userBondTimestamp; + mapping(address => uint256) public userBondAmount; + mapping(address => uint256) public userRewards; + + // Liquidity management variables + uint256 public penaltyPeriod = 7 days; + uint256 public baseFee = 100; // 1% in basis points + uint256 public maxFee = 300; // 3% + uint256 public earlyWithdrawalPenalty = 50; // 0.5% penalty for early removal + + event BondIssued(address indexed user, BondTier tier); + event LiquidityAdded(address indexed user, uint256 amount); + event LiquidityRemoved(address indexed user, uint256 amount, uint256 penalty); + event SwapFeeAdjusted(uint256 newFee); + event BondRegistered(address indexed pool); + event RewardsClaimed(address indexed user, uint256 amount); + event BondTierUpgraded(address indexed user, BondTier newTier); + event PenaltyApplied(address indexed user, uint256 penalty); + + constructor(IVault vaultInstance, string memory name, string memory symbol) + VaultGuard(vaultInstance) + ERC721(name, symbol) + { + vault = vaultInstance; + + // Initialize bond tiers + tierInfo[BondTier.Explorer] = TierInfo({ + maturityPeriod: 2 weeks, + swapFeeRefund: 5, // 5% + multiplierBoost: 0, + governanceAccess: false, + rewardRate: 100, // 1% + description: "Explorer Tier: Enjoy a modest reward and early access to new features." + }); + + tierInfo[BondTier.Strategist] = TierInfo({ + maturityPeriod: 2 months, + swapFeeRefund: 15, // 15% + multiplierBoost: 2, // 2x boost + governanceAccess: false, + rewardRate: 200, // 2% + description: "Strategist Tier: Higher rewards and enhanced multipliers." + }); + + tierInfo[BondTier.Veteran] = TierInfo({ + maturityPeriod: 6 months, + swapFeeRefund: 40, // 40% + multiplierBoost: 0, + governanceAccess: true, + rewardRate: 500, // 5% + description: "Veteran Tier: Full governance access and maximum rewards." + }); + } + + function issueBond(BondTier tier) external nonReentrant { + require(userBondTier[msg.sender] == BondTier.Explorer, "Bond already issued"); + userBondTier[msg.sender] = tier; + userBondTimestamp[msg.sender] = block.timestamp; + userBondAmount[msg.sender] = 1; // Set initial bond amount (1 token for simplicity) + + emit BondIssued(msg.sender, tier); + } + + function upgradeBondTier(BondTier newTier) external nonReentrant { + require(newTier > userBondTier[msg.sender], "New tier must be higher"); + require(block.timestamp >= userBondTimestamp[msg.sender] + tierInfo[userBondTier[msg.sender]].maturityPeriod, "Bond not matured"); + + userBondTier[msg.sender] = newTier; + userBondTimestamp[msg.sender] = block.timestamp; // Reset timestamp + emit BondTierUpgraded(msg.sender, newTier); + } + + function onRegister(address pool) external { + require(msg.sender == address(vault), "Unauthorized access"); + emit BondRegistered(pool); + } + + function onAfterAddLiquidity(address user, uint256 amount) external { + require(msg.sender == address(vault), "Only Vault can call"); + userBondTimestamp[user] = block.timestamp; + userBondAmount[user] += amount; // Increment bond amount + emit LiquidityAdded(user, amount); + } + + function onBeforeRemoveLiquidity(address user, uint256 amount) external { + require(msg.sender == address(vault), "Only Vault can call"); + + uint256 timeElapsed = block.timestamp - userBondTimestamp[user]; + uint256 penalty = 0; + + if (timeElapsed < penaltyPeriod) { + penalty = (amount * earlyWithdrawalPenalty) / 10000; // Apply penalty + userBondAmount[user] -= penalty; // Adjust bond amount + emit PenaltyApplied(user, penalty); + } + + emit LiquidityRemoved(user, amount, penalty); + } + + function claimRewards() external nonReentrant { + uint256 rewards = calculateRewards(msg.sender); + userRewards[msg.sender] += rewards; // Update user rewards + emit RewardsClaimed(msg.sender, rewards); + } + + function calculateRewards(address user) internal view returns (uint256) { + uint256 timeHeld = block.timestamp - userBondTimestamp[user]; + uint256 rate = tierInfo[userBondTier[user]].rewardRate; + + // Calculate rewards based on time held and rate + return (userBondAmount[user] * rate * timeHeld) / (365 days * 10000); + } + + function onComputeDynamicSwapFeePercentage() external view returns (uint256) { + uint256 poolUtilization = _getPoolUtilization(); + uint256 volatilityFactor = _getMarketVolatilityFactor(); + + // Example logic: increase fees during high utilization and volatility + uint256 newFee = baseFee + (poolUtilization / 10) + volatilityFactor; + + if (newFee > maxFee) { + newFee = maxFee; + } + + emit SwapFeeAdjusted(newFee); + return newFee; + } + + function _getMarketVolatilityFactor() internal view returns (uint256) { + // Mock logic to simulate volatility based on some metrics. + uint256 marketSentiment = getMarketSentiment(); // Replace with actual data source + uint256 tradingVolume = getTradingVolume(); // Replace with actual data source + + // Increase factor based on market sentiment and trading volume + if (marketSentiment > 70) { + return 50; // 0.5% increase + } else if (tradingVolume > 1000 ether) { + return 30; // 0.3% increase + } else { + return 10; // 0.1% increase + } + } + + function getMarketSentiment() internal view returns (uint256) { + // Mock sentiment retrieval logic. Replace with real market data. + return 80; // Example: 80% positive sentiment + } + + function getTradingVolume() internal view returns (uint256) { + // Mock trading volume retrieval logic. Replace with real market data. + return 1500 ether; // Example: 1500 Ether traded + } + + function _getPoolUtilization() internal view returns (uint256) { + // Example pool metrics. Replace with actual logic. + uint256 totalLiquidity = vault.getTotalLiquidity(); // Total assets in the pool + uint256 totalAssets = vault.getTotalAssets(); // Total assets under management + + if (totalLiquidity == 0) { + return 0; // Prevent division by zero + } + + uint256 utilization = (totalAssets * 10000) / totalLiquidity; // Return in basis points (0-10000) + return utilization; + } + + function getBondPerks(address user) external view returns (TierInfo memory) { + return tierInfo[userBondTier[user]]; + } + + function isGovernanceEligible(address user) external view returns (bool, string memory) { + TierInfo memory tier = tierInfo[userBondTier[user]]; + return (tier.governanceAccess, tier.description); // Return governance eligibility and description + } +} From 173b95b07ccf8092e9e6e5e21ab5b0db4fcd7ee0 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:22:06 +0530 Subject: [PATCH 02/20] Update DynamicLoyaltyBondHook.sol --- .../DynamicLoyaltyBondHook.sol | 259 ++++-------------- 1 file changed, 55 insertions(+), 204 deletions(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 2684e253..9ea776c0 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -9,6 +9,12 @@ import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol" import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { + LiquidityManagement, + TokenConfig, + PoolSwapParams, + HookFlags +} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuard, ReentrancyGuard { IVault public vault; @@ -192,220 +198,65 @@ contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuar return utilization; } + // Function to get the perks associated with the user's current bond tier function getBondPerks(address user) external view returns (TierInfo memory) { - return tierInfo[userBondTier[user]]; + // Retrieve the bond tier for the user + BondTier userTier = userBondTier[user]; + // Ensure the user has a bond tier assigned + require(userTier != BondTier.Explorer, "User has not issued a bond yet"); + + // Return the tier information, including all associated benefits and attributes + return tierInfo[userTier]; } - + + // Function to check if a user is eligible for governance and to provide tier description function isGovernanceEligible(address user) external view returns (bool, string memory) { + // Get the tier information for the user TierInfo memory tier = tierInfo[userBondTier[user]]; - return (tier.governanceAccess, tier.description); // Return governance eligibility and description - } -} -``Here's the complete `DynamicLoyaltyBondHook` contract with enhanced functionality. The improvements to the specified functions provide a more robust structure, allowing for future scalability and integration with real-world data sources: - -### Enhanced `DynamicLoyaltyBondHook` Contract - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; -import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; -import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; -import { VaultGuard } from "@balancer-labs/v3-vault/contracts/VaultGuard.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuard, ReentrancyGuard { - IVault public vault; - - enum BondTier { Explorer, Strategist, Veteran } - struct TierInfo { - uint256 maturityPeriod; - uint256 swapFeeRefund; - uint256 multiplierBoost; - bool governanceAccess; - uint256 rewardRate; // Annual reward rate in basis points - string description; // Description of the tier benefits - } - - mapping(BondTier => TierInfo) public tierInfo; - mapping(address => BondTier) public userBondTier; - mapping(address => uint256) public userBondTimestamp; - mapping(address => uint256) public userBondAmount; - mapping(address => uint256) public userRewards; - - // Liquidity management variables - uint256 public penaltyPeriod = 7 days; - uint256 public baseFee = 100; // 1% in basis points - uint256 public maxFee = 300; // 3% - uint256 public earlyWithdrawalPenalty = 50; // 0.5% penalty for early removal - - event BondIssued(address indexed user, BondTier tier); - event LiquidityAdded(address indexed user, uint256 amount); - event LiquidityRemoved(address indexed user, uint256 amount, uint256 penalty); - event SwapFeeAdjusted(uint256 newFee); - event BondRegistered(address indexed pool); - event RewardsClaimed(address indexed user, uint256 amount); - event BondTierUpgraded(address indexed user, BondTier newTier); - event PenaltyApplied(address indexed user, uint256 penalty); - - constructor(IVault vaultInstance, string memory name, string memory symbol) - VaultGuard(vaultInstance) - ERC721(name, symbol) - { - vault = vaultInstance; - - // Initialize bond tiers - tierInfo[BondTier.Explorer] = TierInfo({ - maturityPeriod: 2 weeks, - swapFeeRefund: 5, // 5% - multiplierBoost: 0, - governanceAccess: false, - rewardRate: 100, // 1% - description: "Explorer Tier: Enjoy a modest reward and early access to new features." - }); - - tierInfo[BondTier.Strategist] = TierInfo({ - maturityPeriod: 2 months, - swapFeeRefund: 15, // 15% - multiplierBoost: 2, // 2x boost - governanceAccess: false, - rewardRate: 200, // 2% - description: "Strategist Tier: Higher rewards and enhanced multipliers." - }); - - tierInfo[BondTier.Veteran] = TierInfo({ - maturityPeriod: 6 months, - swapFeeRefund: 40, // 40% - multiplierBoost: 0, - governanceAccess: true, - rewardRate: 500, // 5% - description: "Veteran Tier: Full governance access and maximum rewards." - }); - } - - function issueBond(BondTier tier) external nonReentrant { - require(userBondTier[msg.sender] == BondTier.Explorer, "Bond already issued"); - userBondTier[msg.sender] = tier; - userBondTimestamp[msg.sender] = block.timestamp; - userBondAmount[msg.sender] = 1; // Set initial bond amount (1 token for simplicity) - - emit BondIssued(msg.sender, tier); - } - - function upgradeBondTier(BondTier newTier) external nonReentrant { - require(newTier > userBondTier[msg.sender], "New tier must be higher"); - require(block.timestamp >= userBondTimestamp[msg.sender] + tierInfo[userBondTier[msg.sender]].maturityPeriod, "Bond not matured"); - - userBondTier[msg.sender] = newTier; - userBondTimestamp[msg.sender] = block.timestamp; // Reset timestamp - emit BondTierUpgraded(msg.sender, newTier); + // Return a boolean indicating governance eligibility and a description of the tier benefits + return (tier.governanceAccess, tier.description); } - - function onRegister(address pool) external { - require(msg.sender == address(vault), "Unauthorized access"); - emit BondRegistered(pool); - } - - function onAfterAddLiquidity(address user, uint256 amount) external { - require(msg.sender == address(vault), "Only Vault can call"); - userBondTimestamp[user] = block.timestamp; - userBondAmount[user] += amount; // Increment bond amount - emit LiquidityAdded(user, amount); + + // Function to return a unique identifier for the hook, useful for governance proposals + function getHookFlags() external view returns (bytes32) { + // Generate a unique identifier based on the hook's name + return keccak256(abi.encodePacked("DynamicLoyaltyBondHook")); } - - function onBeforeRemoveLiquidity(address user, uint256 amount) external { - require(msg.sender == address(vault), "Only Vault can call"); + + // Function to retrieve the current bond tier of a user + function getCurrentBondTier(address user) external view returns (BondTier) { + // Return the current bond tier assigned to the user + BondTier currentTier = userBondTier[user]; - uint256 timeElapsed = block.timestamp - userBondTimestamp[user]; - uint256 penalty = 0; - - if (timeElapsed < penaltyPeriod) { - penalty = (amount * earlyWithdrawalPenalty) / 10000; // Apply penalty - userBondAmount[user] -= penalty; // Adjust bond amount - emit PenaltyApplied(user, penalty); - } - - emit LiquidityRemoved(user, amount, penalty); - } - - function claimRewards() external nonReentrant { - uint256 rewards = calculateRewards(msg.sender); - userRewards[msg.sender] += rewards; // Update user rewards - emit RewardsClaimed(msg.sender, rewards); + // If the user has not issued a bond yet, return a default tier + require(currentTier != BondTier.Explorer, "User has not issued a bond yet"); + + return currentTier; // Return the bond tier for the specified user } - - function calculateRewards(address user) internal view returns (uint256) { - uint256 timeHeld = block.timestamp - userBondTimestamp[user]; - uint256 rate = tierInfo[userBondTier[user]].rewardRate; + + // Function to get the total rewards accumulated for a user + function getTotalRewards(address user) external view returns (uint256) { + // Retrieve and return the total rewards that the user has accumulated + uint256 totalRewards = userRewards[user]; + + // Ensure that the user has rewards before returning + require(totalRewards > 0, "No rewards accumulated yet"); - // Calculate rewards based on time held and rate - return (userBondAmount[user] * rate * timeHeld) / (365 days * 10000); + return totalRewards; // Return the total rewards accumulated by the user } - - function onComputeDynamicSwapFeePercentage() external view returns (uint256) { - uint256 poolUtilization = _getPoolUtilization(); - uint256 volatilityFactor = _getMarketVolatilityFactor(); - - // Example logic: increase fees during high utilization and volatility - uint256 newFee = baseFee + (poolUtilization / 10) + volatilityFactor; - - if (newFee > maxFee) { - newFee = maxFee; - } - - emit SwapFeeAdjusted(newFee); - return newFee; - } - - function _getMarketVolatilityFactor() internal view returns (uint256) { - // Mock logic to simulate volatility based on some metrics. - uint256 marketSentiment = getMarketSentiment(); // Replace with actual data source - uint256 tradingVolume = getTradingVolume(); // Replace with actual data source - - // Increase factor based on market sentiment and trading volume - if (marketSentiment > 70) { - return 50; // 0.5% increase - } else if (tradingVolume > 1000 ether) { - return 30; // 0.3% increase - } else { - return 10; // 0.1% increase - } - } - - function getMarketSentiment() internal view returns (uint256) { - // Mock sentiment retrieval logic. Replace with real market data. - return 80; // Example: 80% positive sentiment - } - - function getTradingVolume() internal view returns (uint256) { - // Mock trading volume retrieval logic. Replace with real market data. - return 1500 ether; // Example: 1500 Ether traded - } - - function _getPoolUtilization() internal view returns (uint256) { - // Example pool metrics. Replace with actual logic. - uint256 totalLiquidity = vault.getTotalLiquidity(); // Total assets in the pool - uint256 totalAssets = vault.getTotalAssets(); // Total assets under management - - if (totalLiquidity == 0) { - return 0; // Prevent division by zero - } - - uint256 utilization = (totalAssets * 10000) / totalLiquidity; // Return in basis points (0-10000) - return utilization; - } - - function getBondPerks(address user) external view returns (TierInfo memory) { - return tierInfo[userBondTier[user]]; - } - - function isGovernanceEligible(address user) external view returns (bool, string memory) { - TierInfo memory tier = tierInfo[userBondTier[user]]; - return (tier.governanceAccess, tier.description); // Return governance eligibility and description + + // Function to get the bond status of a user, including tier, timestamp, and amount + function getBondStatus(address user) external view returns (BondTier, uint256, uint256) { + // Retrieve the user's bond tier, timestamp, and amount + BondTier userTier = userBondTier[user]; + uint256 bondTimestamp = userBondTimestamp[user]; + uint256 bondAmount = userBondAmount[user]; + + // Ensure that the user has a bond before returning the status + require(userTier != BondTier.Explorer, "User has not issued a bond yet"); + + // Return the bond tier, timestamp, and amount for the specified user + return (userTier, bondTimestamp, bondAmount); } } From d98a189354c0904e91f6d6c6ad1be58f7f278d63 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:26:56 +0530 Subject: [PATCH 03/20] Update DynamicLoyaltyBondHook.sol --- .../DynamicLoyaltyBondHook.sol | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 9ea776c0..155dca0c 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -16,7 +16,82 @@ import { HookFlags } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; -contract DynamicLoyaltyBondHook is IHooks, BaseHooks, ERC721, Ownable, VaultGuard, ReentrancyGuard { +/*contract DynamicLoyaltyBondHook is Ownable, ReentrancyGuard { + IVault public vault; + uint256 public penaltyPeriod = 7 days; + uint256 public baseFee = 100; // 1% in basis points + uint256 public maxFee = 300; // 3% + uint256 public earlyWithdrawalPenalty = 50; // 0.5% + + event BondRegistered(address indexed pool); + event LiquidityAdded(address indexed user, uint256 amount); + event LiquidityRemoved(address indexed user, uint256 amount, uint256 penalty); + event SwapFeeAdjusted(uint256 newFee); + + mapping(address => uint256) public bondTimestamp; + mapping(address => uint256) public userRewards; + + constructor(IVault _vault) { + vault = _vault; + } + + function onRegister(address pool) external onlyOwner { + emit BondRegistered(pool); + } + + function onAfterAddLiquidity(address user, uint256 amount) external nonReentrant { + require(msg.sender == address(vault), "Only Vault can call"); + bondTimestamp[user] = block.timestamp; + emit LiquidityAdded(user, amount); + } + + function onBeforeRemoveLiquidity(address user, uint256 amount) external nonReentrant { + require(msg.sender == address(vault), "Only Vault can call"); + + uint256 timeElapsed = block.timestamp - bondTimestamp[user]; + uint256 penalty = 0; + + if (timeElapsed < penaltyPeriod) { + penalty = (amount * earlyWithdrawalPenalty) / 10000; + require(IERC20(address(vault)).transfer(address(vault), penalty), + "Penalty transfer failed"); + } + + emit LiquidityRemoved(user, amount, penalty); + } + + function claimRewards(address user) external nonReentrant { + uint256 rewards = userRewards[user]; + require(rewards > 0, "No rewards available"); + userRewards[user] = 0; + IERC20(address(vault)).transfer(user, rewards); + } + + function onComputeDynamicSwapFeePercentage() external view returns (uint256) { + uint256 utilization = _getPoolUtilization(); + uint256 volatility = _getMarketVolatilityFactor(); + uint256 newFee = baseFee + (utilization / 10) + volatility; + + if (newFee > maxFee) newFee = maxFee; + emit SwapFeeAdjusted(newFee); + return newFee; + } + + function _getMarketVolatilityFactor() internal view returns (uint256) { + return 25; // Adds 0.25% to fee + } + + function _getPoolUtilization() internal view returns (uint256) { + return 750; // Example: 75% utilization in basis points + }*/ + +contract DynamicLoyaltyBondHook is +IHooks, +BaseHooks, +ERC721, +Ownable, +VaultGuard, +ReentrancyGuard { IVault public vault; enum BondTier { Explorer, Strategist, Veteran } From 9bf3ac33a4c33809bb5e98e38f5158e2065e03c0 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:40:00 +0530 Subject: [PATCH 04/20] Create LoyaltyBondStructure.sol --- .../contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol @@ -0,0 +1 @@ + From 90bb2aa5e8b4c94d7090732b9d9bcee6501632e2 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:40:19 +0530 Subject: [PATCH 05/20] Update LoyaltyBondStructure.sol --- .../DynamicBondHook/LoyaltyBondStructure.sol | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol index 8b137891..ea30de4c 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol @@ -1 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; +import "@openzeppelin/contracts/utils/Counters.sol"; +import "./IDynamicBondHook.sol"; + +contract DynamicBondMetadata { + using Counters for Counters.Counter; + + // Mapping to store metadata for each tier + mapping(IDynamicBondHook.BondTier => IDynamicBondHook.TierInfo) public tierInfo; + + constructor() { + // Initialize tier info with new names and details + tierInfo[IDynamicBondHook.BondTier.ExplorerBond] = IDynamicBondHook.TierInfo({ + maturityPeriod: 2 weeks, + swapFeeRefund: 5, + multiplierBoost: 0, // No multiplier for this tier + governanceAccess: false, + rewardRate: 100, // Example value; adjust as needed + description: "Explorer Bond: 5% refund on swap fees, entry into Balancer’s weekly lotteries." + }); + + tierInfo[IDynamicBondHook.BondTier.StrategistBond] = IDynamicBondHook.TierInfo({ + maturityPeriod: 2 months, + swapFeeRefund: 15, + multiplierBoost: 2, // 2x multiplier during low-liquidity phases + governanceAccess: false, + rewardRate: 200, // Example value; adjust as needed + description: "Strategist Bond: 15% fee refund, 2x multiplier boost during low-liquidity phases." + }); + + tierInfo[IDynamicBondHook.BondTier.VeteranBond] = IDynamicBondHook.TierInfo({ + maturityPeriod: 6 months, + swapFeeRefund: 40, + multiplierBoost: 0, // No multiplier for this tier + governanceAccess: true, + rewardRate: 300, // Example value; adjust as needed + description: "Veteran Bond: 40% fee refund, seasonal bonuses, governance access boost." + }); + } + + // Function to get tier info + function getTierInfo(IDynamicBondHook.BondTier tier) external view returns (IDynamicBondHook.TierInfo memory) { + return tierInfo[tier]; + } +} From be5578fcf2b5dd054772c906708a8a90c0e999f1 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:42:15 +0530 Subject: [PATCH 06/20] Update DynamicLoyaltyBondHook.sol --- .../contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 155dca0c..03d56e96 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; From 4ac2072b5301936a41fd1c51a07f10d6e3357383 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:42:37 +0530 Subject: [PATCH 07/20] Update LoyaltyBondStructure.sol --- .../contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol index ea30de4c..bbb9653f 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/utils/Counters.sol"; From da17eb0531c9a26bf8b6ee553a50fb2214205d8b Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:45:57 +0530 Subject: [PATCH 08/20] Create IDynamicLoyaltyBondHook.sol --- .../script/IDynamicLoyaltyBondHook.sol | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol new file mode 100644 index 00000000..5ab270a9 --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.8.24; + +interface IDynamicBondHook { + enum BondTier { + ExplorerBond, + StrategistBond, + VeteranBond + } + + struct TierInfo { + uint256 maturityPeriod; + uint256 swapFeeRefund; + uint256 multiplierBoost; + bool governanceAccess; + uint256 rewardRate; + string description; // A description for the tier + } + + // Function to register a bond + function registerBond(address user, BondTier tier, uint256 amount) external; + + // Function to get bond perks + function getBondPerks(address user) external view returns (TierInfo memory); + + // Function to check governance eligibility + function isGovernanceEligible(address user) external view returns (bool, string memory); + + // Function to adjust tier parameters + function setTierInfo( + BondTier tier, + uint256 maturityPeriod, + uint256 swapFeeRefund, + uint256 multiplierBoost, + bool governanceAccess, + uint256 rewardRate + ) external; + + // Additional functionality as per your DynamicBondHook requirements +} From 34eb58fb5fe10c196583a31d7ff1186a2bd3ee02 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:48:08 +0530 Subject: [PATCH 09/20] Update DynamicLoyaltyBondHook.sol --- .../contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 03d56e96..4bb865e8 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; From 617a5a2fb18b9066e28487583637eb29b35af393 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:49:26 +0530 Subject: [PATCH 10/20] Update IDynamicLoyaltyBondHook.sol --- .../script/IDynamicLoyaltyBondHook.sol | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol index 5ab270a9..f53d3f14 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/script/IDynamicLoyaltyBondHook.sol @@ -1,39 +1,37 @@ pragma solidity ^0.8.24; interface IDynamicBondHook { - enum BondTier { - ExplorerBond, - StrategistBond, - VeteranBond + struct BondInfo { + uint256 bondAmount; // Amount of bond staked by the user + uint256 bondStartTime; // Timestamp when the bond was started + uint256 lastRewardClaimed; // Last time the user claimed rewards + uint256 currentTier; // Current bond tier of the user + uint256 maturityTime; // Time until the bond matures } struct TierInfo { - uint256 maturityPeriod; - uint256 swapFeeRefund; - uint256 multiplierBoost; - bool governanceAccess; - uint256 rewardRate; - string description; // A description for the tier + uint256 maturityPeriod; // Duration for maturity + uint256 swapFeeRefund; // Percentage of swap fee refunded + uint256 multiplierBoost; // Multiplier for rewards during specific phases + bool governanceAccess; // Eligibility for governance + uint256 rewardRate; // Rate at which rewards are accumulated + string description; // Description of the tier benefits } - // Function to register a bond - function registerBond(address user, BondTier tier, uint256 amount) external; + event BondCreated(address indexed user, uint256 amount, uint256 tier); + event BondClaimed(address indexed user, uint256 rewards); - // Function to get bond perks - function getBondPerks(address user) external view returns (TierInfo memory); + function bondInfo(address user) external view returns ( + uint256 bondAmount, + uint256 bondStartTime, + uint256 lastRewardClaimed, + uint256 currentTier, + uint256 maturityTime + ); - // Function to check governance eligibility - function isGovernanceEligible(address user) external view returns (bool, string memory); - - // Function to adjust tier parameters - function setTierInfo( - BondTier tier, - uint256 maturityPeriod, - uint256 swapFeeRefund, - uint256 multiplierBoost, - bool governanceAccess, - uint256 rewardRate - ) external; - - // Additional functionality as per your DynamicBondHook requirements + function getTierInfo(uint256 tier) external view returns (TierInfo memory); + function calculateRewards(address user) external view returns (uint256); + function claimRewards(address user) external returns (uint256); + function getCurrentBondTier(address user) external view returns (uint256); + function getFeeDiscount(address user) external view returns (uint256); } From 8ff72603454ac9cbd81e927837afb80dfff9f4ee Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 17:54:55 +0530 Subject: [PATCH 11/20] Create BadgeToken.sol --- .../hooks/DynamicBondHook/BadgeToken.sol | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/BadgeToken.sol diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/BadgeToken.sol b/packages/foundry/contracts/hooks/DynamicBondHook/BadgeToken.sol new file mode 100644 index 00000000..4ff6bfbc --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/BadgeToken.sol @@ -0,0 +1,37 @@ +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract RewardToken is ERC20 { + address public admin; + + // Mapping to track rewards for each user + mapping(address => uint256) public userRewards; + + event RewardsDistributed(address indexed user, uint256 amount); + + constructor() ERC20("BadgeToken", "RWD") { + admin = msg.sender; // Assign the contract deployer as admin + } + + modifier onlyAdmin() { + require(msg.sender == admin, "Not admin"); + _; + } + + // Function to mint rewards for a user + function distributeRewards(address user, uint256 amount) external onlyAdmin { + userRewards[user] += amount; + _mint(user, amount); // Mint the specified amount of tokens + emit RewardsDistributed(user, amount); + } + + // Function to claim rewards + function claimRewards() external { + uint256 amount = userRewards[msg.sender]; + require(amount > 0, "No rewards to claim"); + + userRewards[msg.sender] = 0; // Reset user rewards + _mint(msg.sender, amount); // Mint the tokens to the user + } +} From c31fff88adf8d5d7e8b7577586a4ea1944b5ef56 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:35:12 +0530 Subject: [PATCH 12/20] Update DynamicLoyaltyBondHook.sol --- .../DynamicLoyaltyBondHook.sol | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 4bb865e8..e0a0778a 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -84,6 +84,7 @@ import { return 750; // Example: 75% utilization in basis points }*/ + contract DynamicLoyaltyBondHook is IHooks, BaseHooks, @@ -93,7 +94,7 @@ VaultGuard, ReentrancyGuard { IVault public vault; - enum BondTier { Explorer, Strategist, Veteran } + enum BondTier { None, Explorer, Strategist, Veteran } struct TierInfo { uint256 maturityPeriod; @@ -211,6 +212,36 @@ ReentrancyGuard { emit RewardsClaimed(msg.sender, rewards); } + function getBondBenefits() public view returns (BondTier[] memory tiers, uint256[] memory maturityPeriods, + uint256[] memory swapFeeRefunds, uint256[] memory multipliers, + string[] memory descriptions) { + tiers = new BondTier maturityPeriods = new uint256 ; + swapFeeRefunds = new uint256 ; + multipliers = new uint256 ; + descriptions = new string ; + + // A bond tier details + tiers[0] = BondTier.ExplorerBond; + maturityPeriods[0] = tierInfo[BondTier.ExplorerBond].maturityPeriod; + swapFeeRefunds[0] = tierInfo[BondTier.ExplorerBond].swapFeeRefund; + multipliers[0] = tierInfo[BondTier.ExplorerBond].multiplierBoost; + descriptions[0] = tierInfo[BondTier.ExplorerBond].description; + + tiers[1] = BondTier.StrategistBond; + maturityPeriods[1] = tierInfo[BondTier.StrategistBond].maturityPeriod; + swapFeeRefunds[1] = tierInfo[BondTier.StrategistBond].swapFeeRefund; + multipliers[1] = tierInfo[BondTier.StrategistBond].multiplierBoost; + descriptions[1] = tierInfo[BondTier.StrategistBond].description; + + tiers[2] = BondTier.VeteranBond; + maturityPeriods[2] = tierInfo[BondTier.VeteranBond].maturityPeriod; + swapFeeRefunds[2] = tierInfo[BondTier.VeteranBond].swapFeeRefund; + multipliers[2] = tierInfo[BondTier.VeteranBond].multiplierBoost; + descriptions[2] = tierInfo[BondTier.VeteranBond].description; + + return (tiers, maturityPeriods, swapFeeRefunds, multipliers, descriptions); + } + function calculateRewards(address user) internal view returns (uint256) { uint256 timeHeld = block.timestamp - userBondTimestamp[user]; uint256 rate = tierInfo[userBondTier[user]].rewardRate; From 0ef52cc140cd19c478e504881423e1c1c5185d6c Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:40:00 +0530 Subject: [PATCH 13/20] Update DynamicLoyaltyBondHook.sol --- .../hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index e0a0778a..00c13da4 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -15,6 +15,11 @@ import { HookFlags } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import { IDynamicLoyaltyBondHook } from "./script/IDynamicLoyaltyBondHook.sol"; +import "./LoyaltyBondStructure.sol"; +import "./NFTGovernor.sol"; +import "./BadgeToken.sol"; + /*contract DynamicLoyaltyBondHook is Ownable, ReentrancyGuard { IVault public vault; uint256 public penaltyPeriod = 7 days; From 8c5b1228f6b0704f1d29a2df45963c0a8828b87e Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:43:40 +0530 Subject: [PATCH 14/20] Update DynamicLoyaltyBondHook.sol --- .../contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol index 00c13da4..44ddcbcf 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol @@ -17,7 +17,7 @@ import { import { IDynamicLoyaltyBondHook } from "./script/IDynamicLoyaltyBondHook.sol"; import "./LoyaltyBondStructure.sol"; -import "./NFTGovernor.sol"; +import "./AccessControl.sol"; import "./BadgeToken.sol"; /*contract DynamicLoyaltyBondHook is Ownable, ReentrancyGuard { From 77496fe21247d6c1f797868dca5963aacb97d7f9 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:44:07 +0530 Subject: [PATCH 15/20] Create AccessControl.sol --- .../foundry/contracts/hooks/DynamicBondHook/AccessControl.sol | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol b/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol @@ -0,0 +1 @@ + From 8bfcfc16ac2b93f93d4e9667d1c1eec2d9e4af3c Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:44:29 +0530 Subject: [PATCH 16/20] Update AccessControl.sol --- .../hooks/DynamicBondHook/AccessControl.sol | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol b/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol index 8b137891..5275ca66 100644 --- a/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol +++ b/packages/foundry/contracts/hooks/DynamicBondHook/AccessControl.sol @@ -1 +1,26 @@ +pragma solidity ^0.8.24; + +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract DynamicBondAccessControl is AccessControl { + bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); + + constructor() { + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // Grant the deployer the default admin role + _setupRole(MANAGER_ROLE, msg.sender); // Grant the deployer manager role + } + + modifier onlyManager() { + require(hasRole(MANAGER_ROLE, msg.sender), "Caller is not a manager"); + _; + } + + function grantManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { + grantRole(MANAGER_ROLE, account); + } + + function revokeManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { + revokeRole(MANAGER_ROLE, account); + } +} From 6499d1b3104041f16fdcfa76ed76924344403947 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 19:00:41 +0530 Subject: [PATCH 17/20] Create DynamicLoyaltyBondHook.t.sol --- .../foundry/test/DynamicLoyaltyBondHook.t.sol | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/foundry/test/DynamicLoyaltyBondHook.t.sol diff --git a/packages/foundry/test/DynamicLoyaltyBondHook.t.sol b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol new file mode 100644 index 00000000..690691f0 --- /dev/null +++ b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol @@ -0,0 +1,54 @@ +import "forge-std/Test.sol"; +import "@balancer-labs/v3-vault/contracts/Vault.sol"; +import "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; +import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; +import "@balancer-labs/v3-vault/contracts/test/PoolMock.sol"; +import "@balancer-labs/v3-vault/contracts/test/PoolFactoryMock.sol"; +import "@balancer-labs/v3-vault/contracts/test/RouterMock.sol"; +import "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol"; +import "permit2/src/interfaces/IPermit2.sol"; +import "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; +import "../contracts/hooks/NFTLiquidityStaking/NFTLiquidityStakingHook.sol"; +import "../contracts/hooks/NFTLiquidityStaking/NFTMetadata.sol"; +import "../contracts/hooks/NFTLiquidityStaking/NFTGovernor.sol"; +import "../contracts/hooks/NFTLiquidityStaking/RewardToken.sol"; +import "@openzeppelin/contracts/governance/IGovernor.sol"; +import "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; + +contract MockProtocolFeeController is IProtocolFeeController { + function getProtocolFeePercentages(uint256) external pure returns (uint256, uint256) { + return (0, 0); + } + + function collectAggregateFees(address) external pure {} + + function computeAggregateFeePercentage( + uint256 protocolFeePercentage, + uint256 poolCreatorFeePercentage + ) external pure returns (uint256) { + // Mock implementation: simply return the sum of the two percentages + return protocolFeePercentage + poolCreatorFeePercentage; + } + + function registerPool( + address pool, + address poolCreator, + bool protocolFeeExempt + ) external pure returns (uint256, uint256) { + return (0, 0); + } + + function getGlobalProtocolSwapFeePercentage() external pure returns (uint256) { + return 0; + } + function getGlobalProtocolYieldFeePercentage() external pure returns (uint256) { + return 0; + } + function getPoolCreatorFeeAmounts(address) external pure returns (uint256[] memory) { + return new uint256[](0); + } + function getPoolProtocolSwapFeeInfo(address) external pure returns (uint256, bool) { + return (0, false); + } + function getPoolProtocolYieldFeeInfo(address) external pure returns (uint256, bool) { + return (0, false); From 6538ef218bb9d6fa0c6c3021b23232196238174a Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 19:08:42 +0530 Subject: [PATCH 18/20] Update DynamicLoyaltyBondHook.t.sol --- .../foundry/test/DynamicLoyaltyBondHook.t.sol | 101 +++++++++++++++++- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/packages/foundry/test/DynamicLoyaltyBondHook.t.sol b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol index 690691f0..2983bdf5 100644 --- a/packages/foundry/test/DynamicLoyaltyBondHook.t.sol +++ b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol @@ -8,10 +8,10 @@ import "@balancer-labs/v3-vault/contracts/test/RouterMock.sol"; import "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol"; import "permit2/src/interfaces/IPermit2.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; -import "../contracts/hooks/NFTLiquidityStaking/NFTLiquidityStakingHook.sol"; -import "../contracts/hooks/NFTLiquidityStaking/NFTMetadata.sol"; -import "../contracts/hooks/NFTLiquidityStaking/NFTGovernor.sol"; -import "../contracts/hooks/NFTLiquidityStaking/RewardToken.sol"; +import "../contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol"; +import "../contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol"; +import "../contracts/hooks/DynamicBondHook/AccessControl.sol"; +import "../contracts/hooks/DynamicBondHook/BadgeToken"; import "@openzeppelin/contracts/governance/IGovernor.sol"; import "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; @@ -52,3 +52,96 @@ contract MockProtocolFeeController is IProtocolFeeController { } function getPoolProtocolYieldFeeInfo(address) external pure returns (uint256, bool) { return (0, false); + } +} +contract DynamicBondHookTest is Test { + DynamicLoyaltyBondHook public bondHook; + RewardToken public rewardToken; + MockProtocolFeeController public mockFeeController; + address public user1; + address public user2; + + function setUp() public { + bondHook = new DynamicLoyaltyBondHook(); + rewardToken = new RewardToken(); // Assuming a constructor exists + mockFeeController = new MockProtocolFeeController(); + user1 = address(0x1); + user2 = address(0x2); + } + + // Test for register function in DynamicLoyaltyBondHook + function testRegister() public { + bondHook.onRegister(user1); + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); + assertEq(bondAmount, 0, "Bond amount should be 0 after registration"); + assertEq(tier, 1, "Tier should be Bronze (1) after registration"); + } + + // Test for add liquidity function in DynamicLoyaltyBondHook + function testAddLiquidity() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 1000 ether); + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); + assertEq(bondAmount, 1000 ether, "Bond amount should be 1000 after adding liquidity"); + assertEq(tier, 1, "Tier should still be Bronze (1) after adding liquidity"); + } + + // Test for remove liquidity function in DynamicLoyaltyBondHook + function testRemoveLiquidity() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 1000 ether); + bondHook.onBeforeRemoveLiquidity(user1, 500 ether); + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); + assertEq(bondAmount, 500 ether, "Bond amount should be 500 after removing liquidity"); + } + + // Test for dynamic swap fee calculation + function testDynamicSwapFee() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 1000 ether); + uint256 swapFee = bondHook.onComputeDynamicSwapFeePercentage(user1); + assertEq(swapFee, 500, "Swap fee should reflect user loyalty tier"); + } + + // Test for tier upgrade based on bond amount + function testTierUpgrade() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 10000 ether); // This should trigger a tier upgrade + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); + assertEq(tier, 2, "Tier should be Silver (2) after sufficient liquidity added"); + } + + // Test for loyalty bonus calculation + function testLoyaltyBonus() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 2000 ether); + uint256 loyaltyBonus = bondHook.calculateLoyaltyBonus(user1); + assertEq(loyaltyBonus, 200, "Loyalty bonus should be calculated correctly based on bond amount"); + } + + // Test for the maximum bond limit + function testMaxBondLimit() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 10000 ether); // Assume the max limit is set to a lower value + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); + assertEq(bondAmount, 0, "Bond amount should not exceed max limit"); + } + + // Tests for RewardToken contract + function testRewardDistribution() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 1000 ether); + rewardToken.distributeRewards(user1, 100 ether); // Assuming a function exists + uint256 rewardBalance = rewardToken.balanceOf(user1); + assertEq(rewardBalance, 100 ether, "User should receive 100 rewards"); + } + + function testRewardClaim() public { + bondHook.onRegister(user1); + bondHook.onAfterAddLiquidity(user1, 1000 ether); + rewardToken.distributeRewards(user1, 100 ether); + rewardToken.claimRewards(user1); // Assuming a claim function exists + uint256 userBalance = rewardToken.balanceOf(user1); + assertEq(userBalance, 100 ether, "User should have claimed rewards"); + } +} From 245fc00bc15fc94ff8ca777d22b3bae47240e5c1 Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 19:58:55 +0530 Subject: [PATCH 19/20] Update DynamicLoyaltyBondHook.t.sol --- .../foundry/test/DynamicLoyaltyBondHook.t.sol | 416 +++++++++++++----- 1 file changed, 313 insertions(+), 103 deletions(-) diff --git a/packages/foundry/test/DynamicLoyaltyBondHook.t.sol b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol index 2983bdf5..deea1282 100644 --- a/packages/foundry/test/DynamicLoyaltyBondHook.t.sol +++ b/packages/foundry/test/DynamicLoyaltyBondHook.t.sol @@ -1,147 +1,357 @@ +pragma solidity ^0.8.24; + import "forge-std/Test.sol"; import "@balancer-labs/v3-vault/contracts/Vault.sol"; -import "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; -import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import "@balancer-labs/v3-vault/contracts/test/PoolMock.sol"; import "@balancer-labs/v3-vault/contracts/test/PoolFactoryMock.sol"; import "@balancer-labs/v3-vault/contracts/test/RouterMock.sol"; import "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol"; -import "permit2/src/interfaces/IPermit2.sol"; -import "@balancer-labs/v3-interfaces/contracts/vault/IProtocolFeeController.sol"; import "../contracts/hooks/DynamicBondHook/DynamicLoyaltyBondHook.sol"; -import "../contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol"; import "../contracts/hooks/DynamicBondHook/AccessControl.sol"; -import "../contracts/hooks/DynamicBondHook/BadgeToken"; +import "../contracts/hooks/DynamicBondHook/BadgeToken.sol"; +import "../contracts/hooks/DynamicBondHook/LoyaltyBondStructure.sol"; import "@openzeppelin/contracts/governance/IGovernor.sol"; import "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -contract MockProtocolFeeController is IProtocolFeeController { - function getProtocolFeePercentages(uint256) external pure returns (uint256, uint256) { - return (0, 0); - } - function collectAggregateFees(address) external pure {} - function computeAggregateFeePercentage( - uint256 protocolFeePercentage, - uint256 poolCreatorFeePercentage - ) external pure returns (uint256) { - // Mock implementation: simply return the sum of the two percentages - return protocolFeePercentage + poolCreatorFeePercentage; - } +contract DynamicBondHookTest is Test { + using FixedPoint for uint256; - function registerPool( - address pool, - address poolCreator, - bool protocolFeeExempt - ) external pure returns (uint256, uint256) { - return (0, 0); - } + DynamicLoyaltyBondHook public bondHook; + PoolMock public pool; + PoolFactoryMock public factory; + RouterMock public router; - function getGlobalProtocolSwapFeePercentage() external pure returns (uint256) { - return 0; - } - function getGlobalProtocolYieldFeePercentage() external pure returns (uint256) { - return 0; - } - function getPoolCreatorFeeAmounts(address) external pure returns (uint256[] memory) { - return new uint256[](0); - } - function getPoolProtocolSwapFeeInfo(address) external pure returns (uint256, bool) { - return (0, false); + address public alice; + address public bob; + uint256 public constant STAKE_AMOUNT = 1000e18; + + // Setup function + function setUp() public { + console.log("Starting setUp()"); + + address weth = 0x1D05f8153A0Dc80fB76fA728cFa3349624479ecb; + address permit2 = address(0x5678); + + console.log("Creating PoolFactoryMock"); + factory = new PoolFactoryMock(IVault(address(0)), 0); + console.log("PoolFactoryMock created at:", address(factory)); + + console.log("Creating RouterMock"); + router = new RouterMock(IVault(address(0)), IWETH(weth), IPermit2(permit2)); + console.log("RouterMock created at:", address(router)); + + console.log("Creating DynamicBondHook"); + hook = new DynamicBondHook( + IVault(address(0)), + address(factory), + "Dynamic Bond Token", + "DBOND" + ); + console.log("DynamicBondHook created at:", address(hook)); + + rewardToken = RewardToken(hook.rewardToken()); + + console.log("Creating PoolMock"); + pool = new PoolMock(IVault(address(0)), "Bonded Liquidity Pool", "BLP"); + console.log("PoolMock created at:", address(pool)); + + console.log("Registering pool with factory"); + try + factory.registerPool( + address(pool), + new TokenConfig PoolRoleAccounts({ + pauseManager: address(0), + swapFeeManager: address(0), + poolCreator: address(0) + }), + address(hook), + LiquidityManagement({ + disableUnbalancedLiquidity: false, + enableAddLiquidityCustom: true, + enableRemoveLiquidityCustom: true, + enableDonation: true + }) + ) + { + console.log("Pool registered successfully"); + } catch Error(string memory reason) { + console.log("Failed to register pool. Reason:", reason); + revert(reason); + } catch (bytes memory lowLevelData) { + console.log("Failed to register pool. Low-level error."); + revert("Low-level error in pool registration"); + } + + alice = address(0x1); + bob = address(0x2); + + vm.label(address(hook), "DynamicBondHook"); + vm.label(address(rewardToken), "RewardToken"); + vm.label(address(pool), "Pool"); + vm.label(address(factory), "Factory"); + + console.log("Dealing tokens to Alice and Bob"); + deal(address(pool), alice, INITIAL_BALANCE); + deal(address(pool), bob, INITIAL_BALANCE); + + console.log("setUp() completed"); } - function getPoolProtocolYieldFeeInfo(address) external pure returns (uint256, bool) { - return (0, false); + + // 1. Registration Tests + function testRegisterUser() public { + bondHook.onRegister(alice); + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(alice); + assertEq(bondAmount, 0, "Initial bond amount should be 0"); + assertEq(tier, 1, "Initial tier should be Bronze"); } -} -contract DynamicBondHookTest is Test { - DynamicLoyaltyBondHook public bondHook; - RewardToken public rewardToken; - MockProtocolFeeController public mockFeeController; - address public user1; - address public user2; - function setUp() public { - bondHook = new DynamicLoyaltyBondHook(); - rewardToken = new RewardToken(); // Assuming a constructor exists - mockFeeController = new MockProtocolFeeController(); - user1 = address(0x1); - user2 = address(0x2); + // 2. Bond Management Tests + function testCreateBond() public { + bondHook.createBond(alice, STAKE_AMOUNT); + (address holder, uint256 amount) = bondHook.getBondDetails(alice); + assertEq(holder, alice, "Bond holder should be Alice"); + assertEq(amount, STAKE_AMOUNT, "Bond amount should match staked amount"); } - // Test for register function in DynamicLoyaltyBondHook - function testRegister() public { - bondHook.onRegister(user1); - (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); - assertEq(bondAmount, 0, "Bond amount should be 0 after registration"); - assertEq(tier, 1, "Tier should be Bronze (1) after registration"); + function testRedeemBond() public { + bondHook.createBond(alice, STAKE_AMOUNT); + bondHook.redeemBond(alice); + (address holder, uint256 amount) = bondHook.getBondDetails(alice); + assertEq(holder, address(0), "Bond should be removed"); + assertEq(amount, 0, "Bond amount should be 0 after redemption"); } - // Test for add liquidity function in DynamicLoyaltyBondHook + // 3. Liquidity Interaction Tests function testAddLiquidity() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 1000 ether); - (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); - assertEq(bondAmount, 1000 ether, "Bond amount should be 1000 after adding liquidity"); - assertEq(tier, 1, "Tier should still be Bronze (1) after adding liquidity"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(alice); + assertEq(bondAmount, 1000 ether, "Bond amount should reflect added liquidity"); } - // Test for remove liquidity function in DynamicLoyaltyBondHook function testRemoveLiquidity() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 1000 ether); - bondHook.onBeforeRemoveLiquidity(user1, 500 ether); - (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); - assertEq(bondAmount, 500 ether, "Bond amount should be 500 after removing liquidity"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + bondHook.onBeforeRemoveLiquidity(alice, 500 ether); + (uint256 bondAmount, ) = bondHook.getUserBond(alice); + assertEq(bondAmount, 500 ether, "Bond amount should decrease after removal"); } - // Test for dynamic swap fee calculation - function testDynamicSwapFee() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 1000 ether); - uint256 swapFee = bondHook.onComputeDynamicSwapFeePercentage(user1); - assertEq(swapFee, 500, "Swap fee should reflect user loyalty tier"); + // 4. Reward Calculation Tests + function testCalculateBondReward() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 2000 ether); + uint256 reward = bondHook.calculateBondReward(alice, address(pool)); + assertEq(reward, 200 ether, "Reward should be correctly calculated"); } - // Test for tier upgrade based on bond amount function testTierUpgrade() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 10000 ether); // This should trigger a tier upgrade - (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); - assertEq(tier, 2, "Tier should be Silver (2) after sufficient liquidity added"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 10000 ether); + ( , uint256 tier) = bondHook.getUserBond(alice); + assertEq(tier, 2, "User should be upgraded to Silver tier"); } - // Test for loyalty bonus calculation function testLoyaltyBonus() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 2000 ether); - uint256 loyaltyBonus = bondHook.calculateLoyaltyBonus(user1); - assertEq(loyaltyBonus, 200, "Loyalty bonus should be calculated correctly based on bond amount"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 3000 ether); + uint256 bonus = bondHook.calculateLoyaltyBonus(alice); + assertEq(bonus, 300 ether, "Loyalty bonus should be accurate"); + } + + // 5. Swap Fee Calculation Test + function testDynamicSwapFee() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + uint256 swapFee = bondHook.onComputeDynamicSwapFeePercentage(alice); + assertEq(swapFee, 500, "Swap fee should match user tier"); + } + + // 6. Decay Mechanism Test + function testBondDecay() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 2000 ether); + vm.warp(block.timestamp + 60 days); + uint256 decayedReward = bondHook.calculateBondReward(alice, address(pool)); + assertTrue(decayedReward < 2000 ether, "Reward should decay over time"); } - // Test for the maximum bond limit + // 7. Multi-Tier Rewards Test + function testMultiTierRewards() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 5000 ether); + vm.warp(block.timestamp + 30 days); + bondHook.upgradeToStrategicVeteran(alice); + ( , uint256 tier) = bondHook.getUserBond(alice); + assertEq(tier, 3, "User should upgrade to Strategic Veteran tier"); + } + + // 8. Maximum Bond Limit Test function testMaxBondLimit() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 10000 ether); // Assume the max limit is set to a lower value - (uint256 bondAmount, uint256 tier) = bondHook.getUserBond(user1); - assertEq(bondAmount, 0, "Bond amount should not exceed max limit"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 100000 ether); // Exceeds limit + (uint256 bondAmount, ) = bondHook.getUserBond(alice); + assertEq(bondAmount, 0, "Bond amount should not exceed limit"); } - // Tests for RewardToken contract + // 9. Reward Token Tests function testRewardDistribution() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 1000 ether); - rewardToken.distributeRewards(user1, 100 ether); // Assuming a function exists - uint256 rewardBalance = rewardToken.balanceOf(user1); - assertEq(rewardBalance, 100 ether, "User should receive 100 rewards"); + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + bondHook.distributeRewards(alice, 100 ether); + uint256 rewardBalance = bondHook.getRewardBalance(alice); + assertEq(rewardBalance, 100 ether, "User should receive correct reward"); + } + + function testClaimRewards() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + bondHook.distributeRewards(alice, 100 ether); + bondHook.claimRewards(alice); + uint256 rewardBalance = bondHook.getRewardBalance(alice); + assertEq(rewardBalance, 0, "Rewards should be claimed successfully"); + } + // 10. Test Invalid User Registration + function testRegisterInvalidUser() public { + address invalidUser = address(0); // Address zero as an invalid case + vm.expectRevert("Invalid user address"); // Assuming revert message from the contract + bondHook.onRegister(invalidUser); + } + + // 11. Test Bond Creation with Insufficient Funds + function testBondCreationInsufficientFunds() public { + bondHook.onRegister(bob); + deal(address(pool), bob, 500 ether); // Bob has insufficient balance + vm.startPrank(bob); + vm.expectRevert("Insufficient balance for bond creation"); + bondHook.createBond(bob, 1000 ether); // Trying to create a bond exceeding balance + vm.stopPrank(); + } + + // 12. Test Bond Transfer Between Users + function testBondTransfer() public { + bondHook.onRegister(alice); + bondHook.createBond(alice, STAKE_AMOUNT); + + // Transfer bond from Alice to Bob + bondHook.transferBond(alice, bob); + (address holder, uint256 amount) = bondHook.getBondDetails(bob); + + assertEq(holder, bob, "Bob should now hold the bond"); + assertEq(amount, STAKE_AMOUNT, "Transferred bond amount should be correct"); } + + // 13. Test Reward Decay with Partial Withdrawal + function testPartialWithdrawalWithDecay() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 5000 ether); + + // Simulate time passing and partial liquidity removal + vm.warp(block.timestamp + 30 days); + bondHook.onBeforeRemoveLiquidity(alice, 2500 ether); + + uint256 remainingBond = bondHook.getBondAmount(alice); + uint256 decayedReward = bondHook.calculateBondReward(alice, address(pool)); + + assertEq(remainingBond, 2500 ether, "Remaining bond should match the withdrawn amount"); + assertTrue(decayedReward < 5000 ether, "Reward should decay after 30 days"); + } + + // 14. Test Governance-Based Bonus Allocation + function testGovernanceBonusAllocation() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 2000 ether); + + // Grant governance bonus + bondHook.allocateGovernanceBonus(alice, 500 ether); + uint256 totalReward = bondHook.calculateTotalReward(alice); + + assertEq(totalReward, 700 ether, "Total reward should include governance bonus"); + } + + // 15. Test Hook with Multiple Users and Liquidity Events + function testMultipleUsersLiquidityInteraction() public { + bondHook.onRegister(alice); + bondHook.onRegister(bob); + + bondHook.onAfterAddLiquidity(alice, 3000 ether); + bondHook.onAfterAddLiquidity(bob, 5000 ether); + + // Alice removes liquidity partially + bondHook.onBeforeRemoveLiquidity(alice, 1000 ether); + + // Validate both users' bond amounts + uint256 aliceBond = bondHook.getBondAmount(alice); + uint256 bobBond = bondHook.getBondAmount(bob); + + assertEq(aliceBond, 2000 ether, "Alice's bond amount should be correct"); + assertEq(bobBond, 5000 ether, "Bob's bond amount should remain unchanged"); + } + + // 16. Test Swap Fee Adjustment Based on Tier Downgrade + function testSwapFeeAdjustmentOnTierDowngrade() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 10000 ether); // User reaches Silver tier + + vm.warp(block.timestamp + 90 days); // Simulate inactivity for 3 months + bondHook.downgradeUserTier(alice); // Downgrade to Bronze + + uint256 newSwapFee = bondHook.onComputeDynamicSwapFeePercentage(alice); + assertEq(newSwapFee, 300, "Swap fee should adjust to Bronze tier percentage"); + } + + // 17. Test Reward Claim by Unauthorized User + function testUnauthorizedRewardClaim() public { + bondHook.onRegister(alice); + bondHook.onAfterAddLiquidity(alice, 1000 ether); + + vm.prank(bob); // Simulate Bob trying to claim Alice's reward + vm.expectRevert("Unauthorized reward claim attempt"); + bondHook.claimRewards(alice); + } + + //18. Bond Reward Calculation + function getBondReward(address user) public view returns (uint256 reward, string memory tier) { + uint256 bondAmount = getBondAmount(user); + uint256 governanceBonus = governanceRewards[user]; + uint256 tierBonus = calculateTierBonus(user); + + reward = bondAmount + governanceBonus + tierBonus; - function testRewardClaim() public { - bondHook.onRegister(user1); - bondHook.onAfterAddLiquidity(user1, 1000 ether); - rewardToken.distributeRewards(user1, 100 ether); - rewardToken.claimRewards(user1); // Assuming a claim function exists - uint256 userBalance = rewardToken.balanceOf(user1); - assertEq(userBalance, 100 ether, "User should have claimed rewards"); + // Determine user’s reward tier based on their bond amount + if (bondAmount >= 10000 ether) { + tier = "Veteran"; // Highest reward tier + } else if (bondAmount >= 5000 ether) { + tier = "Strategist"; // Mid-level reward tier + } else { + tier = "Explorer"; // Base reward tier } + + function testUserBondTier() public { + console.log("Testing user reward tiers based on bond amount"); + + // Alice adds 12,000 ETH (Veteran Tier) + vm.startPrank(alice); + pool.approve(address(hook), 12000 ether); + hook.onAfterAddLiquidity(alice, 12000 ether); + assertEq(hook.getUserTier(alice), "Veteran", "Alice should be in the Veteran tier"); + vm.stopPrank(); + + // Bob adds 6,000 ETH (Strategic Tier) + vm.startPrank(bob); + pool.approve(address(hook), 6000 ether); + hook.onAfterAddLiquidity(bob, 6000 ether); + assertEq(hook.getUserTier(bob), "Strategic", "Bob should be in the Strategic tier"); + vm.stopPrank(); + + // Alice removes some liquidity, moving her to the Strategic tier + vm.startPrank(alice); + hook.onBeforeRemoveLiquidity(alice, 3000 ether); // Alice now has 9,000 ETH + assertEq(hook.getUserTier(alice), "Explorer", "Alice should be downgraded to Explorer tier"); + vm.stopPrank(); + } + } + From ac792e57f2a68e3cb32a94b4b9d2d5a13593874f Mon Sep 17 00:00:00 2001 From: Tanishq Chauhan <123287153+Volcandrabuzz@users.noreply.github.com> Date: Sun, 20 Oct 2024 20:03:53 +0530 Subject: [PATCH 20/20] Create Readme.md --- .../contracts/hooks/DynamicBondHook/Readme.md | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 packages/foundry/contracts/hooks/DynamicBondHook/Readme.md diff --git a/packages/foundry/contracts/hooks/DynamicBondHook/Readme.md b/packages/foundry/contracts/hooks/DynamicBondHook/Readme.md new file mode 100644 index 00000000..1acaae65 --- /dev/null +++ b/packages/foundry/contracts/hooks/DynamicBondHook/Readme.md @@ -0,0 +1,106 @@ +# Dynamic Loyalty Bond Hook + +An innovative **Balancer V3 hook** designed to foster long-term liquidity provision through evolving bond-based rewards. This mechanism gamifies liquidity staking with dynamic perks, seasonal challenges, and governance incentives, encouraging deeper engagement with the Balancer ecosystem and expanding Balancer V3’s liquidity and fee management capabilities. + +## 📖 Overview + +The **Dynamic Loyalty Bond Hook** leverages Balancer V3 hooks to enhance liquidity incentives by issuing **"Loyalty Bonds"**—dynamic rewards that offer evolving perks based on market trends, user behavior, and seasonal Balancer campaigns. Through features like **multiplier decay, bonus lotteries, and advanced governance**, this hook encourages participants to stay invested and unlock exclusive privileges. + +It integrates with **Balancer V3’s dynamic fee and swap management hooks**, creating a seamless reward system tied directly to liquidity activities. This ensures optimized trading conditions, sustainable rewards, and engaging gamified mechanics. + +--- + +## ✨ Features + +1. **Dynamic Bond Tiers via Balancer V3 Hooks**: Custom hooks track user actions to adjust rewards in real-time. +2. **Seasonal Challenges**: Limited-time campaigns provide rare bonds with unique perks. +3. **Multiplier Decay Mechanism**: Fee hook logic ensures reward decay, promoting re-staking. +4. **Governance XP and Committees**: High-tier users gain access to advanced governance tools and can influence pool policies. +5. **Flash Proposal Integration**: Users vote on short-term liquidity proposals through governance hooks. + +--- + +## 🎖 Bond Levels and Perks System + +The tiered reward system tracks liquidity participation, automatically adjusting perks based on performance and time. Rewards scale dynamically based on Balancer V3’s pool metrics. + +| **Bond Tier** | **Maturity Period** | **Perks** | +|-----------------|--------------------|-----------------------------------------------| +| Explorer Bond | 2 weeks | 5% swap fee refund, entry into lotteries. | +| Strategist Bond | 2 months | 15% fee refund, 2x multiplier during low liquidity phases. | +| Veteran Bond | 6 months | 40% fee refund, seasonal bonuses, governance access. | + +- **Flash Proposal Voting**: Enabled for high-tier bond holders. +- **Multiplier Weekends**: Swap fee hooks trigger bonuses during weekends. + +--- + +## 🛠 Architecture and Balancer V3 Hooks + +### **DynamicBondHook Contract** +- Manages liquidity tracking, reward logic, and multiplier decay. +- Connects with **fee hooks** to optimize user profitability. +- Adjusts rewards dynamically based on user behavior and seasonal campaigns. + +### **BondGovernor Contract** +- Uses governance hooks to enable flash proposals and voting. +- Allocates voting power using **XP points**. +- Facilitates real-time voting on pool configurations. + +### **BondMetadata Contract** +- Generates on-chain **SVG images** representing bonds and staking performance. +- Creates **NFT-compatible bonds** tradable on marketplaces. + +--- + +## 🚀 Usage and Example Use Case + +**Scenario**: Alice provides liquidity to a Balancer pool with the Dynamic Loyalty Bond Hook integrated. + +1. **Alice Stakes Liquidity**: Deposits 100 DAI, triggering the hook to track her position. +2. **Bond Issued**: After two weeks, Alice receives an **Explorer Bond** with a 5% swap fee refund. +3. **Bond Upgrade**: At two months, Alice’s bond upgrades to **Strategist**, granting a 2x multiplier. +4. **Governance Access**: Alice gains access to flash proposal voting to optimize pool fees. + +--- + +## 🏦 Benefits + +### **For Users** +- **Gamified Experience**: Engage in seasonal events, lotteries, and flash proposals. +- **Long-Term Rewards**: Fee refunds and multipliers reward sustained participation. +- **Governance Influence**: High-tier bonds offer direct protocol governance access. + +### **For Pool Creators** +- **Enhanced Liquidity**: Incentivizes long-term liquidity, improving pool performance. +- **Optimized Trading Conditions**: Dynamically managed fees create efficient markets. +- **Community Engagement**: Seasonal campaigns foster participation and collaboration. + +--- + +## 🧪 Testing Plan + +1. **Bond Issuance Tests**: Validate NFT minting and reward calculations. +2. **Multiplier Decay Simulation**: Ensure proper decay behavior to encourage re-staking. +3. **Governance Proposal Tests**: Verify creation and voting functionality. +4. **Seasonal Event Simulations**: Test dynamic fee adjustment during key events. + +--- + +## 🛠 Development Notes + +- **Extensible Design**: Future updates can introduce exclusive rewards and new multipliers. +- **Security Audits**: Comprehensive audits ensure safety and reliability. +- **Strategy Pattern**: Separation of reward logic ensures smooth updates without disruptions. + +--- + +## 📌 Roadmap + +1. **Phase 1** – Launch core hooks with Explorer, Strategist, and Veteran tiers. +2. **Phase 2** – Introduce quarterly seasonal campaigns with bonus bonds. +3. **Phase 3** – Enable governance committees with advanced tools. +4. **Phase 4** – Add badges, flash events, and social gamification features. + + +