Skip to content

Commit

Permalink
Merge pull request #123 from pods-finance/openzeppelin/M-06
Browse files Browse the repository at this point in the history
[OZ#2: M-06] Phantom permit functions callable from child vault
  • Loading branch information
ggviana committed Nov 28, 2022
2 parents 1f972b4 + fd3ca22 commit f283227
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 6 deletions.
5 changes: 5 additions & 0 deletions abi/STETHVault.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@
"name": "IVault__ZeroAssets",
"type": "error"
},
{
"inputs": [],
"name": "STETHVault__PermitNotAvailable",
"type": "error"
},
{
"anonymous": false,
"inputs": [
Expand Down
13 changes: 13 additions & 0 deletions contracts/interfaces/IWrappedSTETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity 0.8.17;

import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

interface IWrappedSTETH is IERC20Metadata {
function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256);

function wrap(uint256 _stETHAmount) external returns (uint256);

function unwrap(uint256 _wstETHAmount) external returns (uint256);
}
4 changes: 2 additions & 2 deletions contracts/vaults/BaseVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ abstract contract BaseVault is IVault, ERC20Permit, ERC4626, Capped {
uint8 v,
bytes32 r,
bytes32 s
) external override whenNotProcessingDeposits returns (uint256) {
) external virtual override whenNotProcessingDeposits returns (uint256) {
IERC20Permit(asset()).permit(msg.sender, address(this), assets, deadline, v, r, s);
return super.deposit(assets, receiver);
}
Expand Down Expand Up @@ -163,7 +163,7 @@ abstract contract BaseVault is IVault, ERC20Permit, ERC4626, Capped {
uint8 v,
bytes32 r,
bytes32 s
) external override whenNotProcessingDeposits returns (uint256) {
) external virtual override whenNotProcessingDeposits returns (uint256) {
uint256 assets = previewMint(shares);
IERC20Permit(asset()).permit(msg.sender, address(this), assets, deadline, v, r, s);
return super.mint(shares, receiver);
Expand Down
30 changes: 30 additions & 0 deletions contracts/vaults/STETHVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ contract STETHVault is BaseVault {
);
event SharePrice(uint256 indexed roundId, uint256 startSharePrice, uint256 endSharePrice);

error STETHVault__PermitNotAvailable();

constructor(
IConfigurationManager _configuration,
IERC20Metadata _asset,
Expand Down Expand Up @@ -74,6 +76,34 @@ contract STETHVault is BaseVault {
return convertToAssets(10**sharePriceDecimals);
}

/**
* @inheritdoc IVault
*/
function depositWithPermit(
uint256 assets,
address receiver,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint256) {
revert STETHVault__PermitNotAvailable();
}

/**
* @inheritdoc IVault
*/
function mintWithPermit(
uint256 shares,
address receiver,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external override returns (uint256) {
revert STETHVault__PermitNotAvailable();
}

/**
* @inheritdoc BaseVault
*/
Expand Down
78 changes: 74 additions & 4 deletions test/vaults/STETHVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,23 @@ describe('STETHVault', () => {
})

describe('Lifecycle', () => {
it('cannot redeem between a round\'s end and the beginning of the next', async () => {
const assets = ethers.utils.parseEther('100')

await vault.connect(user0).deposit(assets, user0.address)
await vault.connect(vaultController).endRound()
await vault.connect(vaultController).processQueuedDeposits([user0.address])
const shares = await vault.balanceOf(user0.address)

await expect(
vault.connect(user0).redeem(shares, user0.address, user0.address)
).to.be.revertedWithCustomError(vault, 'IVault__ForbiddenWhileProcessingDeposits')
})

it('cannot withdraw between a round\'s end and the beginning of the next', async () => {
const assetAmount = ethers.utils.parseEther('100')
const assets = ethers.utils.parseEther('100')

await vault.connect(user0).deposit(assetAmount, user0.address)
await vault.connect(user0).deposit(assets, user0.address)
await vault.connect(vaultController).endRound()
await vault.connect(vaultController).processQueuedDeposits([user0.address])

Expand All @@ -312,11 +325,20 @@ describe('STETHVault', () => {
})

it('cannot deposit between a round\'s end and the beginning of the next', async () => {
const assetAmount = ethers.utils.parseEther('10')
const assets = ethers.utils.parseEther('10')

await vault.connect(vaultController).endRound()
await expect(
vault.connect(user0).deposit(assetAmount, user0.address)
vault.connect(user0).deposit(assets, user0.address)
).to.be.revertedWithCustomError(vault, 'IVault__ForbiddenWhileProcessingDeposits')
})

it('cannot mint between a round\'s end and the beginning of the next', async () => {
const shares = ethers.utils.parseEther('10')

await vault.connect(vaultController).endRound()
await expect(
vault.connect(user0).mint(shares, user0.address)
).to.be.revertedWithCustomError(vault, 'IVault__ForbiddenWhileProcessingDeposits')
})

Expand All @@ -342,6 +364,54 @@ describe('STETHVault', () => {
})
})

describe('Permit', () => {
it('can deposit with permits', async () => {
const assets = ethers.utils.parseEther('10')

const mockPermit = {
deadline: +new Date(),
v: 0,
r: ethers.utils.randomBytes(32),
s: ethers.utils.randomBytes(32)
}

const tx = vault.connect(user0).depositWithPermit(
assets,
user0.address,
mockPermit.deadline,
mockPermit.v,
mockPermit.r,
mockPermit.s
)

await expect(tx)
.to.be.revertedWithCustomError(vault, 'STETHVault__PermitNotAvailable')
})

it('can mint with permits', async () => {
const shares = ethers.utils.parseEther('10')

const mockPermit = {
deadline: +new Date(),
v: 0,
r: ethers.utils.randomBytes(32),
s: ethers.utils.randomBytes(32)
}

const tx = vault.connect(user0).mintWithPermit(
shares,
user0.address,
mockPermit.deadline,
mockPermit.v,
mockPermit.r,
mockPermit.s
)

await expect(tx)
.to.be.revertedWithCustomError(vault, 'STETHVault__PermitNotAvailable')
})
})

describe('Events', () => {
it('endSharePrice should be consistent with the vault state', async () => {
// This test will only work if InvestRatio = 50%
Expand Down

0 comments on commit f283227

Please sign in to comment.