Skip to content

Commit dc3be15

Browse files
Merge pull request #175 from euler-xyz/only-owner-or-subaccount
Add `onlyEVCAccount` modifier and `_msgSenderOnlyEVCAccount`, `_msgSenderOnlyEVCAccountOwner` functions
2 parents ef8c02c + 3686aea commit dc3be15

File tree

2 files changed

+148
-10
lines changed

2 files changed

+148
-10
lines changed

src/utils/EVCUtil.sol

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ abstract contract EVCUtil {
4949
_;
5050
}
5151

52+
/// @notice Ensures a standard authentication path on the EVC allowing the account owner or any of its EVC accounts.
53+
/// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context.
54+
/// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress.
55+
/// @dev This modifier must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw.
56+
/// @dev This modifier must not be used on checkAccountStatus and checkVaultStatus functions.
57+
/// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on
58+
/// the EVC.
59+
modifier onlyEVCAccount() virtual {
60+
_authenticateCallerWithStandardContextState(false);
61+
_;
62+
}
63+
5264
/// @notice Ensures a standard authentication path on the EVC.
5365
/// @dev This modifier checks if the caller is the EVC and if so, verifies the execution context.
5466
/// It reverts if the operator is authenticated, control collateral is in progress, or checks are in progress.
@@ -59,7 +71,7 @@ abstract contract EVCUtil {
5971
/// @dev This modifier can be used on access controlled functions to prevent non-standard authentication paths on
6072
/// the EVC.
6173
modifier onlyEVCAccountOwner() virtual {
62-
_onlyEVCAccountOwner();
74+
_authenticateCallerWithStandardContextState(true);
6375
_;
6476
}
6577

@@ -121,6 +133,29 @@ abstract contract EVCUtil {
121133
return sender;
122134
}
123135

136+
/// @notice Retrieves the message sender, ensuring it's any EVC account meaning that the execution context is in a
137+
/// standard state (not operator authenticated, not control collateral in progress, not checks in progress).
138+
/// @dev This function must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw.
139+
/// @dev This function must not be used on checkAccountStatus and checkVaultStatus functions.
140+
/// @dev This function can be used on access controlled functions to prevent non-standard authentication paths on
141+
/// the EVC.
142+
/// @return The address of the message sender.
143+
function _msgSenderOnlyEVCAccount() internal view returns (address) {
144+
return _authenticateCallerWithStandardContextState(false);
145+
}
146+
147+
/// @notice Retrieves the message sender, ensuring it's the EVC account owner and that the execution context is in a
148+
/// standard state (not operator authenticated, not control collateral in progress, not checks in progress).
149+
/// @dev It assumes that if the caller is not the EVC, the caller is the account owner.
150+
/// @dev This function must not be used on functions utilized by liquidation flows, i.e. transfer or withdraw.
151+
/// @dev This function must not be used on checkAccountStatus and checkVaultStatus functions.
152+
/// @dev This function can be used on access controlled functions to prevent non-standard authentication paths on
153+
/// the EVC.
154+
/// @return The address of the message sender.
155+
function _msgSenderOnlyEVCAccountOwner() internal view returns (address) {
156+
return _authenticateCallerWithStandardContextState(true);
157+
}
158+
124159
/// @notice Calls the current external function through the EVC.
125160
/// @dev This function is used to route the current call through the EVC if it's not already coming from the EVC. It
126161
/// makes the EVC set the execution context and call back this contract with unchanged calldata. msg.sender is used
@@ -159,12 +194,14 @@ abstract contract EVCUtil {
159194
}
160195
}
161196

