Skip to content

Commit

Permalink
Add tests for Erc20PriceOracle base redeem
Browse files Browse the repository at this point in the history
  • Loading branch information
ninokeldishvili committed Jul 10, 2024
1 parent 9b4a706 commit b635b98
Show file tree
Hide file tree
Showing 2 changed files with 370 additions and 219 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,370 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

import {ERC20PriceOracleReceiptVault} from "../../../../../contracts/concrete/vault/ERC20PriceOracleReceiptVault.sol";
import {ERC20PriceOracleReceiptVaultTest, Vm} from "test/foundry/abstract/ERC20PriceOracleReceiptVaultTest.sol";
import {TwoPriceOracle} from "../../../../../contracts/oracle/price/TwoPriceOracle.sol";
import {
LibFixedPointDecimalArithmeticOpenZeppelin,
Math
} from "rain.math.fixedpoint/lib/LibFixedPointDecimalArithmeticOpenZeppelin.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {Receipt as ReceiptContract} from "../../../../../contracts/concrete/receipt/Receipt.sol";
import {ZeroAssetsAmount, ZeroReceiver, ZeroOwner} from "../../../../../contracts/abstract/ReceiptVault.sol";

contract ERC20PriceOracleReceiptVaultBaseRedeemTest is ERC20PriceOracleReceiptVaultTest {
using LibFixedPointDecimalArithmeticOpenZeppelin for uint256;

event WithdrawWithReceipt(
address sender,
address receiver,
address owner,
uint256 assets,
uint256 shares,
uint256 id,
bytes receiptInformation
);

/// Checks that balance owner balance changes after wirthdraw
function checkBalanceChange(
ERC20PriceOracleReceiptVault vault,
address receiver,
address owner,
uint256 id,
uint256 shares,
ReceiptContract receipt,
bytes memory data
) internal {
uint256 initialBalanceOwner = receipt.balanceOf(owner, id);
uint256 assets = shares.fixedPointDiv(id, Math.Rounding.Down);

vault.setWithdrawId(id);

// Set up the event expectation for WithdrawWithReceipt
vm.expectEmit(true, true, true, true);
emit WithdrawWithReceipt(owner, receiver, owner, assets, shares, id, data);

// Call redeem function
vault.redeem(shares, receiver, owner);

uint256 balanceAfterOwner = receipt.balanceOf(owner, id);
assertEq(balanceAfterOwner, initialBalanceOwner - shares);
}

/// Checks that balance owner balance does not change after redeem revert
function checkNoBalanceChange(
ERC20PriceOracleReceiptVault vault,
address receiver,
address owner,
uint256 id,
uint256 shares,
ReceiptContract receipt,
bytes memory expectedRevertData
) internal {
uint256 initialBalanceOwner;
uint256 balanceAfterOwner;

vault.setWithdrawId(id);

if (owner != address(0)) {
initialBalanceOwner = receipt.balanceOf(owner, id);
}

// Check if expectedRevertData is provided
if (expectedRevertData.length > 0) {
vm.expectRevert(expectedRevertData);
} else {
vm.expectRevert();
}
// Call redeem function
vault.redeem(shares, receiver, owner);

if (owner != address(0)) {
balanceAfterOwner = receipt.balanceOf(owner, id);
}
assertEq(balanceAfterOwner, initialBalanceOwner);
}

/// Test Base Redeem function
function testBaseRedeem(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 assets,
uint256 shares,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);
vm.startPrank(alice);
// Start recording logs
vm.recordLogs();
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);
ReceiptContract receipt = getReceipt();

vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18));
// Ensure Alice has enough balance and allowance
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets));

uint256 totalSupply = iAsset.totalSupply();
// Getting ZeroSharesAmount if bounded from 1
assets = bound(assets, 2, totalSupply);
vm.mockCall(
address(iAsset),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets),
abi.encode(true)
);

uint256 oraclePrice = twoPriceOracle.price();

vault.deposit(assets, alice, oraclePrice, bytes(""));

// Bound shares with max avalilable receipt balance
shares = bound(shares, 1, receipt.balanceOf(alice, oraclePrice));
checkBalanceChange(vault, alice, alice, oraclePrice, shares, receipt, bytes(""));
}

/// Test Base Redeem function reverts on zero shares
function testBaseRedeemRevertsOnZeroShares(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 assets,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);
vm.startPrank(alice);
// Start recording logs
vm.recordLogs();
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);
ReceiptContract receipt = getReceipt();

vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18));
// Ensure Alice has enough balance and allowance
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets));

uint256 totalSupply = iAsset.totalSupply();
// Getting ZeroSharesAmount if bounded from 1
assets = bound(assets, 2, totalSupply);
vm.mockCall(
address(iAsset),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets),
abi.encode(true)
);

uint256 oraclePrice = twoPriceOracle.price();

vault.deposit(assets, alice, oraclePrice, bytes(""));

