Multiple claims + usdc tracking in withdrawliabilities

This commit is contained in:
2025-09-15 14:07:43 +02:00
parent 4f8f062f35
commit 8e0b6a58bd
2 changed files with 153 additions and 126 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
@@ -41,6 +40,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 estDaysRemaining; uint256 estDaysRemaining;
uint256 currentTreasuryTvl; uint256 currentTreasuryTvl;
uint256 totalLiability; // Snapshot of totalBigStakes at epoch end uint256 totalLiability; // Snapshot of totalBigStakes at epoch end
uint256 paybackPercent; // Payback percentage used for this epoch (scaled by 10000)
uint256 unlockPercentage; // Calculated unlock percentage (scaled by 10000) uint256 unlockPercentage; // Calculated unlock percentage (scaled by 10000)
uint256 timestamp; // When this epoch ended uint256 timestamp; // When this epoch ended
} }
@@ -90,8 +90,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 private stakeIdCounter; uint256 private stakeIdCounter;
uint256 private vestingStakeIdCounter; uint256 private vestingStakeIdCounter;
// Track total withdraw vesting liabilities by token address // Track total withdraw liabilities by token address (all tokens including BSC_TOKEN)
mapping(address => uint256) public withdrawVestingLiabilities; mapping(address => uint256) public withdrawLiabilities;
// Epoch-based staking variables // Epoch-based staking variables
mapping(uint256 => Epoch) public epochs; mapping(uint256 => Epoch) public epochs;
@@ -107,13 +107,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 public marketplaceMin; // Minimum value for listings (in USD, e.g., 25 * 1e18 = $25) uint256 public marketplaceMin; // Minimum value for listings (in USD, e.g., 25 * 1e18 = $25)
uint256 public cancellationFee; // Fee percentage for cancelling listings (e.g., 500 = 5%) uint256 public cancellationFee; // Fee percentage for cancelling listings (e.g., 500 = 5%)
mapping(address => uint256) public marketplace_sales; // Track total sales per user mapping(address => uint256) public marketplace_sales; // Track total sales per user
mapping(address => uint256) public pendingSellStakes; // Track total value of pending sell stakes per user
SellStakeKey[] public sellStakeKeys; // Array for iteration over active sell stakes SellStakeKey[] public sellStakeKeys; // Array for iteration over active sell stakes
mapping(address => mapping(uint256 => uint256)) private sellStakeKeyIndex; // Track position in keys array mapping(address => mapping(uint256 => uint256)) private sellStakeKeyIndex; // Track position in keys array
MarketplaceHistory[] public marketplaceHistory; // Complete history of all transactions MarketplaceHistory[] public marketplaceHistory; // Complete history of all transactions
mapping(address => uint256) public totalClaimed; // Track total amount claimed and sent to withdrawStakes per user mapping(address => uint256) public totalClaimed; // Track total amount claimed and sent to withdrawStakes per user
// Events // Events
event VestingCreated(address indexed user, uint256 amount, uint256 bonus);
event VestingClaimed(address indexed user, uint256 amount, uint256 bonus); event VestingClaimed(address indexed user, uint256 amount, uint256 bonus);
event BonusClaimed(address indexed user, uint256 bonus); event BonusClaimed(address indexed user, uint256 bonus);
event UnlockScheduleSet(address indexed token); event UnlockScheduleSet(address indexed token);
@@ -343,6 +343,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
estDaysRemaining: estDaysRemaining, estDaysRemaining: estDaysRemaining,
currentTreasuryTvl: currentTreasuryTvl, currentTreasuryTvl: currentTreasuryTvl,
totalLiability: totalBigStakes, totalLiability: totalBigStakes,
paybackPercent: _paybackPercent,
unlockPercentage: unlockPercentage, unlockPercentage: unlockPercentage,
timestamp: block.timestamp timestamp: block.timestamp
}); });
@@ -366,11 +367,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
return totalUnclaimed; return totalUnclaimed;
} }
/// @notice Get user's net stake (big stake minus unclaimed funds) /// @notice Get user's net stake (big stake minus unclaimed funds minus pending sell stakes)
function getNetStake(address user) public view returns (uint256) { function getNetStake(address user) public view returns (uint256) {
uint256 bigStake = userBigStake[user]; uint256 bigStake = userBigStake[user];
uint256 unclaimed = calculateUnclaimedFunds(user); uint256 unclaimed = calculateUnclaimedFunds(user);
return bigStake - unclaimed; uint256 pending = pendingSellStakes[user];
uint256 committed = unclaimed + pending;
return bigStake > committed ? bigStake - committed : 0;
} }
/// @notice Get comprehensive user stake information /// @notice Get comprehensive user stake information
@@ -411,6 +414,9 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: BSC_TOKEN token: BSC_TOKEN
})); }));
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += unclaimedAmount;
emit FundsClaimed(msg.sender, unclaimedAmount); emit FundsClaimed(msg.sender, unclaimedAmount);
} }
@@ -433,10 +439,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
address token = stake.token; address token = stake.token;
stake.amount = 0; // Mark as withdrawn stake.amount = 0; // Mark as withdrawn
// Decrement withdraw vesting liabilities for non-BSC tokens // Decrement withdraw liabilities for all tokens
if (token != BSC_TOKEN) { withdrawLiabilities[token] -= amount;
withdrawVestingLiabilities[token] -= amount;
}
// Transfer tokens to user based on the specified token // Transfer tokens to user based on the specified token
IERC20(token).safeTransfer(msg.sender, amount); IERC20(token).safeTransfer(msg.sender, amount);
@@ -482,6 +486,9 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: BSC_TOKEN token: BSC_TOKEN
})); }));
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += payoutAmount;
emit FundsClaimed(msg.sender, payoutAmount); emit FundsClaimed(msg.sender, payoutAmount);
} }
@@ -622,9 +629,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
stakeIdCounter++; stakeIdCounter++;
uint256 stakeId = stakeIdCounter; uint256 stakeId = stakeIdCounter;
// Deduct value from user's big stake immediately // Add to pending sell stakes tracking (don't touch actual bigStake until sale/cancel)
userBigStake[msg.sender] -= value; pendingSellStakes[msg.sender] += value;
totalBigStakes -= value;
// Create the sellStake entry // Create the sellStake entry
sellStakes[msg.sender][stakeId] = SellStake({ sellStakes[msg.sender][stakeId] = SellStake({
@@ -653,14 +659,16 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
uint256 value = sellStakeEntry.value; uint256 value = sellStakeEntry.value;
// Calculate cancellation fee // Calculate cancellation fee and apply directly to userBigStake
uint256 fee = (value * cancellationFee) / 10000; uint256 fee = (value * cancellationFee) / 10000;
uint256 valueAfterFee = value - fee;
// Restore value minus fee to user's big stake // Remove from pending and apply fee directly to bigStake
userBigStake[msg.sender] += valueAfterFee; pendingSellStakes[msg.sender] -= value;
totalBigStakes += valueAfterFee; if (fee > 0) {
// Note: fee reduces total liability (not added back to totalBigStakes) userBigStake[msg.sender] -= fee;
totalBigStakes -= fee;
}
// Note: fee reduces total liability permanently
// Emit fee event // Emit fee event
if (fee > 0) { if (fee > 0) {
@@ -720,10 +728,12 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
// Transfer payment from buyer to seller (direct transfer) // Transfer payment from buyer to seller (direct transfer)
IERC20(BSC_TOKEN).safeTransferFrom(msg.sender, seller, salePrice); IERC20(BSC_TOKEN).safeTransferFrom(msg.sender, seller, salePrice);
// Add buyerStake to buyer's big stake (value - protocol share) // Transfer stakes: remove from seller, add to buyer (minus protocol share)
userBigStake[seller] -= value;
userBigStake[msg.sender] += buyerStake; userBigStake[msg.sender] += buyerStake;
totalBigStakes += buyerStake; pendingSellStakes[seller] -= value;
// Note: protocolShare reduces total liability (not added back to totalBigStakes) // Note: totalBigStakes decreases by protocolShare (value - buyerStake)
totalBigStakes -= (value - buyerStake);
// Track marketplace sales for seller // Track marketplace sales for seller
marketplace_sales[seller] += salePrice; marketplace_sales[seller] += salePrice;
@@ -759,27 +769,27 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
/// @notice This function will end and clear a user's vestings. /// @notice This function will end and clear a user's vestings.
/// @dev Only to be used by bots in emergencies /// @dev Only to be used by bots in emergencies
/// @param user The user whose vestings will be ended and 0'd /// @param user The user whose vestings will be ended and 0'd
function clearVesting(address user) external onlyBot { // function clearVesting(address user) external onlyBot {
for (uint256 i = 0; i < vestings[user].length; ++i) { // for (uint256 i = 0; i < vestings[user].length; ++i) {
Vesting storage vesting = vestings[user][i]; // Vesting storage vesting = vestings[user][i];
// Decrement accounting variables before clearing // // Decrement accounting variables before clearing
if (!vesting.complete) { // if (!vesting.complete) {
if (dollarsVested[user] >= vesting.usdAmount) { // if (dollarsVested[user] >= vesting.usdAmount) {
dollarsVested[user] -= vesting.usdAmount; // dollarsVested[user] -= vesting.usdAmount;
} // }
if (vestedTotal[vesting.token] >= vesting.amount) { // if (vestedTotal[vesting.token] >= vesting.amount) {
vestedTotal[vesting.token] -= vesting.amount; // vestedTotal[vesting.token] -= vesting.amount;
} // }
} // }
vesting.amount = 0; // vesting.amount = 0;
vesting.bonus = 0; // vesting.bonus = 0;
vesting.claimedAmount = 0; // vesting.claimedAmount = 0;
vesting.claimedBonus = 0; // vesting.claimedBonus = 0;
vesting.complete = true; // vesting.complete = true;
} // }
} // }
/// @notice Creates a vesting for a given user /// @notice Creates a vesting for a given user
/// @dev Only to be used by bots for manual vesting creation /// @dev Only to be used by bots for manual vesting creation
@@ -1025,8 +1035,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: vesting.token token: vesting.token
})); }));
// Increment withdraw vesting liabilities for this token // Increment withdraw liabilities for this token
withdrawVestingLiabilities[vesting.token] += amountToClaim; withdrawLiabilities[vesting.token] += amountToClaim;
emit VestingClaimed(msg.sender, amountToClaim, 0); emit VestingClaimed(msg.sender, amountToClaim, 0);
} }
@@ -1073,35 +1083,56 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
token: _token token: _token
})); }));
// Increment withdraw vesting liabilities for this token // Increment withdraw liabilities for this token
withdrawVestingLiabilities[_token] += totalReward; withdrawLiabilities[_token] += totalReward;
emit VestingClaimed(msg.sender, totalReward, 0); emit VestingClaimed(msg.sender, totalReward, 0);
} }
/// @notice Claim unlocked bonus tokens from a specific vesting /// @notice Claim unlocked bonus tokens from multiple vestings
/// @param _vestingIndex The index of the vesting to claim bonus from /// @param _vestingIndices Array of vesting indices to claim bonus from
function claimBonus(uint256 _vestingIndex) external nonReentrant { function claimBonus(uint256[] calldata _vestingIndices) external nonReentrant {
require(_vestingIndex < vestings[msg.sender].length, "Invalid vesting index"); uint256 totalBonusClaimed = 0;
Vesting storage vesting = vestings[msg.sender][_vestingIndex]; for (uint256 i = 0; i < _vestingIndices.length; i++) {
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex); uint256 _vestingIndex = _vestingIndices[i];
require(maxBonus >= vesting.claimedBonus, "Invalid claim amount"); // Skip invalid vesting indices
uint256 bonusToClaim = maxBonus - vesting.claimedBonus; if (_vestingIndex >= vestings[msg.sender].length) {
require(bonusToClaim > 0, "Nothing to claim"); continue;
}
vesting.claimedBonus += bonusToClaim; Vesting storage vesting = vestings[msg.sender][_vestingIndex];
uint256 maxBonus = getUnlockedVestingBonus(msg.sender, _vestingIndex);
// Create withdrawable stake with unlock delay (add 1e6 to distinguish from normal stakes) // Skip if invalid claim amount or nothing to claim
withdrawStakes[msg.sender].push(WithdrawStake({ if (maxBonus < vesting.claimedBonus) {
stakeId: _vestingIndex + 1e6, continue;
amount: bonusToClaim, }
unlockTime: block.timestamp + unlockDelay,
token: BSC_TOKEN
}));
emit BonusClaimed(msg.sender, bonusToClaim); uint256 bonusToClaim = maxBonus - vesting.claimedBonus;
if (bonusToClaim == 0) {
continue;
}
vesting.claimedBonus += bonusToClaim;
totalBonusClaimed += bonusToClaim;
// Create withdrawable stake with unlock delay (add 1e6 to distinguish from normal stakes)
withdrawStakes[msg.sender].push(WithdrawStake({
stakeId: _vestingIndex + 1e6,
amount: bonusToClaim,
unlockTime: block.timestamp + unlockDelay,
token: BSC_TOKEN
}));
}
// Only update liabilities and emit event if something was actually claimed
if (totalBonusClaimed > 0) {
// Increment withdraw liabilities for BSC_TOKEN
withdrawLiabilities[BSC_TOKEN] += totalBonusClaimed;
emit BonusClaimed(msg.sender, totalBonusClaimed);
}
} }