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

VAL-104 Run crank lazily #104

Merged
merged 9 commits into from
Nov 29, 2022
Merged
Changes from all commits
Commits
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
53 changes: 45 additions & 8 deletions contracts/Pool.sol
Original file line number Diff line number Diff line change
@@ -83,6 +83,14 @@ contract Pool is IPool, ERC20 {
_;
}

/**
* @dev Modifier to ensure the Pool is cranked.
*/
modifier onlyCrankedPool() {
_crank();
_;
}

/**
* @dev Constructor for Pool
* @param liquidityAsset asset held by the poo
@@ -206,7 +214,11 @@ contract Pool is IPool, ERC20 {
/**
* @inheritdoc IPool
*/
function fundLoan(address addr) external onlyPoolController {
function fundLoan(address addr)
external
onlyPoolController
onlyCrankedPool
{
ILoan loan = ILoan(addr);
uint256 principal = loan.principal();

@@ -224,7 +236,11 @@ contract Pool is IPool, ERC20 {
/**
* @inheritdoc IPool
*/
function removeFundedLoan(address addr) external onlyPoolController {
function removeFundedLoan(address addr)
external
onlyPoolController
onlyCrankedPool
{
require(_fundedLoans.remove(addr), "Pool: unfunded loan");
_accountings.outstandingLoanPrincipals -= ILoan(addr).principal();
}
@@ -280,7 +296,7 @@ contract Pool is IPool, ERC20 {
address recipient,
uint256 fixedFee,
uint256 fixedFeeInterval
) external onlyPoolController {
) external onlyPoolController onlyCrankedPool {
require(
_accountings.fixedFeeDueDate < block.timestamp,
"Pool: fixed fee not due"
@@ -361,6 +377,7 @@ contract Pool is IPool, ERC20 {
external
onlyActivatedPool
onlyLender
onlyCrankedPool
returns (uint256 assets)
{
assets = convertToAssets(shares);
@@ -374,6 +391,7 @@ contract Pool is IPool, ERC20 {
external
onlyActivatedPool
onlyLender
onlyCrankedPool
returns (uint256 shares)
{
shares = convertToShares(assets);
@@ -426,6 +444,7 @@ contract Pool is IPool, ERC20 {
external
onlyActivatedPool
onlyLender
onlyCrankedPool
returns (uint256 assets)
{
assets = convertToAssets(shares);
@@ -443,6 +462,7 @@ contract Pool is IPool, ERC20 {
external
onlyActivatedPool
onlyLender
onlyCrankedPool
returns (uint256 shares)
{
shares = convertToShares(assets);
@@ -473,10 +493,25 @@ contract Pool is IPool, ERC20 {
//////////////////////////////////////////////////////////////*/

/**
* @dev Crank the protocol. Issues withdrawals
* @inheritdoc IPool
*/
function crank() external returns (uint256 redeemableShares) {
redeemableShares = withdrawController.crank();
function crank() public virtual {
_crank();
}

/**
* @dev Internal crank function run lazily.
*/
function _crank() internal {
(
uint256 period,
uint256 redeemableShares,
uint256 withdrawableAssets,
bool periodCranked
) = withdrawController.crank(poolController.withdrawGate());
if (periodCranked) {
emit PoolCranked(period, redeemableShares, withdrawableAssets);
}
}

/*//////////////////////////////////////////////////////////////
@@ -587,6 +622,7 @@ contract Pool is IPool, ERC20 {
virtual
override
atState(IPoolLifeCycleState.Active)
onlyCrankedPool
returns (uint256 shares)
{
shares = PoolLib.executeDeposit(
@@ -642,6 +678,7 @@ contract Pool is IPool, ERC20 {
virtual
override
atState(IPoolLifeCycleState.Active)
onlyCrankedPool
returns (uint256 assets)
{
assets = previewMint(shares);
@@ -690,7 +727,7 @@ contract Pool is IPool, ERC20 {
uint256 assets,
address receiver,
address owner
) external virtual returns (uint256 shares) {
) external virtual onlyCrankedPool returns (uint256 shares) {
require(receiver == owner, "Pool: Withdrawal to unrelated address");
require(receiver == msg.sender, "Pool: Must transfer to msg.sender");
require(assets > 0, "Pool: 0 withdraw not allowed");
@@ -738,7 +775,7 @@ contract Pool is IPool, ERC20 {
uint256 shares,
address receiver,
address owner
) external virtual returns (uint256 assets) {
) external virtual onlyCrankedPool returns (uint256 assets) {
require(receiver == owner, "Pool: Withdrawal to unrelated address");
require(receiver == msg.sender, "Pool: Must transfer to msg.sender");
require(shares > 0, "Pool: 0 redeem not allowed");
8 changes: 8 additions & 0 deletions contracts/controllers/PoolController.sol
Original file line number Diff line number Diff line change
@@ -357,4 +357,12 @@ contract PoolController is IPoolController {
_settings.fixedFeeInterval
);
}

/*//////////////////////////////////////////////////////////////
Crank
//////////////////////////////////////////////////////////////*/

function crank() external override onlyAdmin {
pool.crank();
}
}
62 changes: 30 additions & 32 deletions contracts/controllers/WithdrawController.sol
Original file line number Diff line number Diff line change
@@ -347,48 +347,47 @@ contract WithdrawController is IWithdrawController {
/**
* @inheritdoc IWithdrawController
*/
function crank() external onlyPool returns (uint256 redeemableShares) {
uint256 currentPeriod = withdrawPeriod();
function crank(uint256 withdrawGate)
external
onlyPool
returns (
uint256 period,
uint256 redeemableShares,
uint256 withdrawableAssets,
bool periodCranked
)
{
period = withdrawPeriod();
IPoolWithdrawState memory globalState = _currentGlobalWithdrawState();
if (globalState.latestCrankPeriod == currentPeriod) {
return 0;
if (globalState.latestCrankPeriod == period) {
return (period, 0, 0, false);
}

// Calculate the amount available for withdrawal
uint256 liquidAssets = _pool.liquidityPoolAssets();
IPoolController _poolController = IPoolController(
_pool.poolController()
);

uint256 availableAssets = liquidAssets
.mul(_poolController.withdrawGate())
.mul(withdrawGate)
.mul(PoolLib.RAY)
.div(10_000)
.div(PoolLib.RAY);

uint256 availableShares = _pool.convertToShares(availableAssets);

if (
availableAssets <= 0 ||
availableShares <= 0 ||
globalState.eligibleShares <= 0
) {
// unable to redeem anything
redeemableShares = 0;
return 0;
}

// Determine the amount of shares that we will actually distribute.
redeemableShares = Math.min(
availableShares,
globalState.eligibleShares - 1 // We offset by 1 to avoid a 100% redeem rate, which throws off all the math.
globalState.eligibleShares > 0 ? globalState.eligibleShares - 1 : 0 // We offset by 1 to avoid a 100% redeem rate, which throws off all the math.
);

if (redeemableShares == 0) {
return 0;
// unable to redeem anything, so the snapshot is unchanged from the last
_globalWithdrawState.latestCrankPeriod = period;
_snapshots[period] = _snapshots[globalState.latestCrankPeriod];
return (period, 0, 0, true);
}

uint256 withdrawableAssets = _pool.convertToAssets(redeemableShares);
periodCranked = true;
withdrawableAssets = _pool.convertToAssets(redeemableShares);

// Calculate the redeemable rate for each lender
uint256 redeemableRateRay = redeemableShares.mul(PoolLib.RAY).div(
@@ -411,18 +410,18 @@ contract WithdrawController is IWithdrawController {
? lastSnapshot.aggregationDifferenceRay
: PoolLib.RAY;

// Cache new accumulating term to avoid duplicating the math
uint256 newAccumulatedTerm = redeemableRateRay.mul(lastDiff).div(
PoolLib.RAY
);

// Compute the new snapshotted values
_snapshots[currentPeriod] = IPoolSnapshotState(
_snapshots[period] = IPoolSnapshotState(
// New aggregation
lastSnapshot.aggregationSumRay +
redeemableRateRay.mul(lastDiff).div(PoolLib.RAY),
lastSnapshot.aggregationSumRay + newAccumulatedTerm,
// New aggregation w/ FX
lastSnapshot.aggregationSumFxRay +
redeemableRateRay
.mul(lastDiff)
.div(PoolLib.RAY)
.mul(fxExchangeRate)
.div(PoolLib.RAY),
newAccumulatedTerm.mul(fxExchangeRate).div(PoolLib.RAY),
// New difference
lastDiff.mul(PoolLib.RAY - redeemableRateRay).div(PoolLib.RAY)
);
@@ -433,7 +432,7 @@ contract WithdrawController is IWithdrawController {
withdrawableAssets,
redeemableShares
);
globalState.latestCrankPeriod = currentPeriod;
globalState.latestCrankPeriod = period;
_globalWithdrawState = globalState;
}

@@ -446,7 +445,6 @@ contract WithdrawController is IWithdrawController {
view
returns (IPoolWithdrawState memory)
{
uint256 currentPeriod = withdrawPeriod();
uint256 lastPoolCrank = _globalWithdrawState.latestCrankPeriod;

// Current snaphot
9 changes: 9 additions & 0 deletions contracts/controllers/interfaces/IPoolController.sol
Original file line number Diff line number Diff line change
@@ -174,4 +174,13 @@ interface IPoolController {
* claimed once every interval, as set on the pool.
*/
function claimFixedFee() external;

/*//////////////////////////////////////////////////////////////
Crank
//////////////////////////////////////////////////////////////*/

/**
* @dev Cranks the Pool.
*/
function crank() external;
}
9 changes: 8 additions & 1 deletion contracts/controllers/interfaces/IWithdrawController.sol
Original file line number Diff line number Diff line change
@@ -175,7 +175,14 @@ interface IWithdrawController {
/**
* @dev Crank the protocol. Performs accounting for withdrawals
*/
function crank() external returns (uint256);
function crank(uint256 withdrawGate)
external
returns (
uint256 period,
uint256 shares,
uint256 assets,
bool periodCranked
);

/*//////////////////////////////////////////////////////////////
Withdraw / Redeem
11 changes: 10 additions & 1 deletion contracts/interfaces/IPool.sol
Original file line number Diff line number Diff line change
@@ -52,6 +52,15 @@ interface IPool is IERC4626 {
uint256 shares
);

/**
* @dev Emitted when the pool is cranked for a given withdraw period.
*/
event PoolCranked(
uint256 withDrawPeriod,
uint256 redeemableShares,
uint256 withdrawableAssets
);

/**
* @dev The PoolController contract
*/
@@ -129,7 +138,7 @@ interface IPool is IERC4626 {
/**
* @dev Cranks the pool's withdrawals
*/
function crank() external returns (uint256);
function crank() external;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can add this back in, but since we're emitting an event with more information, felt duplicative?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value doesn't seem very meaningful anyway. I can't think of an on-chain use case off the top of my head.


/**
* @dev Determines how many funded loans exist
12 changes: 12 additions & 0 deletions contracts/permissioned/PermissionedPool.sol
Original file line number Diff line number Diff line change
@@ -71,6 +71,18 @@ contract PermissionedPool is Pool {
);
}

/**
* @inheritdoc Pool
*/
function crank() public override {
require(
msg.sender == address(poolController) ||
poolAccessControl.isValidParticipant(msg.sender),
"Pool: not allowed"
);
super.crank();
}

/**
* @inheritdoc Pool
*/
Loading