Skip to content

Commit

Permalink
feat: add ERC712 permit to functions with transferFrom
Browse files Browse the repository at this point in the history
  • Loading branch information
cucupac committed Jan 26, 2024
1 parent e4458de commit eeaaefd
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 8 deletions.
31 changes: 31 additions & 0 deletions src/Position.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DebtService } from "src/services/DebtService.sol";
import { SwapService } from "src/services/SwapService.sol";
import { SafeTransferLib, ERC20 } from "solmate/utils/SafeTransferLib.sol";
import { IERC20 } from "src/interfaces/token/IERC20.sol";
import { IERC20Permit } from "src/interfaces/token/IERC20Permit.sol";

Check warning on line 9 in src/Position.sol

View workflow job for this annotation

GitHub Actions / run-ci

imported name IERC20Permit is not used
import { IFeeCollector } from "src/interfaces/IFeeCollector.sol";

/// @title Position
Expand Down Expand Up @@ -62,6 +63,36 @@ contract Position is DebtService, SwapService {
emit Short(cAmtNet, dAmt, bAmt);
}

/**
* @notice Adds to this contract's short position with permit, obviating the need for a separate approve tx.
* @param _cAmt The amount of collateral to be supplied for this transaction-specific loan (units: C_DECIMALS).
* @param _ltv The desired loan-to-value ratio for this transaction-specific loan (ex: 75 is 75%).
* @param _swapAmtOutMin The minimum amount of output tokens from swap for the tx to go through.
* @param _poolFee The fee of the Uniswap pool.
* @param _client The address, controlled by client operators, for receiving protocol fees (use address(0) if no client).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function shortWithPermit(
uint256 _cAmt,
uint256 _ltv,
uint256 _swapAmtOutMin,
uint24 _poolFee,
address _client,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) public payable onlyOwner {
// 1. Approve with permit
IERC20WithPermit(C_TOKEN).permit(msg.sender, address(this), _cAmt, _deadline, _v, _r, _s);

// 2. Short
short(_cAmt, _ltv, _swapAmtOutMin, _poolFee, _client);
}

/**
* @notice Fully closes the short position.
* @param _poolFee The fee of the Uniswap pool.
Expand Down
56 changes: 56 additions & 0 deletions src/interfaces/IPosition.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ interface IPosition {
external
payable;

/**
* @notice Adds to this contract's short position with permit, obviating the need for a separate approve tx.
* @param _cAmt The amount of collateral to be supplied for this transaction-specific loan (units: C_DECIMALS).
* @param _ltv The desired loan-to-value ratio for this transaction-specific loan (ex: 75 is 75%).
* @param _swapAmtOutMin The minimum amount of output tokens from swap for the tx to go through.
* @param _poolFee The fee of the Uniswap pool.
* @param _client The address, controlled by client operators, for receiving protocol fees (use address(0) if no client).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function shortWithPermit(
uint256 _cAmt,
uint256 _ltv,
uint256 _swapAmtOutMin,
uint24 _poolFee,
address _client,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external payable;

/**
* @notice Fully closes the short position.
* @param _poolFee The fee of the Uniswap pool.
Expand All @@ -59,6 +83,18 @@ interface IPosition {
*/
function addCollateral(uint256 _cAmt) external payable;

/**
* @notice Increases the collateral amount for this contract's loan with permit, obviating the need for a separate approve tx.
* @param _cAmt The amount of collateral to be supplied (units: C_DECIMALS).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function addCollateralWithPermit(uint256 _cAmt, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
external
payable;

/**
* @notice Repays any outstanding debt to Aave and transfers remaining collateral from Aave to owner.
* @param _dAmt The amount of debt token to repay to Aave (units: D_DECIMALS).
Expand All @@ -67,6 +103,26 @@ interface IPosition {
*/
function repayAfterClose(uint256 _dAmt, uint256 _withdrawBuffer) external payable;

