Skip to content

Commit

Permalink
feat: add depositWithPermit in GearStakingV3
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhovitsky committed Oct 17, 2023
1 parent cd80746 commit 19f25e0
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 7 deletions.
19 changes: 19 additions & 0 deletions contracts/governance/GearStakingV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.17;

import {SafeERC20} from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import {AP_GEAR_TOKEN, IAddressProviderV3, NO_VERSION_CONTROL} from "../interfaces/IAddressProviderV3.sol";
Expand Down Expand Up @@ -68,10 +69,28 @@ contract GearStakingV3 is ACLNonReentrantTrait, IGearStakingV3 {
/// @notice Stakes given amount of GEAR, and, optionally, performs a sequence of votes
/// @param amount Amount of GEAR to stake
/// @param votes Sequence of votes to perform, see `MultiVote`
/// @dev Requires approval from `msg.sender` for GEAR to this contract
function deposit(uint96 amount, MultiVote[] calldata votes) external override nonReentrant {
_deposit(amount, msg.sender, votes); // U: [GS-02]
}

/// @notice Same as `deposit` but uses signed EIP-2612 permit message
/// @param amount Amount of GEAR to stake
/// @param votes Sequence of votes to perform, see `MultiVote`
/// @param deadline Permit deadline
/// @dev `v`, `r`, `s` must be a valid signature of the permit message from `msg.sender` for GEAR to this contract
function depositWithPermit(
uint96 amount,
MultiVote[] calldata votes,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external override nonReentrant {
try IERC20Permit(gear).permit(msg.sender, address(this), amount, deadline, v, r, s) {} catch {} // U:[GS-02]
_deposit(amount, msg.sender, votes); // U:[GS-02]
}

/// @dev Implementation of `deposit`
function _deposit(uint96 amount, address to, MultiVote[] calldata votes) internal {
IERC20(gear).safeTransferFrom(msg.sender, address(this), amount);
Expand Down
9 changes: 9 additions & 0 deletions contracts/interfaces/IGearStakingV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ interface IGearStakingV3 is IGearStakingV3Events, IVersion {

function deposit(uint96 amount, MultiVote[] calldata votes) external;

function depositWithPermit(
uint96 amount,
MultiVote[] calldata votes,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;

function multivote(MultiVote[] calldata votes) external;

function withdraw(uint96 amount, address to, MultiVote[] calldata votes) external;
Expand Down
9 changes: 9 additions & 0 deletions contracts/test/mocks/governance/GearStakingMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ contract GearStakingMock is IGearStakingV3 {

function deposit(uint96 amount, MultiVote[] calldata votes) external {}

function depositWithPermit(
uint96 amount,
MultiVote[] calldata votes,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {}

function multivote(MultiVote[] calldata votes) external {}

function withdraw(uint96 amount, address to, MultiVote[] calldata votes) external {}
Expand Down
38 changes: 31 additions & 7 deletions contracts/test/unit/governance/GearStaking.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IGearStakingV3Events, MultiVote, VotingContractStatus} from "../../../in
import {IVotingContractV3} from "../../../interfaces/IVotingContractV3.sol";

import "../../../interfaces/IAddressProviderV3.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";

// TEST
import "../../lib/constants.sol";
Expand Down Expand Up @@ -79,17 +80,40 @@ contract GearStakingTest is Test, IGearStakingV3Events {
tokenTestSuite.mint(gearToken, USER, WAD);
tokenTestSuite.approve(gearToken, USER, address(gearStaking));

vm.expectEmit(true, false, false, true);
emit DepositGear(USER, WAD);
uint256 snapshot = vm.snapshot();
for (uint256 i; i < 2; ++i) {
bool withPermit = i == 1;

vm.expectCall(address(votingContract), abi.encodeCall(IVotingContractV3.vote, (USER, uint96(WAD / 2), "")));
vm.expectEmit(true, false, false, true);
emit DepositGear(USER, WAD);

vm.prank(USER);
gearStaking.deposit(uint96(WAD), votes);
if (withPermit) {
vm.mockCall(
gearToken,
abi.encodeCall(IERC20Permit.permit, (USER, address(gearStaking), WAD, 0, 0, bytes32(0), bytes32(0))),
bytes("")
);
vm.expectCall(
gearToken,
abi.encodeCall(IERC20Permit.permit, (USER, address(gearStaking), WAD, 0, 0, bytes32(0), bytes32(0)))
);
}

assertEq(gearStaking.balanceOf(USER), WAD);
vm.expectCall(address(votingContract), abi.encodeCall(IVotingContractV3.vote, (USER, uint96(WAD / 2), "")));

assertEq(gearStaking.availableBalance(USER), WAD / 2);
vm.prank(USER);
if (withPermit) {
gearStaking.depositWithPermit(uint96(WAD), votes, 0, 0, bytes32(0), bytes32(0));
} else {
gearStaking.deposit(uint96(WAD), votes);
}

assertEq(gearStaking.balanceOf(USER), WAD);

assertEq(gearStaking.availableBalance(USER), WAD / 2);

vm.revertTo(snapshot);
}
}

/// @dev U:[GS-03]: withdraw performs operations in order and emits events
Expand Down

0 comments on commit 19f25e0

Please sign in to comment.