Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

AP-818 Slippage support for investment calls #398

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
12 changes: 12 additions & 0 deletions contracts/core/accounts/facets/AccountsStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: investRequest.lockAmt,
liqAmt: investRequest.liquidAmt,
lockMinTokensOut: investRequest.lockMinTokensOut,
liqMinTokensOut: investRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down Expand Up @@ -160,6 +162,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: investRequest.lockAmt,
liqAmt: investRequest.liquidAmt,
lockMinTokensOut: investRequest.lockMinTokensOut,
liqMinTokensOut: investRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down Expand Up @@ -260,6 +264,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: redeemRequest.lockAmt,
liqAmt: redeemRequest.liquidAmt,
lockMinTokensOut: redeemRequest.lockMinTokensOut,
liqMinTokensOut: redeemRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down Expand Up @@ -299,6 +305,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: redeemRequest.lockAmt,
liqAmt: redeemRequest.liquidAmt,
lockMinTokensOut: redeemRequest.lockMinTokensOut,
liqMinTokensOut: redeemRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down Expand Up @@ -395,6 +403,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: redeemAllRequest.redeemLocked ? 1 : 0,
liqAmt: redeemAllRequest.redeemLiquid ? 1 : 0,
lockMinTokensOut: redeemAllRequest.lockMinTokensOut,
liqMinTokensOut: redeemAllRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down Expand Up @@ -433,6 +443,8 @@ contract AccountsStrategy is
token: tokenAddress,
lockAmt: redeemAllRequest.redeemLocked ? 1 : 0,
liqAmt: redeemAllRequest.redeemLiquid ? 1 : 0,
lockMinTokensOut: redeemAllRequest.lockMinTokensOut,
liqMinTokensOut: redeemAllRequest.liqMinTokensOut,
status: IVault.VaultActionStatus.UNPROCESSED
});
bytes memory packedPayload = RouterLib.packCallData(payload);
Expand Down
6 changes: 6 additions & 0 deletions contracts/core/accounts/message.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ library AccountMessages {
string token;
uint256 lockAmt;
uint256 liquidAmt;
uint256[] lockMinTokensOut;
uint256[] liqMinTokensOut;
uint256 gasFee;
}

Expand All @@ -117,6 +119,8 @@ library AccountMessages {
string token;
uint256 lockAmt;
uint256 liquidAmt;
uint256[] lockMinTokensOut;
uint256[] liqMinTokensOut;
uint256 gasFee;
}

Expand All @@ -125,6 +129,8 @@ library AccountMessages {
string token;
bool redeemLocked;
bool redeemLiquid;
uint256[] lockMinTokensOut;
uint256[] liqMinTokensOut;
uint256 gasFee;
}

Expand Down
30 changes: 24 additions & 6 deletions contracts/core/router/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,23 @@ contract Router is IRouter, Initializable, AxelarExecutable {
if (action.lockAmt > 0) {
// Send tokens to locked vault and call deposit
IERC20Metadata(action.token).safeTransfer(params.lockedVaultAddr, action.lockAmt);
IVault(params.lockedVaultAddr).deposit(action.accountId, action.token, action.lockAmt);
IVault(params.lockedVaultAddr).deposit(
action.accountId,
action.token,
action.lockAmt,
action.lockMinTokensOut
);
}

if (action.liqAmt > 0) {
// Send tokens to liquid vault and call deposit
IERC20Metadata(action.token).safeTransfer(params.liquidVaultAddr, action.liqAmt);
IVault(params.liquidVaultAddr).deposit(action.accountId, action.token, action.liqAmt);
IVault(params.liquidVaultAddr).deposit(
action.accountId,
action.token,
action.liqAmt,
action.liqMinTokensOut
);
}
}

