Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
RPC_MAINNET="https://eth.llamarpc.com"
# RPC_MAINNET="https://mainnet.infura.io/v3/API-KEY"
6 changes: 5 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ jobs:

- name: Run tests
run: forge test -vvv
env:
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}

- name: Run snapshot
run: forge snapshot
run: forge snapshot
env:
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ To generate the docs, run `npx hardhat docgen` (you may need to run `npm install

### Run Tests

Prior to running tests, you should set up your environment. At present this repository contains fork tests against ETH mainnet; your environment will need an `RPC_MAINNET` key to run these tests. See the `.env.example` file for an example -- two simple options are to copy the LlamaNodes RPC url to your `env` or use your own infura API key in the provided format.

The main command to run tests is:

`forge test -vv`

### Run Static Analysis
Expand Down
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ via_ir = false
# Override the Solidity version (this overrides `auto_detect_solc`)
solc_version = '0.8.12'

[rpc_endpoints]
mainnet = "${RPC_MAINNET}"

# See more config options https://github.com/gakonst/foundry/tree/master/config
10 changes: 7 additions & 3 deletions src/contracts/strategies/StrategyBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,15 @@ contract StrategyBase is Initializable, Pausable, IStrategy {
* @notice calculation of newShares *mirrors* `underlyingToShares(amount)`, but is different since the balance of `underlyingToken`
* has already been increased due to the `strategyManager` transferring tokens to this strategy prior to calling this function
*/
uint256 priorTokenBalance = _tokenBalance() - amount;
if (priorTokenBalance == 0 || totalShares == 0) {
if (totalShares == 0) {
newShares = amount;
} else {
newShares = (amount * totalShares) / priorTokenBalance;
uint256 priorTokenBalance = _tokenBalance() - amount;
if (priorTokenBalance == 0) {
newShares = amount;
} else {
newShares = (amount * totalShares) / priorTokenBalance;
}
}

// checks to ensure correctness / avoid edge case where share rate can be massively inflated as a 'griefing' sort of attack
Expand Down
149 changes: 149 additions & 0 deletions src/test/DepositWithdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity =0.8.12;

import "./EigenLayerTestHelper.t.sol";
import "../contracts/core/StrategyManagerStorage.sol";
import "./mocks/ERC20_OneWeiFeeOnTransfer.sol";

contract DepositWithdrawTests is EigenLayerTestHelper {
uint256[] public emptyUintArray;
Expand Down Expand Up @@ -410,6 +411,154 @@ contract DepositWithdrawTests is EigenLayerTestHelper {

}

function testDepositTokenWithOneWeiFeeOnTransfer(address sender, uint64 amountToDeposit) public fuzzedAddress(sender) {
// MIN_NONZERO_TOTAL_SHARES = 1e9
cheats.assume(amountToDeposit >= 1e9);

uint256 initSupply = 1e50;
address initOwner = address(this);

ERC20_OneWeiFeeOnTransfer oneWeiFeeOnTransferToken = new ERC20_OneWeiFeeOnTransfer(initSupply, initOwner);
IERC20 underlyingToken = IERC20(address(oneWeiFeeOnTransferToken));

// need to transfer extra here because otherwise the `sender` won't have enough tokens
underlyingToken.transfer(sender, 1000);

IStrategy oneWeiFeeOnTransferTokenStrategy = StrategyBase(
address(
new TransparentUpgradeableProxy(
address(baseStrategyImplementation),
address(eigenLayerProxyAdmin),
abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg)
)
)
);

_testDepositToStrategy(sender, amountToDeposit, underlyingToken, oneWeiFeeOnTransferTokenStrategy);
}

/// @notice Shadow-forks mainnet and tests depositing stETH tokens into a "StrategyBase" contract.
function testForkMainnetDepositSteth() public {
// hard-coded inputs
address sender = address(this);
uint64 amountToDeposit = 1e12;

// shadow-fork mainnet
uint256 forkId = cheats.createFork("mainnet");
cheats.selectFork(forkId);

// cast mainnet stETH address to IERC20 interface
IERC20 steth = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84);
IERC20 underlyingToken = steth;

// deploy necessary contracts on the shadow-forked network
// deploy proxy admin for ability to upgrade proxy contracts
eigenLayerProxyAdmin = new ProxyAdmin();
//deploy pauser registry
eigenLayerPauserReg = new PauserRegistry(pauser, unpauser);
/**
* First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are
* not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code.
*/
emptyContract = new EmptyContract();
delegation = DelegationManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
strategyManager = StrategyManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
slasher = Slasher(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
eigenPodManager = EigenPodManager(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);
delayedWithdrawalRouter = DelayedWithdrawalRouter(
address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), ""))
);

address[] memory initialOracleSignersArray = new address[](0);
beaconChainOracle = new BeaconChainOracle(eigenLayerReputedMultisig, initialBeaconChainOracleThreshold, initialOracleSignersArray);

ethPOSDeposit = new ETHPOSDepositMock();
pod = new EigenPod(ethPOSDeposit, delayedWithdrawalRouter, eigenPodManager, REQUIRED_BALANCE_WEI);

eigenPodBeacon = new UpgradeableBeacon(address(pod));

// Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs
DelegationManager delegationImplementation = new DelegationManager(strategyManager, slasher);
StrategyManager strategyManagerImplementation = new StrategyManager(delegation, eigenPodManager, slasher);
Slasher slasherImplementation = new Slasher(strategyManager, delegation);
EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, slasher);
DelayedWithdrawalRouter delayedWithdrawalRouterImplementation = new DelayedWithdrawalRouter(eigenPodManager);
// Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them.
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(delegation))),
address(delegationImplementation),
abi.encodeWithSelector(
DelegationManager.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0/*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(strategyManager))),
address(strategyManagerImplementation),
abi.encodeWithSelector(
StrategyManager.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0/*initialPausedStatus*/,
0/*withdrawalDelayBlocks*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(slasher))),
address(slasherImplementation),
abi.encodeWithSelector(
Slasher.initialize.selector,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0/*initialPausedStatus*/
)
);
eigenLayerProxyAdmin.upgradeAndCall(
TransparentUpgradeableProxy(payable(address(eigenPodManager))),
address(eigenPodManagerImplementation),
abi.encodeWithSelector(
EigenPodManager.initialize.selector,
beaconChainOracle,
eigenLayerReputedMultisig,
eigenLayerPauserReg,
0/*initialPausedStatus*/
)
);

// cheat a bunch of ETH to this address
cheats.deal(address(this), 1e20);
// deposit a huge amount of ETH to get ample stETH
(bool success, bytes memory returnData) = address(steth).call{value: 1e20}("");
require(success, "depositing stETH failed");
returnData;

// deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it
baseStrategyImplementation = new StrategyBase(strategyManager);
IStrategy stethStrategy = StrategyBase(
address(
new TransparentUpgradeableProxy(
address(baseStrategyImplementation),
address(eigenLayerProxyAdmin),
abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg)
)
)
);

_testDepositToStrategy(sender, amountToDeposit, underlyingToken, stethStrategy);

}

function _whitelistStrategy(StrategyManager _strategyManager, StrategyBase _strategyBase) internal returns(StrategyManager) {
// whitelist the strategy for deposit
cheats.startPrank(strategyManager.owner());
Expand Down
Loading