/**
* @notice Repays any outstanding debt to Aave and transfers remaining collateral from Aave to owner,
* with permit, obviating the need for a separate approve tx.
* @param _dAmt The amount of debt token to repay to Aave (units: D_DECIMALS).
* To pay off entire debt, _dAmt = debtOwed + smallBuffer (to account for interest).
* @param _withdrawBuffer The amount of collateral left as safety buffer for tx to go through (default = 100_000, units: 8 decimals).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function repayAfterCloseWithPermit(
uint256 _dAmt,
uint256 _withdrawBuffer,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external payable;

/* ****************************************************************************
**
** ADMIN FUNCTIONS
Expand Down
16 changes: 8 additions & 8 deletions src/interfaces/aave/IPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,12 @@ interface IPool {
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param deadline The deadline timestamp that the permit is valid
* @param deadline The deadline timestamp that the permit is valid.
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
* @param permitV The V parameter of ERC712 permit signature.
* @param permitR The R parameter of ERC712 permit signature.
* @param permitS The S parameter of ERC712 permit signature.
*
*/
function supplyWithPermit(
Expand Down Expand Up @@ -333,10 +333,10 @@ interface IPool {
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
* @param deadline The deadline timestamp that the permit is valid
* @param permitV The V parameter of ERC712 permit sig
* @param permitR The R parameter of ERC712 permit sig
* @param permitS The S parameter of ERC712 permit sig
* @param deadline The deadline timestamp that the permit is valid.
* @param permitV The V parameter of ERC712 permit signature.
* @param permitR The R parameter of ERC712 permit signature.
* @param permitS The S parameter of ERC712 permit signature.
* @return The final amount repaid
*
*/
Expand Down
83 changes: 83 additions & 0 deletions src/interfaces/token/IERC20Permit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.21;

/**
* @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
external;

/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);

/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
47 changes: 47 additions & 0 deletions src/services/DebtService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PositionAdmin } from "src/PositionAdmin.sol";
import { SafeTransferLib, ERC20 } from "solmate/utils/SafeTransferLib.sol";
import { IPool } from "src/interfaces/aave/IPool.sol";
import { IERC20 } from "src/interfaces/token/IERC20.sol";
import { IERC20 } from "src/interfaces/token/IERC20.sol";
import { IAaveOracle } from "src/interfaces/aave/IAaveOracle.sol";
import { IERC20Metadata } from "src/interfaces/token/IERC20Metadata.sol";

Expand Down Expand Up @@ -125,6 +126,26 @@ contract DebtService is PositionAdmin {
IPool(AAVE_POOL).supply(C_TOKEN, _cAmt, address(this), 0);
}

/**
* @notice Increases the collateral amount for this contract's loan with permit, obviating the need for a separate approve tx.
* @param _cAmt The amount of collateral to be supplied (units: C_DECIMALS).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function addCollateralWithPermit(uint256 _cAmt, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s)
public
payable
onlyOwner
{
// 1. Approve with permit
IERC20WithPermit(C_TOKEN).permit(msg.sender, address(this), _cAmt, _deadline, _v, _r, _s);

// 2. Add Collateral
addCollateral(_cAmt);
}

/**
* @notice Repays any outstanding debt to Aave and transfers remaining collateral from Aave to owner.
* @param _dAmt The amount of debt token to repay to Aave (units: D_DECIMALS).
Expand All @@ -138,4 +159,30 @@ contract DebtService is PositionAdmin {

_withdraw(OWNER, _withdrawBuffer);
}

/**
* @notice Repays any outstanding debt to Aave and transfers remaining collateral from Aave to owner,
* with permit, obviating the need for a separate approve tx.
* @param _dAmt The amount of debt token to repay to Aave (units: D_DECIMALS).
* To pay off entire debt, _dAmt = debtOwed + smallBuffer (to account for interest).
* @param _withdrawBuffer The amount of collateral left as safety buffer for tx to go through (default = 100_000, units: 8 decimals).
* @param _deadline The deadline timestamp that the permit is valid.
* @param _v The V parameter of ERC712 permit signature.
* @param _r The R parameter of ERC712 permit signature.
* @param _s The S parameter of ERC712 permit signature.
*/
function repayAfterCloseWithPermit(
uint256 _dAmt,
uint256 _withdrawBuffer,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) public payable onlyOwner {
// 1. Approve with permit
IERC20WithPermit(D_TOKEN).permit(msg.sender, address(this), _dAmt, _deadline, _v, _r, _s);

// 2. Repay
repayAfterClose(_dAmt, _withdrawBuffer);
}
}

0 comments on commit eeaaefd

Please sign in to comment.