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

DRAFT: metaDeposit and metaRedeem #31

Closed
wants to merge 2 commits into from
Closed
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
113 changes: 84 additions & 29 deletions src/contracts/StakeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {IAccessControl} from 'aave-v3-origin/core/contracts/dependencies/openzep

import {PercentageMath} from './lib/PercentageMath.sol';

import {ERC20PermitUpgradeable} from './ERC20PermitUpgradeable.sol';
import {ERC20PermitUpgradeable, ECDSA} from './ERC20PermitUpgradeable.sol';
import {ERC20Upgradeable} from './ERC20Upgradeable.sol';
import {IStakeToken} from './interfaces/IStakeToken.sol';
import {IRewardsController} from './interfaces/IRewardsController.sol';
Expand All @@ -28,7 +28,20 @@ contract StakeToken is ERC20PermitUpgradeable, IStakeToken, Rescuable {
using SafeERC20 for IERC20;
using PercentageMath for uint256;
using SafeCast for uint256;
using SafeCast for uint104;

/// @dev type-hashes for meta transactions

/// @inheritdoc IStakeToken
bytes32 public constant METADEPOSIT_TYPEHASH =
keccak256(
'Deposit(address owner,address receiver,uint256 assets,uint256 nonce,uint256 deadline)'
);

/// @inheritdoc IStakeToken
bytes32 public constant METAREDEEM_TYPEHASH =
keccak256(
'Redeem(address owner,address receiver,uint256 shares,uint256 nonce,uint256 deadline)'
);

/// @dev the exchange rate on stake-token "up only" and reflects hom many stk token you receive for the underlying
uint216 public constant INITIAL_EXCHANGE_RATE = 1e18;
Expand Down Expand Up @@ -235,6 +248,75 @@ contract StakeToken is ERC20PermitUpgradeable, IStakeToken, Rescuable {
return _stake(msg.sender, receiver, assets, true);
}

/// @inheritdoc IStakeToken
function metaRedeem(
address owner,
address receiver,
uint256 shares,
uint256 deadline,
SignatureParams calldata sigParams
) external returns (uint256) {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}

bytes32 structHash = keccak256(
abi.encode(METAREDEEM_TYPEHASH, owner, receiver, shares, _useNonce(owner), deadline)
);

bytes32 hash = _hashTypedDataV4(structHash);

address signer = ECDSA.recover(hash, sigParams.v, sigParams.r, sigParams.s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}

return _redeem(owner, receiver, shares.toUint104());
}

/// @inheritdoc IStakeToken
function metaDeposit(
address owner,
address receiver,
uint256 assets,
uint256 deadline,
PermitParams calldata permit,
SignatureParams calldata sigParams
) external returns (uint256) {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}

bytes32 structHash = keccak256(
abi.encode(METADEPOSIT_TYPEHASH, owner, receiver, assets, _useNonce(owner), deadline)
);

bytes32 hash = _hashTypedDataV4(structHash);

address signer = ECDSA.recover(hash, sigParams.v, sigParams.r, sigParams.s);
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}

StakeTokenStorage storage $ = _getStakeTokenStorage();
// assume if deadline 0 no permit was supplied
if (permit.deadline != 0) {
try
IERC20Permit($._smConfig.stakedToken).permit(
owner,
address(this),
permit.value,
permit.deadline,
permit.v,
permit.r,
permit.s
)
{} catch {}
}

return _stake(owner, receiver, assets, true);
}

///@inheritdoc IERC4626
function mint(
uint256 shares,
Expand All @@ -245,33 +327,6 @@ contract StakeToken is ERC20PermitUpgradeable, IStakeToken, Rescuable {
return assets;
}

/// @inheritdoc IStakeToken
function stakeWithPermit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external whenNotPaused {
StakeTokenStorage storage $ = _getStakeTokenStorage();
try
IERC20Permit($._smConfig.stakedToken).permit(
msg.sender,
address(this),
amount,
deadline,
v,
r,
s
)
{
// do nothing
} catch (bytes memory) {
// do nothing
}
_stake(msg.sender, msg.sender, amount, true);
}

/// @inheritdoc IStakeToken
function cooldown() external whenNotPaused {
_cooldown(msg.sender);
Expand Down
72 changes: 56 additions & 16 deletions src/contracts/interfaces/IStakeToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ interface IStakeToken is IERC4626 {
// reserved for future use
}

/// @notice v,r,s components of a signature
struct SignatureParams {
uint8 v;
bytes32 r;
bytes32 s;
}

struct PermitParams {
/// @notice value to approve
uint256 value;
/// @notice signature expiration time
uint256 deadline;
/// @notice v,r,s components of a signature
uint8 v;
bytes32 r;
bytes32 s;
}

event Cooldown(address indexed user, uint256 amount);

event MaxSlashablePercentageChanged(uint256 newPercentage);
Expand All @@ -35,6 +53,44 @@ interface IStakeToken is IERC4626 {

function MIN_ASSETS_REMAINING() external returns (uint256);

function METADEPOSIT_TYPEHASH() external view returns (bytes32);

function METAREDEEM_TYPEHASH() external view returns (bytes32);

/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens, gasless
* @param owner address of funds owner
* @param receiver address of funds receiver
* @param assets amount of underlying to deposit
* @param deadline The permit execution deadline
* @param permit The v,r,s components together with value and deadline of permit packed into PermitParams, can be empty
* @param sigParams The v,r,s components of the signed message packed into SignatureParams
*/
function metaDeposit(
address owner,
address receiver,
uint256 assets,
uint256 deadline,
PermitParams calldata permit,
SignatureParams calldata sigParams
) external returns (uint256);

/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver, gasless
* @param owner address of funds owner
* @param receiver address of funds receiver
* @param shares value of shares to burn
* @param deadline The permit execution deadline
* @param sigParams The v,r,s components of the signed message packed into SignatureParams
*/
function metaRedeem(
address owner,
address receiver,
uint256 shares,
uint256 deadline,
SignatureParams calldata sigParams
) external returns (uint256);

/**
* @dev Redeems shares, and stop earning rewards
* @param to Address to redeem to
Expand All @@ -48,22 +104,6 @@ interface IStakeToken is IERC4626 {
*/
function cooldown() external;

/**
* @dev Allows staking a certain amount of STAKED_TOKEN with gasless approvals (permit)
* @param amount The amount to be staked
* @param deadline The permit execution deadline
* @param v The v component of the signed message
* @param r The r component of the signed message
* @param s The s component of the signed message
*/
function stakeWithPermit(
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;

/**
* @dev Returns the current exchange rate
* @return exchangeRate as 18 decimal precision uint216
Expand Down
8 changes: 5 additions & 3 deletions tests/Pause.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ contract Pause is StakeTestBase {
stakeToken.deposit(0, user);
}

function test_stakeWithPermit_should_revert() external {
function test_metaDeposit_should_revert() external {
_setPaused(true);

IStakeToken.PermitParams memory permit;
IStakeToken.SignatureParams memory sigParams;

vm.expectRevert(PausableUpgradeable.EnforcedPause.selector);
vm.prank(user);
stakeToken.stakeWithPermit(0, 0, 0, bytes32(0), bytes32(0));
stakeToken.metaDeposit(address(0), address(0), 0, 0, permit, sigParams);
}

function test_redeem_should_revert() external {
Expand Down
Loading