Skip to content

Commit fd751e3

Browse files
committed
feat: add several unit tests for the StrategyFactory
1 parent c7df6bc commit fd751e3

File tree

4 files changed

+187
-16
lines changed

4 files changed

+187
-16
lines changed

src/contracts/strategies/StrategyFactory.sol

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import "./StrategyBase.sol";
88
import "../permissions/Pausable.sol";
99

1010
/**
11-
* @title TODO: write this
11+
* @title Factory contract for deploying StrategyBase contracts for arbitrary ERC20 tokens
12+
* and automatically adding them to the StrategyWhitelist in EigenLayer.
1213
* @author Layr Labs, Inc.
13-
* @notice TODO: write this
14+
* @dev This may not be compatible with non-standard ERC20 tokens. Caution is warranted.
1415
*/
1516
contract StrategyFactory is OwnableUpgradeable, Pausable {
1617

@@ -19,40 +20,45 @@ contract StrategyFactory is OwnableUpgradeable, Pausable {
1920
/// @notice EigenLayer's StrategyManager contract
2021
IStrategyManager public immutable strategyManager;
2122

22-
StrategyBase public immutable strategyImplementation;
23+
StrategyBase public strategyImplementation;
2324

2425
ProxyAdmin public eigenLayerProxyAdmin;
2526

2627
// @notice Mapping token => strategy contract for the token
2728
mapping(IERC20 => IStrategy) public tokenStrategies;
2829

29-
event ProxyAdminChanged(ProxyAdmin previousProxyAdmin, ProxyAdmin newProxyAdmin);
30+
event StrategyImplementationModified(StrategyBase previousImplementation, StrategyBase newImplementation);
31+
event ProxyAdminModified(ProxyAdmin previousProxyAdmin, ProxyAdmin newProxyAdmin);
3032
event StrategySetForToken(IERC20 token, IStrategy strategy);
3133

3234
/// @notice Since this contract is designed to be initializable, the constructor simply sets the immutable variables.
33-
constructor(IStrategyManager _strategyManager, StrategyBase _strategyImplementation) {
35+
constructor(IStrategyManager _strategyManager) {
3436
strategyManager = _strategyManager;
35-
strategyImplementation = _strategyImplementation;
3637
_disableInitializers();
3738
}
3839

3940
function initialize(
4041
address _initialOwner,
4142
IPauserRegistry _pauserRegistry,
4243
uint256 _initialPausedStatus,
44+
StrategyBase _strategyImplementation,
4345
ProxyAdmin _eigenLayerProxyAdmin
4446
)
4547
public virtual initializer
4648
{
4749
_transferOwnership(_initialOwner);
4850
_initializePauser(_pauserRegistry, _initialPausedStatus);
49-
// TODO: decide if a function for changing this is warranted
50-
eigenLayerProxyAdmin = _eigenLayerProxyAdmin;
51-
emit ProxyAdminChanged(ProxyAdmin(address(0)), _eigenLayerProxyAdmin);
51+
_setStrategyImplementation(_strategyImplementation);
52+
_setProxyAdmin(_eigenLayerProxyAdmin);
5253
}
5354

54-
// TODO: document with ample warnings
55-
function deployNewStrategy(IERC20 token) external onlyWhenNotPaused(PAUSED_NEW_STRATEGIES) {
55+
/**
56+
* @notice Deploy a new StrategyBase contract for the ERC20 token.
57+
* @dev A strategy contract must not yet exist for the token.
58+
* $dev Immense caution is warranted for non-standard ERC20 tokens, particularly "reentrant" tokens
59+
* like those that conform to ERC777.
60+
*/
61+
function deployNewStrategy(IERC20 token) external onlyWhenNotPaused(PAUSED_NEW_STRATEGIES) returns (IStrategy newStrategy) {
5662
require(tokenStrategies[token] == IStrategy(address(0)),
5763
"StrategyFactory.deployNewStrategy: Strategy already exists for token");
5864
IStrategy strategy = IStrategy(
@@ -70,6 +76,7 @@ contract StrategyFactory is OwnableUpgradeable, Pausable {
7076
strategiesToWhitelist[0] = strategy;
7177
thirdPartyTransfersForbiddenValues[0] = false;
7278
strategyManager.addStrategiesToDepositWhitelist(strategiesToWhitelist, thirdPartyTransfersForbiddenValues);
79+
return strategy;
7380
}
7481

7582
/**
@@ -99,15 +106,35 @@ contract StrategyFactory is OwnableUpgradeable, Pausable {
99106
}
100107
}
101108

109+
// @notice Owner-only function to modify the `eigenLayerProxyAdmin`
110+
function setProxyAdmin(ProxyAdmin _eigenLayerProxyAdmin) external onlyOwner {
111+
_setProxyAdmin(_eigenLayerProxyAdmin);
112+
}
113+
114+
// @notice Owner-only function to modify the `strategyImplementation`
115+
function setStrategyImplementation(StrategyBase _strategyImplementation) external onlyOwner {
116+
_setStrategyImplementation(_strategyImplementation);
117+
}
118+
102119
function _setStrategyForToken(IERC20 token, IStrategy strategy) internal {
103120
tokenStrategies[token] = strategy;
104121
emit StrategySetForToken(token, strategy);
105122
}
106123

124+
function _setProxyAdmin(ProxyAdmin _eigenLayerProxyAdmin) internal {
125+
emit ProxyAdminModified(eigenLayerProxyAdmin, _eigenLayerProxyAdmin);
126+
eigenLayerProxyAdmin = _eigenLayerProxyAdmin;
127+
}
128+
129+
function _setStrategyImplementation(StrategyBase _strategyImplementation) internal {
130+
emit StrategyImplementationModified(strategyImplementation, _strategyImplementation);
131+
strategyImplementation = _strategyImplementation;
132+
}
133+
107134
/**
108135
* @dev This empty reserved space is put in place to allow future versions to add new
109136
* variables without shifting down storage in the inheritance chain.
110137
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
111138
*/
112-
uint256[49] private __gap;
139+
uint256[47] private __gap;
113140
}

src/test/mocks/StrategyManagerMock.sol

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,14 @@ contract StrategyManagerMock is
123123
// function withdrawalDelayBlocks() external view returns (uint256) {}
124124

125125
function addStrategiesToDepositWhitelist(
126-
IStrategy[] calldata /*strategiesToWhitelist*/,
127-
bool[] calldata /*thirdPartyTransfersForbiddenValues*/
128-
) external pure {}
126+
IStrategy[] calldata strategiesToWhitelist,
127+
bool[] calldata thirdPartyTransfersForbiddenValues
128+
) external {
129+
for (uint256 i = 0; i < strategiesToWhitelist.length; ++i) {
130+
strategyIsWhitelistedForDeposit[strategiesToWhitelist[i]] = true;
131+
thirdPartyTransfersForbidden[strategiesToWhitelist[i]] = thirdPartyTransfersForbiddenValues[i];
132+
}
133+
}
129134

130135
function removeStrategiesFromDepositWhitelist(IStrategy[] calldata /*strategiesToRemoveFromWhitelist*/) external pure {}
131136
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.12;
3+
4+
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
5+
6+
import "src/contracts/strategies/StrategyFactory.sol";
7+
import "src/test/utils/EigenLayerUnitTestSetup.sol";
8+
import "../../contracts/permissions/PauserRegistry.sol";
9+
10+
/**
11+
* @notice Unit testing of the AVSDirectory contract. An AVSs' service manager contract will
12+
* call this to register an operator with the AVS.
13+
* Contracts tested: AVSDirectory
14+
* Contracts not mocked: DelegationManager
15+
*/
16+
contract StrategyFactoryUnitTests is EigenLayerUnitTestSetup {
17+
// Contract under test
18+
StrategyFactory public strategyFactory;
19+
StrategyFactory public strategyFactoryImplementation;
20+
21+
// Contract dependencies
22+
StrategyBase public strategyImplementation;
23+
// eigenLayerProxyAdmin gets deployed in EigenLayerUnitTestSetup
24+
// ProxyAdmin eigenLayerProxyAdmin;
25+
ERC20PresetFixedSupply public underlyingToken;
26+
27+
uint256 initialSupply = 1e36;
28+
address initialOwner = address(this);
29+
30+
uint256 initialPausedStatus = 0;
31+
32+
function setUp() virtual override public {
33+
EigenLayerUnitTestSetup.setUp();
34+
35+
address[] memory pausers = new address[](1);
36+
pausers[0] = pauser;
37+
pauserRegistry = new PauserRegistry(pausers, unpauser);
38+
39+
underlyingToken = new ERC20PresetFixedSupply("Test Token", "TEST", initialSupply, initialOwner);
40+
41+
strategyImplementation = new StrategyBase(strategyManagerMock);
42+
43+
strategyFactoryImplementation = new StrategyFactory(strategyManagerMock);
44+
45+
strategyFactory = StrategyFactory(
46+
address(
47+
new TransparentUpgradeableProxy(
48+
address(strategyFactoryImplementation),
49+
address(eigenLayerProxyAdmin),
50+
abi.encodeWithSelector(StrategyFactory.initialize.selector,
51+
initialOwner,
52+
pauserRegistry,
53+
initialPausedStatus,
54+
strategyImplementation,
55+
eigenLayerProxyAdmin
56+
)
57+
)
58+
)
59+
);
60+
}
61+
62+
function test_initialization() public {
63+
assertEq(
64+
address(strategyFactory.strategyManager()),
65+
address(strategyManagerMock),
66+
"constructor / initializer incorrect, strategyManager set wrong"
67+
);
68+
assertEq(
69+
address(strategyFactory.strategyImplementation()),
70+
address(strategyImplementation),
71+
"constructor / initializer incorrect, strategyImplementation set wrong"
72+
);
73+
assertEq(
74+
address(strategyFactory.eigenLayerProxyAdmin()),
75+
address(eigenLayerProxyAdmin),
76+
"constructor / initializer incorrect, eigenLayerProxyAdmin set wrong"
77+
);
78+
assertEq(
79+
address(strategyFactory.pauserRegistry()),
80+
address(pauserRegistry),
81+
"constructor / initializer incorrect, pauserRegistry set wrong"
82+
);
83+
assertEq(strategyFactory.owner(), initialOwner, "constructor / initializer incorrect, owner set wrong");
84+
assertEq(strategyFactory.paused(), initialPausedStatus, "constructor / initializer incorrect, paused status set wrong");
85+
}
86+
87+
function test_initialize_revert_reinitialization() public {
88+
cheats.expectRevert("Initializable: contract is already initialized");
89+
strategyFactory.initialize(
90+
initialOwner,
91+
pauserRegistry,
92+
initialPausedStatus,
93+
strategyImplementation,
94+
eigenLayerProxyAdmin
95+
);
96+
}
97+
98+
function test_deployNewStrategy() public {
99+
StrategyBase newStrategy = StrategyBase(address(strategyFactory.deployNewStrategy(underlyingToken)));
100+
101+
require(strategyFactory.tokenStrategies(underlyingToken) == newStrategy, "tokenStrategies mapping not set correctly");
102+
require(newStrategy.strategyManager() == strategyManagerMock, "strategyManager not set correctly");
103+
require(eigenLayerProxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(address(newStrategy))))
104+
== address(strategyImplementation),
105+
"strategyImplementation not set correctly");
106+
require(newStrategy.pauserRegistry() == pauserRegistry, "pauserRegistry not set correctly");
107+
require(newStrategy.underlyingToken() == underlyingToken, "underlyingToken not set correctly");
108+
require(strategyManagerMock.strategyIsWhitelistedForDeposit(newStrategy), "underlyingToken is not whitelisted");
109+
require(!strategyManagerMock.thirdPartyTransfersForbidden(newStrategy), "newStrategy has 3rd party transfers forbidden");
110+
}
111+
112+
function test_deployNewStrategy_revert_StrategyAlreadyExists() public {
113+
test_deployNewStrategy();
114+
cheats.expectRevert("StrategyFactory.deployNewStrategy: Strategy already exists for token");
115+
strategyFactory.deployNewStrategy(underlyingToken);
116+
}
117+
118+
function test_whitelistStrategies() public {
119+
StrategyBase strategy = StrategyBase(
120+
address(
121+
new TransparentUpgradeableProxy(
122+
address(strategyImplementation),
123+
address(eigenLayerProxyAdmin),
124+
abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, pauserRegistry)
125+
)
126+
)
127+
);
128+
129+
130+
IStrategy[] memory strategiesToWhitelist = new IStrategy[](1);
131+
bool[] memory thirdPartyTransfersForbiddenValues = new bool[](1);
132+
strategiesToWhitelist[0] = strategy;
133+
thirdPartyTransfersForbiddenValues[0] = true;
134+
strategyFactory.whitelistStrategies(strategiesToWhitelist, thirdPartyTransfersForbiddenValues);
135+
136+
require(strategyFactory.tokenStrategies(underlyingToken) == strategy, "tokenStrategies mapping not set correctly");
137+
require(strategyManagerMock.thirdPartyTransfersForbidden(strategy), "3rd party transfers forbidden not set correctly");
138+
}
139+
}

src/test/utils/EigenLayerUnitTestSetup.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import "src/test/utils/EigenLayerUnitTestBase.sol";
99

1010
abstract contract EigenLayerUnitTestSetup is EigenLayerUnitTestBase {
1111
// Declare Mocks
12-
StrategyManagerMock strategyManagerMock;
12+
StrategyManagerMock public strategyManagerMock;
1313
DelegationManagerMock public delegationManagerMock;
1414
SlasherMock public slasherMock;
1515
EigenPodManagerMock public eigenPodManagerMock;

0 commit comments

Comments
 (0)