Skip to content

Commit

Permalink
Integrate async redeem in router
Browse files Browse the repository at this point in the history
  • Loading branch information
Andreadinenno committed Nov 8, 2024
1 parent 5c70718 commit d9ad9e4
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 43 deletions.
50 changes: 34 additions & 16 deletions src/utils/VaultRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.25;
import {IERC4626, IERC20} from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {ICurveGauge} from "src/interfaces/external/curve/ICurveGauge.sol";
import {IERC7540Redeem} from "ERC-7540/interfaces/IERC7540.sol";

/**
* @title VaultRouter
Expand Down Expand Up @@ -97,7 +98,7 @@ contract VaultRouter {
error ArrayMismatch();
// Vault Receiver Amount
mapping(address => mapping(address => uint256)) public requestShares;
// Asset Receiver Amount
// Vault Receiver Amount
mapping(address => mapping(address => uint256)) public claimableAssets;

function unstakeAndRequestWithdrawal(
Expand All @@ -119,6 +120,7 @@ contract VaultRouter {
uint256 shares
) external {
IERC20(vault).safeTransferFrom(msg.sender, address(this), shares);

_requestWithdrawal(vault, receiver, shares);
}

Expand All @@ -128,6 +130,12 @@ contract VaultRouter {
uint256 shares
) internal {
requestShares[vault][receiver] += shares;

// allow vault to pull shares
IERC20(vault).safeIncreaseAllowance(vault, shares);

// request redeem - send shares to vault
IERC7540Redeem(vault).requestRedeem(shares, receiver, address(this));

emit WithdrawalRequested(
vault,
Expand All @@ -138,15 +146,18 @@ contract VaultRouter {
);
}

function claimWithdrawal(address asset, address receiver) external {
uint256 amount = claimableAssets[asset][receiver];
claimableAssets[asset][receiver] = 0;
// anyone can claim for a receiver
function claimWithdrawal(address vault, address receiver) external {
uint256 amount = claimableAssets[vault][receiver];
claimableAssets[vault][receiver] = 0;

IERC20(asset).safeTransfer(receiver, amount);
// claim asset with receiver shares
IERC4626(vault).withdraw(amount, receiver, receiver);

emit WithdrawalClaimed(asset, receiver, amount);
emit WithdrawalClaimed(vault, receiver, amount);
}

// anyone can fullfil a withdrawal for a receiver
function fullfillWithdrawal(
address vault,
address receiver,
Expand Down Expand Up @@ -180,22 +191,29 @@ contract VaultRouter {
) internal {
requestShares[vault][receiver] -= shares;

uint256 assetAmount = IERC4626(vault).redeem(
shares,
address(this),
address(this)
);
// fulfill redeem of pending shares for receiver
uint256 assetAmount = IERC7540Redeem(vault).fulfillRedeem(shares, receiver);

claimableAssets[address(asset)][receiver] += assetAmount;
// assets are claimable now
claimableAssets[vault][receiver] += assetAmount;

emit WithdrawalFullfilled(vault, address(asset), receiver, shares);
}

function cancelRequest(address vault, uint256 shares) external {
requestShares[vault][msg.sender] -= shares;
error ZeroRequestShares();

// only receiver is able to cancel a request
function cancelRequest(address vault) external {
uint256 sharesCancelled = requestShares[vault][msg.sender];

if(sharesCancelled == 0)
revert ZeroRequestShares();

requestShares[vault][msg.sender] = 0;

IERC20(vault).safeTransfer(msg.sender, shares);
// cancel request and receive pending shares back
IERC7540Redeem(vault).cancelRedeemRequest(msg.sender);

emit WithdrawalCancelled(vault, msg.sender, shares);
emit WithdrawalCancelled(vault, msg.sender, sharesCancelled);
}
}
164 changes: 164 additions & 0 deletions test/mocks/MockERC7540.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.15
pragma solidity ^0.8.15;

import {IERC4626, IERC20, IERC20Metadata} from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {ERC4626Upgradeable, ERC20Upgradeable as ERC20} from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import {SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {Pausable} from "src/utils/Pausable.sol";
import {IERC7540Redeem} from "ERC-7540/interfaces/IERC7540.sol";

contract MockERC7540 is ERC4626Upgradeable, IERC7540Redeem, Pausable {
using SafeERC20 for IERC20;
using Math for uint256;

uint256 public beforeWithdrawHookCalledCounter = 0;
uint256 public afterDepositHookCalledCounter = 0;

uint8 internal _decimals;
uint8 public constant decimalOffset = 0;

/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/

function initialize(
IERC20 _asset,
string memory,
string memory
) external initializer {
__ERC4626_init(IERC20Metadata(address(_asset)));
_decimals = IERC20Metadata(address(_asset)).decimals() + decimalOffset;
}

/*//////////////////////////////////////////////////////////////
GENERAL VIEWS
//////////////////////////////////////////////////////////////*/

function decimals() public view override returns (uint8) {
return _decimals;
}

/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/

function _convertToShares(
uint256 assets,
Math.Rounding rounding
) internal view override returns (uint256 shares) {
return
assets.mulDiv(
totalSupply() + 10 ** decimalOffset,
totalAssets() + 1,
rounding
);
}

function _convertToAssets(
uint256 shares,
Math.Rounding rounding
) internal view override returns (uint256) {
return
shares.mulDiv(
totalAssets() + 1,
totalSupply() + 10 ** decimalOffset,
rounding
);
}

/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/

function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
) internal override {
IERC20(asset()).safeTransferFrom(caller, address(this), assets);
_mint(receiver, shares);

afterDepositHookCalledCounter++;

emit Deposit(caller, receiver, assets, shares);
}

function withdraw(
uint256 assets,
address receiver,
address owner
) public override returns (uint256 shares) {
shares = convertToShares(assets);

_burn(address(this), shares);

IERC20(asset()).safeTransfer(receiver, assets);

emit Withdraw(msg.sender, receiver, owner, assets, shares);
}

/*//////////////////////////////////////////////////////////////
ASYNC WITHDRAW MOCK LOGIC
//////////////////////////////////////////////////////////////*/

mapping (address => uint256) public requestedShares;
mapping (address => uint256) public claimableAssets;

function requestRedeem(
uint256 shares,
address controller,
address owner
) external returns (uint256 requestId) {
requestedShares[controller] += shares;

// Transfer shares from owner to vault (these will be burned on withdrawal)
IERC20(address(this)).safeTransferFrom(owner, address(this), shares);
}

function fulfillRedeem(
uint256 shares,
address controller
) external returns (uint256) {
uint256 assets = convertToAssets(shares);
claimableAssets[controller] = assets;

return assets;
}

function cancelRedeemRequest(address controller) external {
uint256 shares = requestedShares[controller];
requestedShares[controller] = 0;

// Transfer the pending shares back to the receiver
IERC20(address(this)).safeTransfer(controller, shares);
}

function pendingRedeemRequest(uint256 requestId, address controller)
external
view
returns (uint256 pendingShares) {
pendingShares = requestedShares[controller];
}

function claimableRedeemRequest(uint256 requestId, address controller)
external
view
returns (uint256 claimableShares) {
claimableShares = convertToShares(claimableAssets[controller]);
}

/*//////////////////////////////////////////////////////////////
PAUSABLE LOGIC
//////////////////////////////////////////////////////////////*/

function pause() public override {
_pause();
}

function unpause() public override {
_unpause();
}
}
Loading

0 comments on commit d9ad9e4

Please sign in to comment.