Skip to content

Commit

Permalink
feat: transfer shares
Browse files Browse the repository at this point in the history
  • Loading branch information
Schlagonia committed Apr 23, 2024
1 parent afff1f9 commit b0e1515
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 44 deletions.
75 changes: 48 additions & 27 deletions src/L1YearnEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,21 @@ contract L1YearnEscrow is L1Escrow {
function _receiveTokens(
uint256 amount
) internal virtual override whenNotPaused {
super._receiveTokens(amount);
originTokenAddress().safeTransferFrom(
msg.sender,
address(this),
amount
);

VaultStorage storage $ = _getVaultStorage();
uint256 _minimumBuffer = $.minimumBuffer;
// Deposit to the vault if above buffer
if (_minimumBuffer != 0) {
uint256 underlyingBalance = originTokenAddress().balanceOf(
address(this)
);
if (underlyingBalance <= _minimumBuffer) {
return;
}

if (underlyingBalance <= _minimumBuffer) return;

unchecked {
amount = underlyingBalance - _minimumBuffer;
Expand All @@ -135,37 +139,50 @@ contract L1YearnEscrow is L1Escrow {
}

/**
* @dev Handle the transfer of the tokens
* @dev Handle the transfer of the tokens. Will send shares instead of
* the underlying asset if the vault is illiquid.
* @param destinationAddress Address destination that will receive the tokens on the other network
* @param amount Token amount
*/
function _transferTokens(
address destinationAddress,
uint256 amount
) internal virtual override whenNotPaused {
VaultStorage storage $ = _getVaultStorage();

// Check if there is enough loose balance.
uint256 underlyingBalance = originTokenAddress().balanceOf(
address(this)
);
if (underlyingBalance != 0) {
if (underlyingBalance >= amount) {
super._transferTokens(destinationAddress, amount);
return;
}
IERC20 originToken = originTokenAddress();

// Check if there is enough buffer.
uint256 underlyingBalance = originToken.balanceOf(address(this));
if (underlyingBalance >= amount) {
// Only use buffer if it covers the full amount.
originToken.safeTransfer(destinationAddress, amount);
return;
}

uint256 maxWithdraw = $.vaultAddress.maxWithdraw(address(this));
if (maxWithdraw < amount) {
super._transferTokens(destinationAddress, underlyingBalance);
// Check if the vault will allow for a full withdraw.
IVault _vault = _getVaultStorage().vaultAddress;
uint256 maxWithdraw = _vault.maxWithdraw(address(this));
// If liquidity will not allow for a full withdraw.
if (amount > maxWithdraw) {
// First use any loose balance.
if (underlyingBalance != 0) {
originToken.safeTransfer(destinationAddress, underlyingBalance);
unchecked {
amount = amount - underlyingBalance;
}
}

// Check again to account for if there was underlying
if (amount > maxWithdraw) {
// Send an equivalent amount of shares for the difference.
uint256 shares = _vault.convertToShares(amount - maxWithdraw);
_vault.transfer(destinationAddress, shares);
if (maxWithdraw == 0) return;
amount = maxWithdraw;
}
}

// Withdraw from vault to receiver.
$.vaultAddress.withdraw(amount, destinationAddress, address(this));
_vault.withdraw(amount, destinationAddress, address(this));
}

// ****************************
Expand All @@ -175,15 +192,17 @@ contract L1YearnEscrow is L1Escrow {
/**
* @dev Escrow manager can withdraw the token backing
* @param _recipient the recipient address
* @param _amount The amount of token
* @param _amount The amount of token in underlying
*/
function withdraw(
address _recipient,
uint256 _amount
) external virtual override onlyRole(ESCROW_MANAGER_ROLE) whenNotPaused {
VaultStorage storage $ = _getVaultStorage();
uint256 shares = $.vaultAddress.convertToShares(_amount);
$.vaultAddress.transfer(_recipient, shares);
IVault _vault = _getVaultStorage().vaultAddress;
// Transfer the equivalent amount of vault shares
uint256 shares = _vault.convertToShares(_amount);
_vault.transfer(_recipient, shares);

emit Withdraw(_recipient, _amount);
}

Expand All @@ -201,10 +220,12 @@ contract L1YearnEscrow is L1Escrow {
) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
VaultStorage storage $ = _getVaultStorage();
IVault oldVault = $.vaultAddress;
IERC20 originToken = originTokenAddress();

// If re-initializing to a new vault address.
if (address(oldVault) != address(0)) {
// Lower allowance to 0
originTokenAddress().forceApprove(address(oldVault), 0);
originToken.forceApprove(address(oldVault), 0);

uint256 balance = oldVault.balanceOf(address(this));
// Withdraw the full balance of the current vault.
Expand All @@ -216,10 +237,10 @@ contract L1YearnEscrow is L1Escrow {
// Migrate to new vault if applicable
if (_vaultAddress != address(0)) {
// Max approve the new vault
originTokenAddress().forceApprove(_vaultAddress, 2 ** 256 - 1);
originToken.forceApprove(_vaultAddress, 2 ** 256 - 1);

// Deposit any loose funds
uint256 balance = originTokenAddress().balanceOf(address(this));
uint256 balance = originToken.balanceOf(address(this));
if (balance != 0)
IVault(_vaultAddress).deposit(balance, address(this));
}
Expand Down
85 changes: 85 additions & 0 deletions test/L1Escrow.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,91 @@ contract EscrowTest is Setup {
assertEq(vault.balanceOf(address(mockEscrow)), 0);
}

function test_illiquidWithdraw(uint256 _amount) public {
_amount = bound(_amount, minFuzzAmount, maxFuzzAmount);

mockEscrow = deployMockL1Escrow();

// Simulate a bridge txn
mintAndBridge(mockEscrow, user, _amount);

assertEq(vault.totalAssets(), _amount);
assertEq(asset.balanceOf(user), 0);
assertEq(asset.balanceOf(address(mockEscrow)), 0);
assertEq(vault.balanceOf(address(mockEscrow)), _amount);

// send funds to a strategy
uint256 toLock = _amount / 2;
addStrategyAndDebt(vault, setUpStrategy(), toLock);
// And remove from queue
address[] memory queue = new address[](0);
vm.prank(governator);
vault.set_default_queue(queue);

assertEq(vault.maxWithdraw(address(mockEscrow)), _amount - toLock);

// Withdraw everything
bytes memory data = abi.encode(user, _amount);
vm.prank(address(polygonZkEVMBridge));
mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data);

// Should have sent the liquid balance and the rest in shares
assertEq(vault.totalAssets(), toLock);
assertEq(asset.balanceOf(user), _amount - toLock);
assertEq(vault.balanceOf(user), toLock);
assertEq(asset.balanceOf(address(mockEscrow)), 0);
assertEq(vault.balanceOf(address(mockEscrow)), 0);
}

function test_illiquidWithdraw_withBuffer(
uint256 _amount,
uint256 _minimumBuffer
) public {
_amount = bound(_amount, minFuzzAmount, maxFuzzAmount);
_minimumBuffer = bound(_minimumBuffer, 10, _amount / 2);

mockEscrow = deployMockL1Escrow();

vm.prank(governator);
mockEscrow.updateMinimumBuffer(_minimumBuffer);

// Simulate a bridge txn
mintAndBridge(mockEscrow, user, _amount);

assertEq(vault.totalAssets(), _amount - _minimumBuffer);
assertEq(asset.balanceOf(user), 0);
assertEq(asset.balanceOf(address(mockEscrow)), _minimumBuffer);
assertEq(
vault.balanceOf(address(mockEscrow)),
_amount - _minimumBuffer
);

// send funds to a strategy
uint256 toLock = _amount / 2;
addStrategyAndDebt(vault, setUpStrategy(), toLock);
// And remove from queue
address[] memory queue = new address[](0);
vm.prank(governator);
vault.set_default_queue(queue);

assertEq(
vault.maxWithdraw(address(mockEscrow)),
_amount - _minimumBuffer - toLock
);

// Withdraw everything
bytes memory data = abi.encode(user, _amount);
vm.prank(address(polygonZkEVMBridge));
mockEscrow.onMessageReceived(address(l2EscrowImpl), l2RollupID, data);

// Should have sent the liquid balance and the rest in shares
assertEq(vault.totalAssets(), toLock);
assertEq(asset.balanceOf(user), _amount - toLock);
assertEq(vault.balanceOf(user), toLock);
assertEq(asset.balanceOf(address(mockEscrow)), 0);
assertEq(vault.balanceOf(address(mockEscrow)), 0);
}

event BridgeEvent(
uint8 leafType,
uint32 originNetwork,
Expand Down
8 changes: 0 additions & 8 deletions test/mocks/MockStrategy.sol

This file was deleted.

10 changes: 3 additions & 7 deletions test/mocks/MockTokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import {BaseStrategy, ERC20} from "./BaseStrategy.sol";

contract MockTokenizedStrategy is BaseStrategy {
constructor(
address _asset,
string memory _name
) BaseStrategy(_asset, _name) {}
address _asset
) BaseStrategy(_asset, "Mock Tokenized Strategy") {}

function _deployFunds(uint256 _amount) internal virtual override {}

Expand All @@ -22,10 +21,7 @@ contract MockTokenized is MockTokenizedStrategy {
uint256 public loss;
uint256 public limit;

constructor(
address _asset,
string memory _name
) MockTokenizedStrategy(_asset, _name) {}
constructor(address _asset) MockTokenizedStrategy(_asset) {}

function realizeLoss(uint256 _amount) external {
asset.transfer(msg.sender, _amount);
Expand Down
4 changes: 2 additions & 2 deletions test/utils/Setup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {L2Escrow} from "@zkevm-stb/L2Escrow.sol";
import {L2Token} from "@zkevm-stb/L2Token.sol";
import {L2TokenConverter} from "@zkevm-stb/L2TokenConverter.sol";

import {MockStrategy} from "../mocks/MockStrategy.sol";
import {MockTokenizedStrategy} from "../mocks/MockTokenizedStrategy.sol";

contract Setup is ExtendedTest {
using SafeERC20 for ERC20;
Expand Down Expand Up @@ -234,7 +234,7 @@ contract Setup is ExtendedTest {
function setUpStrategy() public returns (IStrategy) {
// we save the strategy as a IStrategyInterface to give it the needed interface
IStrategy _strategy = IStrategy(
address(new MockStrategy(address(asset)))
address(new MockTokenizedStrategy(address(asset)))
);

// set keeper
Expand Down

0 comments on commit b0e1515

Please sign in to comment.