2024-02-21 22:20:04 +01:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
2024-02-23 22:01:23 +01:00
|
|
|
|
2024-03-28 19:55:01 +01:00
|
|
|
pragma solidity ^0.8.19;
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-03-12 20:22:10 +01:00
|
|
|
import {IERC20} from "@openzeppelin/token/ERC20/ERC20.sol";
|
2024-03-12 15:29:59 +01:00
|
|
|
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";
|
2024-04-11 07:28:54 +02:00
|
|
|
import "@openzeppelin/token/ERC20/extensions/ERC20Permit.sol";
|
2024-03-12 20:22:10 +01:00
|
|
|
import {SafeERC20} from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
|
2024-03-12 15:29:59 +01:00
|
|
|
import {Math} from "@openzeppelin/utils/math/Math.sol";
|
|
|
|
|
import "./Harb.sol";
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-03-14 17:31:16 +01:00
|
|
|
error ExceededAvailableStake(address receiver, uint256 stakeWanted, uint256 availableStake);
|
2024-06-07 11:22:22 +02:00
|
|
|
error TooMuchSnatch(address receiver, uint256 stakeWanted, uint256 availableStake, uint256 smallestShare);
|
2024-03-14 17:31:16 +01:00
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
contract Stake {
|
2024-03-12 15:29:59 +01:00
|
|
|
using Math for uint256;
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-03-12 15:29:59 +01:00
|
|
|
uint256 internal DECIMAL_OFFSET = 5 + 2;
|
2024-03-12 20:22:10 +01:00
|
|
|
uint256 internal constant MAX_STAKE = 20; // 20% of HARB supply
|
2024-02-21 22:20:04 +01:00
|
|
|
uint256 internal constant TAX_RATE_BASE = 100;
|
2024-02-23 22:01:23 +01:00
|
|
|
uint256 internal constant TAX_FLOOR_DURATION = 60 * 60 * 24 * 3; //this duration is the minimum basis for fee calculation, regardless of actual holding time.
|
2024-06-13 10:50:09 +02:00
|
|
|
uint256[] public TAX_RATES = [1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
|
2024-02-21 22:20:04 +01:00
|
|
|
/**
|
|
|
|
|
* @dev Attempted to deposit more assets than the max amount for `receiver`.
|
|
|
|
|
*/
|
2024-03-12 20:22:10 +01:00
|
|
|
|
2024-02-21 22:20:04 +01:00
|
|
|
error TaxTooLow(address receiver, uint64 taxRateWanted, uint64 taxRateMet, uint256 positionId);
|
|
|
|
|
error SharesTooLow(address receiver, uint256 assets, uint256 sharesWanted, uint256 minStake);
|
|
|
|
|
error NoPermission(address requester, address owner);
|
2024-06-19 10:33:28 +02:00
|
|
|
error PositionNotFound(uint256 positionId, address requester);
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-03-18 12:42:30 +01:00
|
|
|
event PositionCreated(uint256 indexed positionId, address indexed owner, uint256 share, uint32 creationTime, uint32 taxRate);
|
2024-04-15 07:08:13 +02:00
|
|
|
event TaxPaid(uint256 indexed positionId, address indexed owner, uint256 taxAmount);
|
2024-03-18 12:42:30 +01:00
|
|
|
event PositionRemoved(uint256 indexed positionId, uint256 share, uint32 lastTaxTime);
|
2024-06-07 11:22:22 +02:00
|
|
|
event PositionShrunk(uint256 indexed positionId, uint256 share, uint32 lastTaxTime, uint256 sharesTaken);
|
2024-03-18 12:42:30 +01:00
|
|
|
|
2024-02-21 22:20:04 +01:00
|
|
|
struct StakingPosition {
|
2024-02-23 22:01:23 +01:00
|
|
|
uint256 share;
|
2024-02-21 22:20:04 +01:00
|
|
|
address owner;
|
2024-02-23 22:01:23 +01:00
|
|
|
uint32 creationTime;
|
|
|
|
|
uint32 lastTaxTime;
|
2024-03-12 20:22:10 +01:00
|
|
|
uint32 taxRate; // e.g. value of 60 = 60% tax per year
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-23 22:01:23 +01:00
|
|
|
uint256 public immutable totalSupply;
|
2024-06-13 10:50:09 +02:00
|
|
|
Harb private immutable harb;
|
2024-02-23 22:01:23 +01:00
|
|
|
address private immutable taxPool;
|
2024-02-21 22:20:04 +01:00
|
|
|
uint256 public outstandingStake;
|
2024-03-18 12:42:30 +01:00
|
|
|
uint256 public nextPositionId;
|
2024-03-14 12:40:57 +01:00
|
|
|
mapping(uint256 => StakingPosition) public positions;
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-06-13 10:50:09 +02:00
|
|
|
constructor(address _harb) {
|
|
|
|
|
harb = Harb(_harb);
|
2024-03-12 20:22:10 +01:00
|
|
|
|
2024-06-13 10:50:09 +02:00
|
|
|
totalSupply = 10 ** (harb.decimals() + DECIMAL_OFFSET);
|
|
|
|
|
taxPool = Harb(_harb).TAX_POOL();
|
2024-06-13 08:28:42 +02:00
|
|
|
nextPositionId = 654321;
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-12 20:22:10 +01:00
|
|
|
function authorizedStake() private view returns (uint256) {
|
2024-03-12 11:38:16 +01:00
|
|
|
return totalSupply * MAX_STAKE / 100;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
function assetsToShares(uint256 assets) public view returns (uint256) {
|
|
|
|
|
return assets.mulDiv(totalSupply, harb.totalSupply(), Math.Rounding.Down);
|
2024-06-13 10:50:09 +02:00
|
|
|
//return assets.mulDiv(totalSupply, harb.totalSupply() + 1, rounding);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
function sharesToAssets(uint256 shares) public view returns (uint256) {
|
2024-06-13 10:50:09 +02:00
|
|
|
//return shares.mulDiv(harb.totalSupply() + 1, totalSupply, rounding);
|
|
|
|
|
// TODO: should the average total supply be used for this calculation?
|
2024-06-19 10:33:28 +02:00
|
|
|
return shares.mulDiv(harb.totalSupply(), totalSupply, Math.Rounding.Down);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-11 07:28:54 +02:00
|
|
|
function permitAndSnatch(
|
|
|
|
|
uint256 assets,
|
|
|
|
|
address receiver,
|
|
|
|
|
uint32 taxRate,
|
|
|
|
|
uint256[] calldata positionsToSnatch,
|
|
|
|
|
// address owner,
|
|
|
|
|
// address spender,
|
|
|
|
|
// uint256 value,
|
|
|
|
|
uint256 deadline,
|
|
|
|
|
uint8 v,
|
|
|
|
|
bytes32 r,
|
|
|
|
|
bytes32 s
|
|
|
|
|
) external
|
|
|
|
|
returns (uint256 positionId)
|
|
|
|
|
{
|
2024-06-13 10:50:09 +02:00
|
|
|
ERC20Permit(address(harb)).permit(receiver, address(this), assets, deadline, v, r, s);
|
2024-04-11 07:28:54 +02:00
|
|
|
return snatch(assets, receiver, taxRate, positionsToSnatch);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 20:22:10 +01:00
|
|
|
/**
|
|
|
|
|
* TODO: deal with metatransactions: While these are generally available
|
2024-03-12 11:38:16 +01:00
|
|
|
* via msg.sender and msg.data, they should not be accessed in such a direct
|
|
|
|
|
* manner, since when dealing with meta-transactions the account sending and
|
|
|
|
|
* paying for execution may not be the actual sender (as far as an application
|
|
|
|
|
* is concerned).
|
|
|
|
|
*/
|
2024-03-12 20:22:10 +01:00
|
|
|
function snatch(uint256 assets, address receiver, uint32 taxRate, uint256[] calldata positionsToSnatch)
|
|
|
|
|
public
|
2024-03-18 12:42:30 +01:00
|
|
|
returns (uint256 positionId)
|
2024-03-12 20:22:10 +01:00
|
|
|
{
|
2024-02-21 22:20:04 +01:00
|
|
|
// check lower boundary
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 sharesWanted = assetsToShares(assets);
|
2024-06-13 10:50:09 +02:00
|
|
|
{
|
2024-06-19 10:33:28 +02:00
|
|
|
// check that position size is at least minStake
|
2024-06-13 10:50:09 +02:00
|
|
|
// to prevent excessive fragmentation, increasing snatch cost
|
|
|
|
|
uint256 minStake = harb.previousTotalSupply() / 3000;
|
|
|
|
|
if (sharesWanted < minStake) {
|
|
|
|
|
revert SharesTooLow(receiver, assets, sharesWanted, minStake);
|
|
|
|
|
}
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
2024-06-13 10:50:09 +02:00
|
|
|
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
|
2024-02-21 22:20:04 +01:00
|
|
|
|
2024-06-07 11:22:22 +02:00
|
|
|
uint256 smallestPositionShare = totalSupply;
|
2024-06-07 12:33:20 +02:00
|
|
|
uint256 availableStake = authorizedStake() - outstandingStake;
|
2024-06-07 11:22:22 +02:00
|
|
|
|
2024-06-13 08:28:42 +02:00
|
|
|
if (positionsToSnatch.length >= 2) {
|
|
|
|
|
// run through all but last positions to snatch
|
2024-06-07 12:33:20 +02:00
|
|
|
for (uint256 i = 0; i < positionsToSnatch.length - 1; i++) {
|
|
|
|
|
StakingPosition storage pos = positions[positionsToSnatch[i]];
|
|
|
|
|
if (pos.creationTime == 0) {
|
2024-06-19 10:33:28 +02:00
|
|
|
revert PositionNotFound(positionsToSnatch[i], receiver);
|
2024-06-07 12:33:20 +02:00
|
|
|
}
|
|
|
|
|
// check that tax lower
|
|
|
|
|
if (taxRate <= pos.taxRate) {
|
|
|
|
|
revert TaxTooLow(receiver, taxRate, pos.taxRate, i);
|
|
|
|
|
}
|
|
|
|
|
if (pos.share < smallestPositionShare) {
|
|
|
|
|
smallestPositionShare = pos.share;
|
|
|
|
|
}
|
|
|
|
|
// dissolve position
|
|
|
|
|
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
|
|
|
|
_payTax(positionsToSnatch[i], pos, 0);
|
|
|
|
|
_exitPosition(positionsToSnatch[i], pos);
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
2024-06-13 08:28:42 +02:00
|
|
|
}
|
|
|
|
|
availableStake = authorizedStake() - outstandingStake;
|
2024-03-12 20:22:10 +01:00
|
|
|
|
2024-06-13 08:28:42 +02:00
|
|
|
if (positionsToSnatch.length > 0) {
|
|
|
|
|
// handle last position, either shrink or snatch
|
2024-06-07 11:22:22 +02:00
|
|
|
uint256 index = positionsToSnatch.length - 1;
|
2024-06-09 16:06:41 +02:00
|
|
|
StakingPosition storage lastPos = positions[positionsToSnatch[index]];
|
|
|
|
|
if (lastPos.creationTime == 0) {
|
2024-06-07 11:22:22 +02:00
|
|
|
//TODO:
|
2024-06-19 10:33:28 +02:00
|
|
|
revert PositionNotFound(positionsToSnatch[index], receiver);
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
|
|
|
|
// check that tax lower
|
2024-06-09 16:06:41 +02:00
|
|
|
if (taxRate <= lastPos.taxRate) {
|
|
|
|
|
revert TaxTooLow(receiver, taxRate, lastPos.taxRate, index);
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
2024-06-09 16:06:41 +02:00
|
|
|
if (lastPos.share < smallestPositionShare) {
|
|
|
|
|
smallestPositionShare = lastPos.share;
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
|
|
|
|
// dissolve position
|
2024-06-09 16:06:41 +02:00
|
|
|
_payTax(positionsToSnatch[index], lastPos, 0);
|
2024-06-13 08:28:42 +02:00
|
|
|
if (availableStake > sharesWanted) {
|
|
|
|
|
revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare);
|
|
|
|
|
}
|
|
|
|
|
uint256 lastSharesNeeded = sharesWanted - availableStake;
|
|
|
|
|
if (lastSharesNeeded > lastPos.share * 80 / 100) {
|
|
|
|
|
_exitPosition(positionsToSnatch[index], lastPos);
|
|
|
|
|
} else {
|
|
|
|
|
_shrinkPosition(positionsToSnatch[index], lastPos, lastSharesNeeded);
|
|
|
|
|
}
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
2024-06-13 08:28:42 +02:00
|
|
|
availableStake = authorizedStake() - outstandingStake;
|
2024-06-07 11:22:22 +02:00
|
|
|
|
2024-02-21 22:20:04 +01:00
|
|
|
if (sharesWanted > availableStake) {
|
|
|
|
|
revert ExceededAvailableStake(receiver, sharesWanted, availableStake);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-07 11:22:22 +02:00
|
|
|
// avoid greeving where more positions are freed than needed.
|
|
|
|
|
if (availableStake - sharesWanted > smallestPositionShare) {
|
|
|
|
|
revert TooMuchSnatch(receiver, sharesWanted, availableStake, smallestPositionShare);
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 22:20:04 +01:00
|
|
|
// transfer
|
2024-06-13 10:50:09 +02:00
|
|
|
SafeERC20.safeTransferFrom(harb, msg.sender, address(this), assets);
|
2024-02-21 22:20:04 +01:00
|
|
|
|
|
|
|
|
// mint
|
2024-03-18 12:42:30 +01:00
|
|
|
positionId = nextPositionId++;
|
|
|
|
|
StakingPosition storage sp = positions[positionId];
|
2024-03-12 11:38:16 +01:00
|
|
|
sp.share = sharesWanted;
|
2024-02-21 22:20:04 +01:00
|
|
|
sp.owner = receiver;
|
2024-03-12 12:27:47 +01:00
|
|
|
sp.lastTaxTime = uint32(block.timestamp);
|
|
|
|
|
sp.creationTime = uint32(block.timestamp);
|
|
|
|
|
sp.taxRate = taxRate;
|
2024-02-21 22:20:04 +01:00
|
|
|
|
|
|
|
|
outstandingStake += sharesWanted;
|
2024-03-18 12:42:30 +01:00
|
|
|
emit PositionCreated(positionId, sp.owner, sp.share, sp.creationTime, sp.taxRate);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
function changeTax(uint256 positionId, uint32 taxRate) public {
|
2024-06-13 10:50:09 +02:00
|
|
|
require(taxRate < TAX_RATES.length, "tax rate out of bounds");
|
2024-06-19 10:33:28 +02:00
|
|
|
StakingPosition storage pos = positions[positionId];
|
|
|
|
|
if (pos.creationTime == 0) {
|
|
|
|
|
revert PositionNotFound(positionId, msg.sender);
|
|
|
|
|
}
|
2024-04-15 07:08:13 +02:00
|
|
|
if (pos.owner != msg.sender) {
|
|
|
|
|
revert NoPermission(msg.sender, pos.owner);
|
|
|
|
|
}
|
|
|
|
|
// to prevent snatch-and-change grieving attack, pay TAX_FLOOR_DURATION
|
2024-06-13 10:50:09 +02:00
|
|
|
require(taxRate > pos.taxRate, "tax too low to snatch");
|
2024-06-19 10:33:28 +02:00
|
|
|
_payTax(positionId, pos, TAX_FLOOR_DURATION);
|
2024-04-15 07:08:13 +02:00
|
|
|
pos.taxRate = taxRate;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-18 12:42:30 +01:00
|
|
|
function exitPosition(uint256 positionId) public {
|
|
|
|
|
StakingPosition storage pos = positions[positionId];
|
2024-06-19 10:33:28 +02:00
|
|
|
if (pos.creationTime == 0) {
|
|
|
|
|
revert PositionNotFound(positionId, msg.sender);
|
|
|
|
|
}
|
2024-03-12 20:22:10 +01:00
|
|
|
if (pos.owner != msg.sender) {
|
2024-03-12 15:29:59 +01:00
|
|
|
revert NoPermission(msg.sender, pos.owner);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
2024-03-12 11:38:16 +01:00
|
|
|
// to prevent snatch-and-exit grieving attack, pay TAX_FLOOR_DURATION
|
2024-04-15 07:08:13 +02:00
|
|
|
_payTax(positionId, pos, TAX_FLOOR_DURATION);
|
2024-03-18 12:42:30 +01:00
|
|
|
_exitPosition(positionId, pos);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
2024-03-12 20:22:10 +01:00
|
|
|
|
2024-06-13 10:50:09 +02:00
|
|
|
// TODO: write a bot that calls this function regularly
|
2024-06-19 10:33:28 +02:00
|
|
|
function payTax(uint256 positionId) public {
|
|
|
|
|
StakingPosition storage pos = positions[positionId];
|
2024-03-12 12:27:47 +01:00
|
|
|
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
2024-06-19 10:33:28 +02:00
|
|
|
_payTax(positionId, pos, 0);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
function taxDue(uint256 positionId, uint256 taxFloorDuration) public view returns (uint256 amountDue) {
|
|
|
|
|
StakingPosition storage pos = positions[positionId];
|
2024-03-12 15:29:59 +01:00
|
|
|
// ihet = Implied Holding Expiry Timestamp
|
2024-03-12 20:22:10 +01:00
|
|
|
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
|
|
|
|
? pos.creationTime + taxFloorDuration
|
|
|
|
|
: block.timestamp;
|
2024-03-12 15:29:59 +01:00
|
|
|
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 assetsBefore = sharesToAssets(pos.share);
|
2024-06-09 16:06:41 +02:00
|
|
|
amountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
2024-03-12 15:29:59 +01:00
|
|
|
}
|
|
|
|
|
|
2024-06-19 10:33:28 +02:00
|
|
|
function _payTax(uint256 positionId, StakingPosition storage pos, uint256 taxFloorDuration) private {
|
2024-02-23 22:01:23 +01:00
|
|
|
// ihet = Implied Holding Expiry Timestamp
|
2024-03-12 20:22:10 +01:00
|
|
|
uint256 ihet = (block.timestamp - pos.creationTime < taxFloorDuration)
|
|
|
|
|
? pos.creationTime + taxFloorDuration
|
|
|
|
|
: block.timestamp;
|
2024-02-23 22:01:23 +01:00
|
|
|
uint256 elapsedTime = ihet - pos.lastTaxTime;
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 assetsBefore = sharesToAssets(pos.share);
|
2024-06-09 16:06:41 +02:00
|
|
|
uint256 taxAmountDue = assetsBefore * TAX_RATES[pos.taxRate] * elapsedTime / (365 * 24 * 60 * 60) / TAX_RATE_BASE;
|
2024-03-12 15:29:59 +01:00
|
|
|
if (taxAmountDue >= assetsBefore) {
|
2024-02-21 22:20:04 +01:00
|
|
|
// can not pay more tax than value of position
|
2024-03-12 15:29:59 +01:00
|
|
|
taxAmountDue = assetsBefore;
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
2024-06-13 10:50:09 +02:00
|
|
|
SafeERC20.safeTransfer(harb, taxPool, taxAmountDue);
|
2024-06-19 10:33:28 +02:00
|
|
|
emit TaxPaid(positionId, pos.owner, taxAmountDue);
|
2024-03-12 15:29:59 +01:00
|
|
|
if (assetsBefore - taxAmountDue > 0) {
|
2024-02-21 22:20:04 +01:00
|
|
|
// if something left over, update storage
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 shareAfterTax = assetsToShares(assetsBefore - taxAmountDue);
|
2024-06-13 08:28:42 +02:00
|
|
|
outstandingStake -= pos.share - shareAfterTax;
|
|
|
|
|
pos.share = shareAfterTax;
|
2024-03-12 12:27:47 +01:00
|
|
|
pos.lastTaxTime = uint32(block.timestamp);
|
2024-02-21 22:20:04 +01:00
|
|
|
} else {
|
|
|
|
|
// if nothing left over, liquidate position
|
2024-03-12 11:38:16 +01:00
|
|
|
outstandingStake -= pos.share;
|
2024-06-19 10:33:28 +02:00
|
|
|
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
2024-03-12 12:27:47 +01:00
|
|
|
delete pos.owner;
|
|
|
|
|
delete pos.creationTime;
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-18 12:42:30 +01:00
|
|
|
function _exitPosition(uint256 positionId, StakingPosition storage pos) private {
|
2024-02-23 22:01:23 +01:00
|
|
|
outstandingStake -= pos.share;
|
2024-02-21 22:20:04 +01:00
|
|
|
address owner = pos.owner;
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 assets = sharesToAssets(pos.share);
|
2024-03-18 12:42:30 +01:00
|
|
|
emit PositionRemoved(positionId, pos.share, pos.lastTaxTime);
|
2024-03-12 12:27:47 +01:00
|
|
|
delete pos.owner;
|
|
|
|
|
delete pos.creationTime;
|
2024-06-13 10:50:09 +02:00
|
|
|
SafeERC20.safeTransfer(harb, owner, assets);
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|
2024-06-07 11:22:22 +02:00
|
|
|
|
|
|
|
|
function _shrinkPosition(uint256 positionId, StakingPosition storage pos, uint256 sharesToTake) private {
|
2024-06-13 08:28:42 +02:00
|
|
|
require (sharesToTake < pos.share, "position too small");
|
2024-06-19 10:33:28 +02:00
|
|
|
uint256 assets = sharesToAssets(sharesToTake);
|
2024-06-13 08:28:42 +02:00
|
|
|
pos.share -= sharesToTake;
|
2024-06-07 11:22:22 +02:00
|
|
|
emit PositionShrunk(positionId, pos.share, pos.lastTaxTime, sharesToTake);
|
2024-06-13 10:50:09 +02:00
|
|
|
SafeERC20.safeTransfer(harb, pos.owner, assets);
|
2024-06-07 11:22:22 +02:00
|
|
|
}
|
2024-02-21 22:20:04 +01:00
|
|
|
}
|