Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement stake/unstake/extend methods (#418) #474

Merged
merged 26 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a0776a1
add stake/unstake methods (#418)
doerfli Jul 2, 2024
4023c64
implenent bundle staking (#418)
doerfli Jul 2, 2024
7032395
partially implenent unstake (#418)
doerfli Jul 2, 2024
bad193e
add fixme (#418)
doerfli Jul 2, 2024
611b6ca
add todos (#418)
doerfli Jul 2, 2024
64e8a66
add TODO
doerfli Jul 2, 2024
ed90874
add extend method (#418)
doerfli Jul 2, 2024
80b9543
implement expire bundle (#418)
doerfli Jul 3, 2024
b260da0
add TODOs (#418)
doerfli Jul 3, 2024
b8ad371
replace IBundle.lifetime with IBundle.activatedAt (#418)
doerfli Jul 3, 2024
0ab05fb
implement stake (#418)
doerfli Jul 3, 2024
0687846
implement unstake without performance fees (#418)
doerfli Jul 3, 2024
bab6a62
add happy day testcase for bundle staking (#418)
doerfli Jul 3, 2024
65fdd19
add test for allowance too small (#418)
doerfli Jul 3, 2024
f87b2b7
test for amount is zero (#418)
doerfli Jul 3, 2024
31e59c2
add test for expired and closed bundle (#418)
doerfli Jul 3, 2024
2c10528
add testcase for unstake happy path (#418)
doerfli Jul 3, 2024
69ccd9f
unstake all available (#418)
doerfli Jul 3, 2024
0354ccb
test for unstake exceeds available (#418)
doerfli Jul 3, 2024
84325da
add test for amount is zero (#418)
doerfli Jul 3, 2024
437f3a8
add testcase for allowance too small (#418)
doerfli Jul 3, 2024
ce1250f
add testcase for bundle extension (#418)
doerfli Jul 3, 2024
048dfa1
add tests for expired and closed bundle (#418)
doerfli Jul 3, 2024
1e176b6
add testcase for lifetime is zero (#418)
doerfli Jul 3, 2024
34c5786
remove productNftId from PoolInfo
doerfli Jul 3, 2024
b5cb301
fix deployment script (#418)
doerfli Jul 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contracts/instance/module/IBundle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IBundle {
NftId poolNftId;
Fee fee; // bundle fee on net premium amounts
bytes filter; // required conditions for applications to be considered for collateralization by this bundle
Seconds lifetime; // lifetime of bundle after creation
Timestamp activatedAt;
Timestamp expiredAt; // no new policies starting with this timestamp
Timestamp closedAt; // no open policies, locked amount = 0
}
Expand Down
1 change: 0 additions & 1 deletion contracts/instance/module/IComponents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ interface IComponents {


struct PoolInfo {
NftId productNftId; // the nft of the product this pool is linked to
RoleId bundleOwnerRole; // the required role for bundle owners
// TODO maxCapitalAmount -> maxBalanceAmount
Amount maxCapitalAmount; // max capital amount allowed for pool
Expand Down
4 changes: 3 additions & 1 deletion contracts/pool/BasicPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {NftId, NftIdLib} from "../type/NftId.sol";
import {BUNDLE, COMPONENT, POOL} from "../type/ObjectType.sol";
import {RoleId, PUBLIC_ROLE} from "../type/RoleId.sol";
import {Seconds} from "../type/Seconds.sol";
import {Timestamp} from "../type/Timestamp.sol";
import {TokenHandler} from "../shared/TokenHandler.sol";
import {UFixed, UFixedLib} from "../type/UFixed.sol";

Expand Down Expand Up @@ -81,8 +82,9 @@ abstract contract BasicPool is
virtual
restricted()
onlyBundleOwner(bundleNftId)
returns(Timestamp newExpiredAt)
{
_extend(bundleNftId, lifetimeExtension);
return _extend(bundleNftId, lifetimeExtension);
}


Expand Down
3 changes: 3 additions & 0 deletions contracts/pool/BasicPoolAuthorization.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ contract BasicPoolAuthorization
_authorize(functions, BasicPool.setMaxCapitalAmount.selector, "setMaxCapitalAmount");
_authorize(functions, BasicPool.setBundleOwnerRole.selector, "setBundleOwnerRole");
_authorize(functions, BasicPool.setFees.selector, "setFees");
_authorize(functions, BasicPool.stake.selector, "stake");
_authorize(functions, BasicPool.unstake.selector, "unstake");
_authorize(functions, BasicPool.extend.selector, "extend");

_authorize(functions, IInstanceLinkedComponent.withdrawFees.selector, "withdrawFees");

Expand Down
103 changes: 100 additions & 3 deletions contracts/pool/BundleService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import {IPolicy} from "../instance/module/IPolicy.sol";
import {Amount, AmountLib} from "../type/Amount.sol";
import {BundleSet} from "../instance/BundleSet.sol";
import {ComponentVerifyingService} from "../shared/ComponentVerifyingService.sol";
import {Fee} from "../type/Fee.sol";
import {Fee, FeeLib} from "../type/Fee.sol";
import {InstanceReader} from "../instance/InstanceReader.sol";
import {NftId, NftIdLib} from "../type/NftId.sol";
import {ObjectType, COMPONENT, POOL, BUNDLE, REGISTRY} from "../type/ObjectType.sol";
import {StateId, ACTIVE, PAUSED, CLOSED, KEEP_STATE} from "../type/StateId.sol";
import {Seconds} from "../type/Seconds.sol";
import {TimestampLib, zeroTimestamp} from "../type/Timestamp.sol";
import {Timestamp, TimestampLib, zeroTimestamp} from "../type/Timestamp.sol";

string constant BUNDLE_SERVICE_NAME = "BundleService";

Expand Down Expand Up @@ -117,7 +117,7 @@ contract BundleService is
poolNftId,
doerfli marked this conversation as resolved.
Show resolved Hide resolved
bundleFee,
filter,
lifetime,
TimestampLib.blockTimestamp(),
TimestampLib.blockTimestamp().addSeconds(lifetime),
zeroTimestamp()));

Expand Down Expand Up @@ -233,6 +233,103 @@ contract BundleService is
bundleManager.lock(bundleNftId);
}

/// @inheritdoc IBundleService
function stake(
IInstance instance,
NftId bundleNftId,
Amount amount
)
external
virtual
// TODO: restricted() (once #462 is done)
{
IBundle.BundleInfo memory bundleInfo = instance.getInstanceReader().getBundleInfo(bundleNftId);
StateId bundleState = instance.getInstanceReader().getMetadata(bundleNftId.toKey32(BUNDLE())).state;
doerfli marked this conversation as resolved.
Show resolved Hide resolved

if(bundleState != ACTIVE() || bundleInfo.expiredAt < TimestampLib.blockTimestamp() || bundleInfo.closedAt.gtz()) {
doerfli marked this conversation as resolved.
Show resolved Hide resolved
revert ErrorBundleServiceBundleNotOpen(bundleNftId, bundleState, bundleInfo.expiredAt);
}

_componentService.increaseBundleBalance(
instance.getInstanceStore(),
bundleNftId,
amount,
AmountLib.zero());
}

/// @inheritdoc IBundleService
function unstake(
IInstance instance,
NftId bundleNftId,
Amount amount
)
external
virtual
// TODO: restricted() (once #462 is done)
returns (Amount unstakedAmount)
{
InstanceStore instanceStore = instance.getInstanceStore();
(
Amount balanceAmount,
Amount lockedAmount,
Amount feeAmount
) = instanceStore.getAmounts(bundleNftId);

Amount unstakedAmount = amount;
Amount availableAmount = balanceAmount - (lockedAmount + feeAmount);

// if amount is max, then unstake all available
if (amount.gte(AmountLib.max())) {
unstakedAmount = availableAmount;
}

// ensure unstaked amount does not exceed available amount
if (unstakedAmount > availableAmount) {
revert ErrorBundleServiceUnstakeAmountExceedsLimit(amount, availableAmount);
}

_componentService.decreaseBundleBalance(
doerfli marked this conversation as resolved.
Show resolved Hide resolved
instanceStore,
bundleNftId,
unstakedAmount,
AmountLib.zero());

return unstakedAmount;
}

/// @inheritdoc IBundleService
function extend(NftId bundleNftId, Seconds lifetimeExtension)
external
virtual
// TODO: restricted() (once #462 is done)
returns (Timestamp extendedExpiredAt)
{
(NftId poolNftId,, IInstance instance) = _getAndVerifyActiveComponent(POOL());
IBundle.BundleInfo memory bundleInfo = instance.getInstanceReader().getBundleInfo(bundleNftId);
StateId bundleState = instance.getInstanceReader().getMetadata(bundleNftId.toKey32(BUNDLE())).state;
doerfli marked this conversation as resolved.
Show resolved Hide resolved

// ensure bundle belongs to the pool
if (bundleInfo.poolNftId != poolNftId) {
revert ErrorBundleServiceBundlePoolMismatch(bundleNftId, bundleInfo.poolNftId, poolNftId);
}

// ensure bundle is active and not yet expired
if(bundleState != ACTIVE() || bundleInfo.expiredAt < TimestampLib.blockTimestamp()) {
revert ErrorBundleServiceBundleNotOpen(bundleNftId, bundleState, bundleInfo.expiredAt);
}

if (lifetimeExtension.eqz()) {
revert ErrorBundleServiceExtensionLifetimeIsZero();
}

bundleInfo.expiredAt = bundleInfo.expiredAt.addSeconds(lifetimeExtension);
instance.getInstanceStore().updateBundle(bundleNftId, bundleInfo, KEEP_STATE());

emit LogBundleServiceBundleExtended(bundleNftId, lifetimeExtension, bundleInfo.expiredAt);

return bundleInfo.expiredAt;
}


function releaseCollateral(
IInstance instance,
Expand Down
23 changes: 18 additions & 5 deletions contracts/pool/IBundleService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ interface IBundleService is IService {

error ErrorBundleServicePolicyNotCloseable(NftId policyNftId);

// error ErrorBundleServiceBundleNotActive(NftId distributorNftId);
error ErrorBundleServiceFeesWithdrawAmountExceedsLimit(Amount amount, Amount limit);
error ErrorBundleServiceFeesWithdrawAmountIsZero();
error ErrorBundleServiceWalletAllowanceTooSmall(address wallet, address tokenHandler, uint256 allowance, uint256 amount);

error ErrorBundleServiceUnstakeAmountExceedsLimit(Amount amount, Amount limit);

error ErrorBundleServiceExtensionLifetimeIsZero();

event LogBundleServiceFeesWithdrawn(NftId bundleNftId, address recipient, address tokenAddress, Amount amount);
event LogBundleServiceBundleExtended(NftId bundleNftId, Seconds lifetimeExtension, Timestamp extendedExpiredAt);

/// @dev create a new bundle for the specified attributes
/// may only be called by pool service
Expand All @@ -49,10 +53,19 @@ interface IBundleService is IService {


/// @dev increase bundle stakes by the specified amount
/// may only be called by the bundle owner
// function stake(NftId bundleNftId, uint256 amount) external returns(uint256 netAmount);

// function unstake(NftId bundleNftId, uint256 amount) external returns(uint256 netAmount);
/// may only be called by the pool service
function stake(IInstance instance, NftId bundleNftId, Amount amount) external;

/// @dev decrease bundle stakes by the specified amount
/// may only be called by the pool service
/// @param instance the instance relevant for the bundle
/// @param bundleNftId the bundle nft id
/// @param amount the amount to unstake (set to AmountLib.max() to unstake all available stakes)
/// @return unstakedAmount the effective unstaked amount
function unstake(IInstance instance, NftId bundleNftId, Amount amount) external returns (Amount unstakedAmount);

/// @dev extend the lifetime of the bundle by the specified time in seconds
function extend(NftId bundleNftId, Seconds lifetimeExtension) external returns (Timestamp extendedExpiredAt);

/// @dev locks the specified bundle, locked bundles are not available to collateralize new policies
/// only active bundles may be locked
Expand Down
16 changes: 12 additions & 4 deletions contracts/pool/IPoolService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ interface IPoolService is IService {
event LogPoolServiceBundleCreated(NftId instanceNftId, NftId poolNftId, NftId bundleNftId);
event LogPoolServiceBundleClosed(NftId instanceNftId, NftId poolNftId, NftId bundleNftId);

event LogPoolServiceBundleStaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount, Amount netAmount);
event LogPoolServiceBundleUnstaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount);

error ErrorPoolServiceBundleOwnerRoleAlreadySet(NftId poolNftId);
error ErrorPoolServiceInvalidTransferAmount(Amount expectedAmount, Amount actualAmount);
error ErrorPoolServiceBundlePoolMismatch(NftId bundleNftId, NftId poolNftId);
error ErrorPoolServiceMaxCapitalAmountExceeded(NftId poolNftId, Amount maxCapitalAmount, Amount capitalAmount, Amount amountToBeAdded);
error ErrorPoolServiceWalletAllowanceTooSmall(address wallet, address spender, uint256 allowance, uint256 amount);
error ErrorPoolServiceAmountIsZero();

/// @dev defines the required role for bundle owners for the calling pool
/// default implementation returns PUBLIC ROLE
Expand Down Expand Up @@ -85,7 +92,8 @@ interface IPoolService is IService {

/// @dev create a new bundle for the provided parameters
/// staking fees will be deducted by the pool service from the staking amount
/// may only be called by registered and unlocked pool components
/// may only be called by registered and unlocked pool components.
/// The pool balance is equal to the pool fees plus the capital of all bundles.
function createBundle(
address owner, // initial bundle owner
Fee memory fee, // fees deducted from premium that go to bundle owner
Expand All @@ -95,6 +103,7 @@ interface IPoolService is IService {
)
external
returns(NftId bundleNftId); // the nft id of the newly created bundle
// TODO: return netAmount


/// @dev closes the specified bundle
Expand All @@ -111,13 +120,12 @@ interface IPoolService is IService {
/// @dev increase stakes for bundle
/// staking fees will be deducted by the pool service from the staking amount
/// may only be called by registered and unlocked pool components
// function stake(NftId bundleNftId, uint256 amount) external returns(uint256 netAmount);

function stake(NftId bundleNftId, Amount amount) external returns(Amount netAmount);

/// @dev decrease stakes for bundle
/// performance fees will be deducted by the pool service from the staking amount
/// may only be called by registered and unlocked pool components
// function unstake(NftId bundleNftId, uint256 amount) external returns(uint256 netAmount);
function unstake(NftId bundleNftId, Amount amount) external returns(Amount netAmount);


/// @dev calulate required collateral for the provided parameters
Expand Down
11 changes: 7 additions & 4 deletions contracts/pool/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {Fee, FeeLib} from "../type/Fee.sol";
import {NftId, NftIdLib} from "../type/NftId.sol";
import {RoleId, PUBLIC_ROLE} from "../type/RoleId.sol";
import {Seconds} from "../type/Seconds.sol";
import {Timestamp} from "../type/Timestamp.sol";
import {TokenHandler} from "../shared/TokenHandler.sol";
import {UFixed, UFixedLib} from "../type/UFixed.sol";

Expand Down Expand Up @@ -113,7 +114,6 @@ abstract contract Pool is
returns (IComponents.PoolInfo memory poolInfo)
{
return IComponents.PoolInfo(
NftIdLib.zero(), // will be set when GIF registers the related product
PUBLIC_ROLE(), // bundleOwnerRole
AmountLib.max(), // maxCapitalAmount,
isNftInterceptor(), // isInterceptingBundleTransfers
Expand Down Expand Up @@ -172,8 +172,9 @@ abstract contract Pool is
)
internal
virtual
returns(Amount netAmount)
{
// TODO add implementation
_getPoolStorage()._poolService.stake(bundleNftId, amount);
}


Expand All @@ -185,8 +186,9 @@ abstract contract Pool is
)
internal
virtual
returns(Amount netAmount)
{
// TODO add implementation
return _getPoolStorage()._poolService.unstake(bundleNftId, amount);
}


Expand All @@ -198,8 +200,9 @@ abstract contract Pool is
)
internal
virtual
returns (Timestamp extendedExpiredAt)
{
// TODO add implementation
return _getPoolStorage()._bundleService.extend(bundleNftId, lifetimeExtension);
}


Expand Down
Loading
Loading