162-
/// @notice Ensures that the function is called only by the EVC account owner
197+
/// @notice Ensures that the function is called only by the EVC account owner or any of its EVC accounts
163198
/// @dev This function checks if the caller is the EVC and if so, verifies that the execution context is not in a
164-
/// special state (operator authenticated, collateral control in progress, or checks in progress). If the owner was
165-
/// already registered on the EVC, it verifies that the onBehalfOfAccount is the owner.
166-
/// @dev Reverts if the caller is not the EVC or if the execution context is in a special state.
167-
function _onlyEVCAccountOwner() internal view {
199+
/// special state (operator authenticated, collateral control in progress, or checks in progress). If
200+
/// onlyAccountOwner is true and the owner was already registered on the EVC, it verifies that the onBehalfOfAccount
201+
/// is the owner. If onlyAccountOwner is false, it allows any EVC account of the owner to call the function.
202+
/// @param onlyAccountOwner If true, only allows the account owner; if false, allows any EVC account of the owner
203+
/// @return The address of the message sender.
204+
function _authenticateCallerWithStandardContextState(bool onlyAccountOwner) internal view returns (address) {
168205
if (msg.sender == address(evc)) {
169206
EC ec = EC.wrap(evc.getRawExecutionContext());
170207

@@ -173,11 +210,18 @@ abstract contract EVCUtil {
173210
}
174211

175212
address onBehalfOfAccount = ec.getOnBehalfOfAccount();
176-
address owner = evc.getAccountOwner(onBehalfOfAccount);
177213

178-
if (owner != address(0) && owner != onBehalfOfAccount) {
179-
revert NotAuthorized();
214+
if (onlyAccountOwner) {
215+
address owner = evc.getAccountOwner(onBehalfOfAccount);
216+
217+
if (owner != address(0) && owner != onBehalfOfAccount) {
218+
revert NotAuthorized();
219+
}
180220
}
221+
222+
return onBehalfOfAccount;
181223
}
224+
225+
return msg.sender;
182226
}
183227
}

test/unit/EVCUtil/EVCUtil.t.sol

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ contract EVCClient is EVCUtil {
4141
// do nothing
4242
}
4343

44+
function calledByEVCAccount() external onlyEVCAccount {
45+
// do nothing
46+
}
47+
4448
function calledByEVCAccountOwner() external onlyEVCAccountOwner {
4549
// do nothing
4650
}
@@ -53,6 +57,14 @@ contract EVCClient is EVCUtil {
5357
return _msgSenderForBorrow();
5458
}
5559

60+
function msgSenderOnlyEVCAccount() external view returns (address) {
61+
return _msgSenderOnlyEVCAccount();
62+
}
63+
64+
function msgSenderOnlyEVCAccountOwner() external view returns (address) {
65+
return _msgSenderOnlyEVCAccountOwner();
66+
}
67+
5668
fallback(bytes calldata) external returns (bytes memory) {
5769
return abi.encode(IVault.checkAccountStatus.selector);
5870
}
@@ -187,56 +199,138 @@ contract EVCUtilTest is Test {
187199
evcClient.calledByEVCWithChecksInProgress();
188200
}
189201

190-
function test_calledByEVCAccountOwner(address caller, uint8 id) external {
202+
function test_calledByEVCAccount_calledByEVCAccountOwner_msgSenderOnlyEVCAccount_msgSenderOnlyEVCAccountOwner(
203+
address caller,
204+
uint8 id
205+
) external {
191206
vm.assume(!evc.haveCommonOwner(caller, address(0)) && !evc.haveCommonOwner(caller, address(evc)));
192207
vm.assume(id != 0);
193208

194209
// msg.sender is not EVC
195210
evc.setOnBehalfOfAccount(address(0));
211+
vm.prank(caller);
212+
evcClient.calledByEVCAccount();
213+
196214
vm.prank(caller);
197215
evcClient.calledByEVCAccountOwner();
198216

217+
vm.prank(caller);
218+
assertEq(evcClient.msgSenderOnlyEVCAccount(), caller);
219+
220+
vm.prank(caller);
221+
assertEq(evcClient.msgSenderOnlyEVCAccountOwner(), caller);
222+
199223
// msg.sender is EVC and operator is authenticated
200224
evc.setOperatorAuthenticated(true);
225+
vm.prank(address(evc));
226+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
227+
evcClient.calledByEVCAccount();
228+
201229
vm.prank(address(evc));
202230
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
203231
evcClient.calledByEVCAccountOwner();
204232

233+
vm.prank(address(evc));
234+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
235+
evcClient.msgSenderOnlyEVCAccount();
236+
237+
vm.prank(address(evc));
238+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
239+
evcClient.msgSenderOnlyEVCAccountOwner();
240+
205241
// msg.sender is EVC and control collateral is in progress
206242
evc.setOperatorAuthenticated(false);
207243
evc.setControlCollateralInProgress(true);
244+
vm.prank(address(evc));
245+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
246+
evcClient.calledByEVCAccount();
247+
208248
vm.prank(address(evc));
209249
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
210250
evcClient.calledByEVCAccountOwner();
211251

252+
vm.prank(address(evc));
253+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
254+
evcClient.msgSenderOnlyEVCAccount();
255+
256+
vm.prank(address(evc));
257+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
258+
evcClient.msgSenderOnlyEVCAccountOwner();
259+
212260
// msg.sender is EVC and checks are in progress
213261
evc.setControlCollateralInProgress(false);
214262
evc.setChecksInProgress(true);
263+
vm.prank(address(evc));
264+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
265+
evcClient.calledByEVCAccount();
266+
215267
vm.prank(address(evc));
216268
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
217269
evcClient.calledByEVCAccountOwner();
218270

271+
vm.prank(address(evc));
272+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
273+
evcClient.msgSenderOnlyEVCAccount();
274+
275+
vm.prank(address(evc));
276+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
277+
evcClient.msgSenderOnlyEVCAccountOwner();
278+
219279
// msg.sender is EVC, the owner is not registered yet
220280
evc.setChecksInProgress(false);
221281
evc.setOnBehalfOfAccount(caller);
282+
assertEq(evc.getAccountOwner(caller), address(0));
283+
vm.prank(address(evc));
284+
evcClient.calledByEVCAccount();
285+
222286
assertEq(evc.getAccountOwner(caller), address(0));
223287
vm.prank(address(evc));
224288
evcClient.calledByEVCAccountOwner();
225289

290+
assertEq(evc.getAccountOwner(caller), address(0));
291+
vm.prank(address(evc));
292+
assertEq(evcClient.msgSenderOnlyEVCAccount(), caller);
293+
294+
assertEq(evc.getAccountOwner(caller), address(0));
295+
vm.prank(address(evc));
296+
assertEq(evcClient.msgSenderOnlyEVCAccountOwner(), caller);
297+
226298
// msg.sender is EVC, the owner is registered and the authenticated account is the owner
227299
evc.setOnBehalfOfAccount(address(0));
228300
vm.prank(caller);
229301
evc.call(address(0), caller, 0, "");
230302
assertEq(evc.getAccountOwner(caller), caller);
231303
evc.setOnBehalfOfAccount(caller);
232304
vm.prank(address(evc));
305+
evcClient.calledByEVCAccount();
306+
307+
assertEq(evc.getAccountOwner(caller), caller);
308+
vm.prank(address(evc));
233309
evcClient.calledByEVCAccountOwner();
234310

311+
assertEq(evc.getAccountOwner(caller), caller);
312+
vm.prank(address(evc));
313+
assertEq(evcClient.msgSenderOnlyEVCAccount(), caller);
314+
315+
assertEq(evc.getAccountOwner(caller), caller);
316+
vm.prank(address(evc));
317+
assertEq(evcClient.msgSenderOnlyEVCAccountOwner(), caller);
318+
235319
// msg.sender is EVC, the owner is registered but the authenticated account is not the owner
236320
evc.setOnBehalfOfAccount(address(uint160(uint160(caller) ^ id)));
321+
vm.prank(address(evc));
322+
evcClient.calledByEVCAccount();
323+
237324
vm.prank(address(evc));
238325
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
239326
evcClient.calledByEVCAccountOwner();
327+
328+
vm.prank(address(evc));
329+
assertEq(evcClient.msgSenderOnlyEVCAccount(), address(uint160(uint160(caller) ^ id)));
330+
331+
vm.prank(address(evc));
332+
vm.expectRevert(abi.encodeWithSelector(EVCUtil.NotAuthorized.selector));
333+
evcClient.msgSenderOnlyEVCAccountOwner();
240334
}
241335

242336
function test_msgSender(address caller) external {

0 commit comments

Comments
 (0)