checkNoBalanceChange(
vault, alice, alice, oraclePrice, 0, receipt, abi.encodeWithSelector(ZeroAssetsAmount.selector)
);
}

/// Test Base Redeem function reverts on zero receiver
function testBaseRedeemRevertsOnZeroReceiver(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 assets,
uint256 shares,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);
vm.startPrank(alice);
// Start recording logs
vm.recordLogs();
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);
ReceiptContract receipt = getReceipt();

vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18));
// Ensure Alice has enough balance and allowance
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets));

uint256 totalSupply = iAsset.totalSupply();
// Getting ZeroSharesAmount if bounded from 1
assets = bound(assets, 2, totalSupply);
vm.mockCall(
address(iAsset),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets),
abi.encode(true)
);

uint256 oraclePrice = twoPriceOracle.price();

vault.deposit(assets, alice, oraclePrice, bytes(""));
// Bound shares with max avalilable receipt balance
shares = bound(shares, 1, receipt.balanceOf(alice, oraclePrice));

checkNoBalanceChange(
vault, address(0), alice, oraclePrice, shares, receipt, abi.encodeWithSelector(ZeroReceiver.selector)
);
}

/// Test Base Redeem function reverts on zero owner
function testBaseRedeemRevertsOnZeroOwner(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 assets,
uint256 shares,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);
vm.startPrank(alice);
// Start recording logs
vm.recordLogs();
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);
ReceiptContract receipt = getReceipt();

vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18));
// Ensure Alice has enough balance and allowance
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets));

uint256 totalSupply = iAsset.totalSupply();
// Getting ZeroSharesAmount if bounded from 1
assets = bound(assets, 2, totalSupply);
vm.mockCall(
address(iAsset),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets),
abi.encode(true)
);

uint256 oraclePrice = twoPriceOracle.price();

vault.deposit(assets, alice, oraclePrice, bytes(""));
// Bound shares with max avalilable receipt balance
shares = bound(shares, 1, receipt.balanceOf(alice, oraclePrice));

checkNoBalanceChange(vault, alice, address(0), oraclePrice, shares, receipt, bytes(""));
}

/// Test Base PreviewRedeem returns correct assets
function testBasePreviewRedeem(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 shares,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);
shares = bound(shares, 1, type(uint64).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);

// Prank as Alice to grant role
vm.startPrank(alice);
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);

uint256 oraclePrice = twoPriceOracle.price();
uint256 assets = shares.fixedPointDiv(oraclePrice, Math.Rounding.Down);
vault.setWithdrawId(oraclePrice);

uint256 ResultAssets = vault.previewRedeem(shares);
assertEq(assets, ResultAssets);
// Stop the prank
vm.stopPrank();
}

/// Test Base Redeem function with more than balance
function testBaseRedeemMoreThanBalance(
uint256 fuzzedKeyAlice,
string memory assetName,
uint256 timestamp,
uint256 assets,
uint256 sharesToRedeem,
uint8 xauDecimals,
uint8 usdDecimals,
uint80 answeredInRound
) external {
// Ensure the fuzzed key is within the valid range for secp256
address alice = vm.addr((fuzzedKeyAlice % (SECP256K1_ORDER - 1)) + 1);
// Use common decimal bounds for price feeds
// Use 0-20 so we at least have some coverage higher than 18
usdDecimals = uint8(bound(usdDecimals, 0, 20));
xauDecimals = uint8(bound(xauDecimals, 0, 20));
timestamp = bound(timestamp, 0, type(uint32).max);

vm.warp(timestamp);
TwoPriceOracle twoPriceOracle = createTwoPriceOracle(usdDecimals, usdDecimals, timestamp, answeredInRound);
vm.startPrank(alice);
// Start recording logs
vm.recordLogs();
ERC20PriceOracleReceiptVault vault = createVault(address(twoPriceOracle), assetName, assetName);
ReceiptContract receipt = getReceipt();

vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.totalSupply.selector), abi.encode(1e18));
// Ensure Alice has enough balance and allowance
vm.mockCall(address(iAsset), abi.encodeWithSelector(IERC20.balanceOf.selector, alice), abi.encode(assets));

uint256 totalSupply = iAsset.totalSupply();
// Getting ZeroSharesAmount if bounded from 1
assets = bound(assets, 2, totalSupply);
vm.mockCall(
address(iAsset),
abi.encodeWithSelector(IERC20.transferFrom.selector, alice, vault, assets),
abi.encode(true)
);

uint256 oraclePrice = twoPriceOracle.price();

vault.deposit(assets, alice, oraclePrice, bytes(""));
uint256 availableReceiptBalance = receipt.balanceOf(alice, oraclePrice);

// Make sure sharesToRedeem is more than available balance
sharesToRedeem = bound(sharesToRedeem, availableReceiptBalance + 1, type(uint256).max);
checkNoBalanceChange(vault, alice, alice, oraclePrice, sharesToRedeem, receipt, bytes(""));
}
}
Loading

0 comments on commit b635b98

Please sign in to comment.