Expand All @@ -155,7 +165,8 @@ contract Router is IRouter, Initializable, AxelarExecutable {
// Redeem tokens from vaults and then txfer to this contract
IVault.RedemptionResponse memory lockResponse = lockedVault.redeem(
_action.accountId,
_action.lockAmt
_action.lockAmt,
_action.lockMinTokensOut
);
if (lockResponse.amount > 0) {
IERC20Metadata(lockResponse.token).safeTransferFrom(
Expand All @@ -168,7 +179,8 @@ contract Router is IRouter, Initializable, AxelarExecutable {

IVault.RedemptionResponse memory liqResponse = liquidVault.redeem(
_action.accountId,
_action.liqAmt
_action.liqAmt,
_action.liqMinTokensOut
);
if (liqResponse.amount > 0) {
IERC20Metadata(liqResponse.token).safeTransferFrom(
Expand Down Expand Up @@ -209,7 +221,10 @@ contract Router is IRouter, Initializable, AxelarExecutable {
IVault liquidVault = IVault(_params.liquidVaultAddr);

// Redeem tokens from vaults and then txfer to this contract
IVault.RedemptionResponse memory lockResponse = lockedVault.redeemAll(_action.accountId);
IVault.RedemptionResponse memory lockResponse = lockedVault.redeemAll(
_action.accountId,
_action.lockMinTokensOut
);
if (lockResponse.amount > 0) {
IERC20Metadata(_action.token).safeTransferFrom(
_params.lockedVaultAddr,
Expand All @@ -219,7 +234,10 @@ contract Router is IRouter, Initializable, AxelarExecutable {
}
_action.lockAmt = lockResponse.amount;

IVault.RedemptionResponse memory liqResponse = liquidVault.redeemAll(_action.accountId);
IVault.RedemptionResponse memory liqResponse = liquidVault.redeemAll(
_action.accountId,
_action.liqMinTokensOut
);
if (liqResponse.amount > 0) {
IERC20Metadata(_action.token).safeTransferFrom(
_params.liquidVaultAddr,
Expand Down
19 changes: 18 additions & 1 deletion contracts/core/router/RouterLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,23 @@ library RouterLib {
address token,
uint256 lockAmt,
uint256 liqAmt,
uint256[] memory lockMinTokensOut,
uint256[] memory liqMinTokensOut,
IVault.VaultActionStatus status
) = abi.decode(
_calldata,
(string, bytes4, bytes4, uint32, address, uint256, uint256, IVault.VaultActionStatus)
(
string,
bytes4,
bytes4,
uint32,
address,
uint256,
uint256,
uint256[],
uint256[],
IVault.VaultActionStatus
)
);

return
Expand All @@ -36,6 +49,8 @@ library RouterLib {
token,
lockAmt,
liqAmt,
lockMinTokensOut,
liqMinTokensOut,
status
);
}
Expand All @@ -52,6 +67,8 @@ library RouterLib {
_calldata.token,
_calldata.lockAmt,
_calldata.liqAmt,
_calldata.lockMinTokensOut,
_calldata.liqMinTokensOut,
_calldata.status
);
}
Expand Down
16 changes: 12 additions & 4 deletions contracts/core/strategy/APStrategy_V1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,24 @@ abstract contract APStrategy_V1 is IStrategy, Pausable {
/// then sends resulting tokens back to sender.
/// @param amt The qty of tokens that the strategy can transfer from the sender. Implies that the
/// token address is config.fromToken
/// Returns the qty of config.toToken resulting from deposit action
function deposit(uint256 amt) external payable virtual returns (uint256) {}
/// @param minTokensOut intermediate trade slippage tolerances
/// @return yieldTokenAmt the qty of `stratConfig.yieldToken` that were yielded from the deposit action
function deposit(
uint256 amt,
uint256[] memory minTokensOut
) external payable virtual returns (uint256) {}

/// @notice Withdraws tokens from the strategy
/// @dev Transfers the balance of config.toToken erc20 from sender to this contract, makes calls necessary
/// to achieve withdraw with the redemption tokens in hand, then sends resulting tokens back to sender.
/// @param amt The qty of tokens that the strategy can transfer from the sender. Implies that the
/// token address is config.toToken
/// Returns the qty of config.fromToken resulting from withdraw action
function withdraw(uint256 amt) external payable virtual returns (uint256) {}
/// @param minTokensOut intermediate trade slippage tolerances
/// @return baseTokenAmt the qty of `stratConfig.baseToken` that are approved for transfer by msg.sender
function withdraw(
uint256 amt,
uint256[] memory minTokensOut
) external payable virtual returns (uint256) {}

/// @notice Shows the expected conversion between from and to token
/// @dev Implies that the amt refers to the config.fromToken
Expand Down
6 changes: 4 additions & 2 deletions contracts/core/strategy/IStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@ interface IStrategy {
/// 2) Convert the base token into the `stratConfig.yieldToken via integration-specific methods
/// 3) Set the msg.sender as approved() for the returned amt
/// @param amt the qty of `stratConfig.baseToken` that the strategy has been approved to use
/// @param minTokensOut intermediate trade slippage tolerances
/// @return yieldTokenAmt the qty of `stratConfig.yieldToken` that were yielded from the deposit action
function deposit(uint256 amt) external payable returns (uint256);
function deposit(uint256 amt, uint256[] memory minTokensOut) external payable returns (uint256);

/// @notice Accepts `yieldTokens` and converts them back into `baseToken`
/// @dev This method must:
/// 1) Transfer the provided `amt` of `stratConfig.yieldToken` to this contract
/// 2) Convert the yield tokens provided back into the `stratConfig.baseToken via integration-specific methods
/// 3) Set the msg.sender as approved() for the returned amt
/// @param amt the qty of `stratConfig.yieldToken` that this contract has been approved to use by msg.sender
/// @param minTokensOut intermediate trade slippage tolerances
/// @return baseTokenAmt the qty of `stratConfig.baseToken` that are approved for transfer by msg.sender
function withdraw(uint256 amt) external payable returns (uint256);
function withdraw(uint256 amt, uint256[] memory minTokensOut) external payable returns (uint256);

/// @notice Provide an estimate for the current exchange rate for a given deposit
/// @dev This method expects that the `amt` provided is denominated in `baseToken`
Expand Down
26 changes: 16 additions & 10 deletions contracts/core/vault/APVault_V1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
function deposit(
uint32 accountId,
address token,
uint256 amt
uint256 amt,
uint256[] memory minTokensOut
) public payable virtual override onlyApproved notPaused onlybaseToken(token) {
IERC20Metadata(token).safeApprove(vaultConfig.strategy, amt);

uint256 yieldAmt = IStrategy(vaultConfig.strategy).deposit(amt);
uint256 yieldAmt = IStrategy(vaultConfig.strategy).deposit(amt, minTokensOut);

_updatePrincipleDeposit(accountId, amt, yieldAmt);

Expand All @@ -103,12 +104,13 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {

function redeem(
uint32 accountId,
uint256 shares
uint256 shares,
uint256[] memory minTokensOut
) public payable virtual override notPaused onlyApproved returns (RedemptionResponse memory) {
// check against requested shares
if (balanceOf(accountId) <= shares) {
// redeemAll if less
return redeemAll(accountId);
return redeemAll(accountId, minTokensOut);
} else if (shares == 0) {
return
RedemptionResponse({
Expand All @@ -119,7 +121,7 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
} else {
// redeem shares for yieldToken -> approve strategy -> strategy withdraw -> base token
uint256 yieldTokenAmt = super.redeemERC4626(shares, vaultConfig.strategy, accountId);
uint256 returnAmt = IStrategy(vaultConfig.strategy).withdraw(yieldTokenAmt);
uint256 returnAmt = IStrategy(vaultConfig.strategy).withdraw(yieldTokenAmt, minTokensOut);
IVaultEmitter(EMITTER_ADDRESS).redeem(accountId, address(this), shares, returnAmt);

IERC20Metadata(vaultConfig.baseToken).safeTransferFrom(
Expand All @@ -143,7 +145,8 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
}

function redeemAll(
uint32 accountId
uint32 accountId,
uint256[] memory minTokensOut
) public payable virtual override notPaused onlyApproved returns (RedemptionResponse memory) {
uint256 balance = balanceOf(accountId);

Expand All @@ -158,7 +161,7 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
// redeem shares for yieldToken -> approve strategy
uint256 yieldTokenAmt = super.redeemERC4626(balance, vaultConfig.strategy, accountId);
// withdraw all baseToken
uint256 returnAmt = IStrategy(vaultConfig.strategy).withdraw(yieldTokenAmt);
uint256 returnAmt = IStrategy(vaultConfig.strategy).withdraw(yieldTokenAmt, minTokensOut);

IVaultEmitter(EMITTER_ADDRESS).redeem(accountId, address(this), returnAmt, balance);

Expand Down Expand Up @@ -253,7 +256,8 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
);
// Redeem shares to cover fee
uint256 dYieldToken = super.redeemERC4626(taxShares, vaultConfig.strategy, accountId);
uint256 redemption = IStrategy(vaultConfig.strategy).withdraw(dYieldToken);
uint256[] memory minTokensOut;
uint256 redemption = IStrategy(vaultConfig.strategy).withdraw(dYieldToken, minTokensOut);
IVaultEmitter(EMITTER_ADDRESS).redeem(accountId, address(this), taxShares, redemption);
IERC20Metadata(vaultConfig.baseToken).safeTransferFrom(
vaultConfig.strategy,
Expand Down Expand Up @@ -382,7 +386,8 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
vaultConfig.strategy,
accountId
);
uint256 redemption = IStrategy(vaultConfig.strategy).withdraw(dYieldToken);
uint256[] memory minTokensOut;
uint256 redemption = IStrategy(vaultConfig.strategy).withdraw(dYieldToken, minTokensOut);
IVaultEmitter(EMITTER_ADDRESS).redeem(
accountId,
address(this),
Expand Down Expand Up @@ -418,7 +423,8 @@ contract APVault_V1 is IVault, ERC4626AP, Ownable {
IVault(stratParams.liquidVaultAddr).deposit(
accountId,
vaultConfig.baseToken,
(redemption - tax)
(redemption - tax),
minTokensOut
);

return tax;
Expand Down
23 changes: 20 additions & 3 deletions contracts/core/vault/interfaces/IVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ abstract contract IVault {
/// @param token The token (if any) that was forwarded along with the calldata packet by GMP
/// @param lockAmt The amount of said token that is intended to interact with the locked vault
/// @param liqAmt The amount of said token that is intended to interact with the liquid vault
/// @param lockMinTokensOut An array of minimum token amts expected for the locked vault trade path (slippage tolerance)
/// @param liqMinTokensOut An array of minimum token amts expected for the liquid vault trade path (slippage tolerance)
/// @param status Call success status for fallback/callback logic paths
struct VaultActionData {
string destinationChain;
bytes4 strategyId;
Expand All @@ -65,6 +68,8 @@ abstract contract IVault {
address token;
uint256 lockAmt;
uint256 liqAmt;
uint256[] lockMinTokensOut;
uint256[] liqMinTokensOut;
VaultActionStatus status;
}

Expand Down Expand Up @@ -120,25 +125,37 @@ abstract contract IVault {
/// @param accountId a unique Id for each Angel Protocol account
/// @param token the deposited token
/// @param amt the amount of the deposited token
function deposit(uint32 accountId, address token, uint256 amt) external payable virtual;
/// @param minTokensOut the amount of tokens expected along each step of the invest request
function deposit(
uint32 accountId,
address token,
uint256 amt,
uint256[] memory minTokensOut
) external payable virtual;

/// @notice redeem value from the vault contract
/// @dev allows an Account to redeem from its staked value. The behavior is different dependent on VaultType.
/// Before returning the redemption amt, the vault must approve the Router to spend the tokens.
/// @param accountId a unique Id for each Angel Protocol account
/// @param amt the amount of shares to redeem
/// @param minTokensOut the amount of tokens expected along each step of the redemption request
/// @return RedemptionResponse returns the number of base tokens redeemed by the call and the status
function redeem(
uint32 accountId,
uint256 amt
uint256 amt,
uint256[] memory minTokensOut
) external payable virtual returns (RedemptionResponse memory);

/// @notice redeem all of the value from the vault contract
/// @dev allows an Account to redeem all of its staked value. Good for rebasing tokens wherein the value isn't
/// known explicitly. Before returning the redemption amt, the vault must approve the Router to spend the tokens.
/// @param accountId a unique Id for each Angel Protocol account
/// @param minTokensOut the amount of tokens expected along each step of the redemption request
/// @return RedemptionResponse returns the number of base tokens redeemed by the call and the status
function redeemAll(uint32 accountId) external payable virtual returns (RedemptionResponse memory);
function redeemAll(
uint32 accountId,
uint256[] memory minTokensOut
) external payable virtual returns (RedemptionResponse memory);

/// @notice restricted method for harvesting accrued rewards
/// @dev Claim reward tokens accumulated to the staked value. The underlying behavior will vary depending
Expand Down
Loading