Buyout SellStakes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -115,6 +115,8 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
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
|
||||||
uint256 public highestRatio; // Track the highest TVL/liability ratio ever achieved (scaled by 10000)
|
uint256 public highestRatio; // Track the highest TVL/liability ratio ever achieved (scaled by 10000)
|
||||||
|
mapping(address => mapping(uint256 => uint256)) public contractOwnedSellStakes; // Track contract-owned sellStakes linked to user withdrawStake IDs
|
||||||
|
uint256 public sellStakePremiumPercent; // Premium percentage for contract-owned sellStakes (e.g., 1000 = 10%)
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
event VestingClaimed(address indexed user, uint256 amount, uint256 bonus);
|
event VestingClaimed(address indexed user, uint256 amount, uint256 bonus);
|
||||||
@@ -163,6 +165,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
|
|
||||||
unlockDelay = 60 * 60 * 72;
|
unlockDelay = 60 * 60 * 72;
|
||||||
maxUnlockPercentage = 100; // 1% maximum unlock per epoch
|
maxUnlockPercentage = 100; // 1% maximum unlock per epoch
|
||||||
|
sellStakePremiumPercent = 1000; // 10% premium for contract-owned sellStakes
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ownership Management
|
// Ownership Management
|
||||||
@@ -212,40 +215,40 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
priceOracles[_token] = _oracle;
|
priceOracles[_token] = _oracle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @notice Set unlock schedule for a token using percentage-based steps
|
// /// @notice Set unlock schedule for a token using percentage-based steps
|
||||||
/// @param _token The token address to set the schedule for
|
// /// @param _token The token address to set the schedule for
|
||||||
/// @param _lockTime The initial lock time in seconds
|
// /// @param _lockTime The initial lock time in seconds
|
||||||
/// @param _percentagePerStep The percentage to unlock at each step (scaled by 10000)
|
// /// @param _percentagePerStep The percentage to unlock at each step (scaled by 10000)
|
||||||
function setUnlockScheduleByPercentage(address _token, uint256 _lockTime, uint256 _percentagePerStep) external onlyOwner {
|
// function setUnlockScheduleByPercentage(address _token, uint256 _lockTime, uint256 _percentagePerStep) external onlyOwner {
|
||||||
require(_token != address(0), "Invalid token address");
|
// require(_token != address(0), "Invalid token address");
|
||||||
require(_percentagePerStep > 0 && _percentagePerStep <= 10000, "Invalid percentage");
|
// require(_percentagePerStep > 0 && _percentagePerStep <= 10000, "Invalid percentage");
|
||||||
|
|
||||||
// Clear existing schedule
|
// // Clear existing schedule
|
||||||
delete unlockSchedules[_token];
|
// delete unlockSchedules[_token];
|
||||||
|
|
||||||
uint256 totalPercentage = 0;
|
// uint256 totalPercentage = 0;
|
||||||
uint256 timeOffset = _lockTime;
|
// uint256 timeOffset = _lockTime;
|
||||||
|
|
||||||
// Create unlock steps until we reach 100%
|
// // Create unlock steps until we reach 100%
|
||||||
while (totalPercentage < 10000) {
|
// while (totalPercentage < 10000) {
|
||||||
uint256 stepPercentage = _percentagePerStep;
|
// uint256 stepPercentage = _percentagePerStep;
|
||||||
|
|
||||||
// Adjust last step to exactly reach 100%
|
// // Adjust last step to exactly reach 100%
|
||||||
if (totalPercentage + stepPercentage > 10000) {
|
// if (totalPercentage + stepPercentage > 10000) {
|
||||||
stepPercentage = 10000 - totalPercentage;
|
// stepPercentage = 10000 - totalPercentage;
|
||||||
}
|
// }
|
||||||
|
|
||||||
unlockSchedules[_token].push(UnlockStep({
|
// unlockSchedules[_token].push(UnlockStep({
|
||||||
timeOffset: timeOffset,
|
// timeOffset: timeOffset,
|
||||||
percentage: stepPercentage
|
// percentage: stepPercentage
|
||||||
}));
|
// }));
|
||||||
|
|
||||||
totalPercentage += stepPercentage;
|
// totalPercentage += stepPercentage;
|
||||||
timeOffset += _lockTime; // Each step adds the same time interval
|
// timeOffset += _lockTime; // Each step adds the same time interval
|
||||||
}
|
// }
|
||||||
|
|
||||||
emit UnlockScheduleSet(_token);
|
// emit UnlockScheduleSet(_token);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// /// @notice Set custom unlock schedule for a token with specific steps
|
// /// @notice Set custom unlock schedule for a token with specific steps
|
||||||
// /// @param _token The token address to set the schedule for
|
// /// @param _token The token address to set the schedule for
|
||||||
@@ -297,6 +300,13 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
instantBuyoutPercent = _newPercent;
|
instantBuyoutPercent = _newPercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @notice Update contract-owned sellStake premium percentage
|
||||||
|
/// @param _newPremium The premium percentage (scaled by 10000), ex: 1000 = 10%
|
||||||
|
function updateSellStakePremiumPercent(uint256 _newPremium) external onlyOwner {
|
||||||
|
require(_newPremium <= 5000, "Premium cannot exceed 50%");
|
||||||
|
sellStakePremiumPercent = _newPremium;
|
||||||
|
}
|
||||||
|
|
||||||
// Epoch-based Staking Functions
|
// Epoch-based Staking Functions
|
||||||
|
|
||||||
/// @notice Internal function to calculate unlock percentage based on ratio improvement vs historical high
|
/// @notice Internal function to calculate unlock percentage based on ratio improvement vs historical high
|
||||||
@@ -440,17 +450,44 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
WithdrawStake storage stake = userStakes[i];
|
WithdrawStake storage stake = userStakes[i];
|
||||||
if (stake.stakeId == stakeId && stake.amount > 0) {
|
if (stake.stakeId == stakeId && stake.amount > 0) {
|
||||||
require(block.timestamp >= stake.unlockTime, "Stake locked");
|
require(block.timestamp >= stake.unlockTime, "Stake locked");
|
||||||
|
|
||||||
|
// Check if there's a contract-owned sellStake linked to this withdrawStake
|
||||||
|
uint256 contractSellStakeId = contractOwnedSellStakes[msg.sender][stakeId];
|
||||||
|
if (contractSellStakeId > 0) {
|
||||||
|
SellStake storage contractSellStake = sellStakes[address(this)][contractSellStakeId];
|
||||||
|
if (contractSellStake.value > 0) {
|
||||||
|
uint256 sellValue = contractSellStake.value;
|
||||||
|
|
||||||
|
// Cancel the contract-owned sellStake (no fee)
|
||||||
|
pendingSellStakes[address(this)] -= sellValue;
|
||||||
|
delete sellStakes[address(this)][contractSellStakeId];
|
||||||
|
|
||||||
|
// Remove from keys array using swap-and-pop
|
||||||
|
uint256 keyIndex = sellStakeKeyIndex[address(this)][contractSellStakeId];
|
||||||
|
uint256 lastKeyIndex = sellStakeKeys.length - 1;
|
||||||
|
if (keyIndex != lastKeyIndex) {
|
||||||
|
SellStakeKey memory lastKey = sellStakeKeys[lastKeyIndex];
|
||||||
|
sellStakeKeys[keyIndex] = lastKey;
|
||||||
|
sellStakeKeyIndex[lastKey.seller][lastKey.stakeId] = keyIndex;
|
||||||
|
}
|
||||||
|
sellStakeKeys.pop();
|
||||||
|
delete sellStakeKeyIndex[address(this)][contractSellStakeId];
|
||||||
|
|
||||||
|
emit StakeSaleCancelled(address(this), contractSellStakeId);
|
||||||
|
}
|
||||||
|
delete contractOwnedSellStakes[msg.sender][stakeId];
|
||||||
|
}
|
||||||
|
|
||||||
uint256 amount = stake.amount;
|
uint256 amount = stake.amount;
|
||||||
address token = stake.token;
|
address token = stake.token;
|
||||||
stake.amount = 0; // Mark as withdrawn
|
stake.amount = 0; // Mark as withdrawn
|
||||||
|
|
||||||
// Decrement withdraw liabilities for all tokens
|
// Decrement withdraw liabilities for all tokens
|
||||||
withdrawLiabilities[token] -= amount;
|
withdrawLiabilities[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);
|
||||||
|
|
||||||
emit StakeWithdrawn(msg.sender, amount, stakeId);
|
emit StakeWithdrawn(msg.sender, amount, stakeId);
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
@@ -485,16 +522,40 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
|
|
||||||
// Create withdrawable stake with unlock delay (like claimUnlockedFunds)
|
// Create withdrawable stake with unlock delay (like claimUnlockedFunds)
|
||||||
stakeIdCounter++;
|
stakeIdCounter++;
|
||||||
|
uint256 withdrawStakeId = stakeIdCounter;
|
||||||
withdrawStakes[msg.sender].push(WithdrawStake({
|
withdrawStakes[msg.sender].push(WithdrawStake({
|
||||||
stakeId: stakeIdCounter,
|
stakeId: withdrawStakeId,
|
||||||
amount: payoutAmount,
|
amount: payoutAmount,
|
||||||
unlockTime: block.timestamp + unlockDelay,
|
unlockTime: block.timestamp + unlockDelay,
|
||||||
token: BSC_TOKEN
|
token: BSC_TOKEN
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Increment withdraw liabilities for BSC_TOKEN
|
// Increment withdraw liabilities for BSC_TOKEN
|
||||||
withdrawLiabilities[BSC_TOKEN] += payoutAmount;
|
withdrawLiabilities[BSC_TOKEN] += payoutAmount;
|
||||||
|
|
||||||
|
// Create contract-owned sellStake at premium price
|
||||||
|
uint256 sellStakePrice = (payoutAmount * (10000 + sellStakePremiumPercent)) / 10000;
|
||||||
|
stakeIdCounter++;
|
||||||
|
uint256 sellStakeId = stakeIdCounter;
|
||||||
|
|
||||||
|
sellStakes[address(this)][sellStakeId] = SellStake({
|
||||||
|
value: amount, // Original amount - what buyer receives in stake
|
||||||
|
salePrice: sellStakePrice, // Premium price buyer pays
|
||||||
|
seller: address(this),
|
||||||
|
listTime: block.timestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
pendingSellStakes[address(this)] += amount;
|
||||||
|
contractOwnedSellStakes[msg.sender][withdrawStakeId] = sellStakeId; // Link withdrawStake ID to sellStake ID
|
||||||
|
|
||||||
|
// Add to iteration keys
|
||||||
|
sellStakeKeys.push(SellStakeKey({
|
||||||
|
seller: address(this),
|
||||||
|
stakeId: sellStakeId
|
||||||
|
}));
|
||||||
|
sellStakeKeyIndex[address(this)][sellStakeId] = sellStakeKeys.length - 1;
|
||||||
|
|
||||||
|
emit StakeUpForSale(address(this), sellStakePrice, sellStakeId);
|
||||||
emit FundsClaimed(msg.sender, payoutAmount);
|
emit FundsClaimed(msg.sender, payoutAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,10 +883,17 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
userLastClaimedEpoch[msg.sender] = currentEpochId;
|
userLastClaimedEpoch[msg.sender] = currentEpochId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer stakes: remove from seller, add to buyer (minus protocol share)
|
// Transfer stakes: handle contract seller differently
|
||||||
userBigStake[seller] -= value;
|
if (seller == address(this)) {
|
||||||
userBigStake[msg.sender] += buyerStake;
|
// Contract is seller - only add to buyer, don't deduct from contract
|
||||||
pendingSellStakes[seller] -= value;
|
userBigStake[msg.sender] += buyerStake;
|
||||||
|
pendingSellStakes[address(this)] -= value;
|
||||||
|
} else {
|
||||||
|
// Normal seller flow - remove from seller, add to buyer (minus protocol share)
|
||||||
|
userBigStake[seller] -= value;
|
||||||
|
userBigStake[msg.sender] += buyerStake;
|
||||||
|
pendingSellStakes[seller] -= value;
|
||||||
|
}
|
||||||
|
|
||||||
// Increment buyer's original stake tracking (marketplace purchases count as original stake)
|
// Increment buyer's original stake tracking (marketplace purchases count as original stake)
|
||||||
userOriginalStake[msg.sender] += buyerStake;
|
userOriginalStake[msg.sender] += buyerStake;
|
||||||
@@ -1383,7 +1451,7 @@ contract CunaFinanceBsc is Initializable, ReentrancyGuardUpgradeable {
|
|||||||
/// @notice Test function for upgrade verification
|
/// @notice Test function for upgrade verification
|
||||||
/// @return Returns a constant value to verify upgrade worked
|
/// @return Returns a constant value to verify upgrade worked
|
||||||
function testUpgradeFunction() external pure returns (uint256) {
|
function testUpgradeFunction() external pure returns (uint256) {
|
||||||
return 1001; // Different value from bsc_paca to distinguish contracts
|
return 1003; // Different value from bsc_paca to distinguish contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -33,42 +33,75 @@ module.exports = {
|
|||||||
url: "http://127.0.0.1:8545",
|
url: "http://127.0.0.1:8545",
|
||||||
},
|
},
|
||||||
bscTestnet: {
|
bscTestnet: {
|
||||||
url: "https://virtual.binance.eu.rpc.tenderly.co/77a9a30c-44ca-45e0-ae2a-9b5ddb41d20e",
|
url: "https://virtual.binance.eu.rpc.tenderly.co/fbd4f88f-4e4c-4894-a197-2272e3b3d197",
|
||||||
chainId: 1337,
|
chainId: 1337,
|
||||||
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
},
|
},
|
||||||
anvil: {
|
anvil: {
|
||||||
url: "https://rpc.tacopancake.com",
|
url: "https://virtual.binance.eu.rpc.tenderly.co/7c672a28-191d-4bb0-9af8-5ae1e4464c5d",
|
||||||
chainId: 1337,
|
chainId: 1337,
|
||||||
accounts: [
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [
|
||||||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // Default Anvil account 0
|
// "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", // Default Anvil account 0
|
||||||
],
|
],
|
||||||
|
allowUnlimitedContractSize: true,
|
||||||
},
|
},
|
||||||
buildbear: {
|
buildbear: {
|
||||||
url: "https://rpc.buildbear.io/stale-bucky-0f42044d",
|
url: "https://rpc.buildbear.io/interim-warlock-36006e5c",
|
||||||
chainId: 31337,
|
chainId: 31337,
|
||||||
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
},
|
},
|
||||||
|
basefork: {
|
||||||
|
url: "https://rpc.buildbear.io/yodelling-wong-30e77094",
|
||||||
|
chainId: 31337,
|
||||||
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
|
},
|
||||||
|
mainnet: {
|
||||||
|
url: env.MAINNET_RPC_URL || "https://bsc-dataseed1.binance.org",
|
||||||
|
chainId: 56,
|
||||||
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
|
},
|
||||||
|
base: {
|
||||||
|
url: env.BASE_RPC_URL || "https://base-mainnet.public.blastapi.io",
|
||||||
|
chainId: 8453,
|
||||||
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
|
},
|
||||||
|
sonic: {
|
||||||
|
url: env.SONIC_RPC_URL || "https://rpc.soniclabs.com",
|
||||||
|
chainId: 146,
|
||||||
|
accounts: env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
etherscan: {
|
etherscan: {
|
||||||
apiKey: {
|
apiKey: "JRKR5T93RQTKVERUJ2NVE4T994TD66QGFH",
|
||||||
bsc: env.BSCSCAN_API_KEY || "",
|
|
||||||
bscTestnet: env.BSCSCAN_API_KEY || "",
|
|
||||||
buildbear: "abc", // API key not needed for BuildBear
|
|
||||||
},
|
|
||||||
customChains: [
|
customChains: [
|
||||||
{
|
{
|
||||||
network: "buildbear",
|
network: "buildbear",
|
||||||
chainId: 31337,
|
chainId: 31337,
|
||||||
urls: {
|
urls: {
|
||||||
apiURL: "https://rpc.buildbear.io/verify/etherscan/stale-bucky-0f42044d",
|
apiURL: "https://rpc.buildbear.io/verify/etherscan/missing-darkphoenix-ad09c709",
|
||||||
browserURL: "https://explorer.buildbear.io/stale-bucky-0f42044d"
|
browserURL: "https://explorer.buildbear.io/missing-darkphoenix-ad09c709"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
network: "base",
|
||||||
|
chainId: 8453,
|
||||||
|
urls: {
|
||||||
|
apiURL: "https://api.basescan.org/api",
|
||||||
|
browserURL: "https://basescan.org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
network: "sonic",
|
||||||
|
chainId: 146,
|
||||||
|
urls: {
|
||||||
|
apiURL: "https://api.sonicscan.org/api",
|
||||||
|
browserURL: "https://sonicscan.org"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
sourcify: {
|
sourcify: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
apiUrl: "https://rpc.buildbear.io/verify/sourcify/server/stale-bucky-0f42044d"
|
apiUrl: "https://rpc.buildbear.io/verify/sourcify/server/missing-darkphoenix-ad09c709"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user