-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reentrancy guard transient #354
Changes from all commits
aec45da
dbb36f7
c121676
f4883c7
afbbe80
83fcada
93c2696
333fd3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,4 +40,4 @@ jobs: | |
id: build | ||
|
||
- name: Run linter | ||
run: yarn run prettier | ||
run: yarn run lint:check |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,28 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
import {Constants} from '../libraries/Constants.sol'; | ||
import {Locker} from '../libraries/Locker.sol'; | ||
|
||
contract LockAndMsgSender { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this contract seems a little silly lol There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It definitely is 😂 honestly i think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok i've moved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great! |
||
error ContractLocked(); | ||
|
||
address internal constant NOT_LOCKED_FLAG = address(1); | ||
address internal lockedBy = NOT_LOCKED_FLAG; | ||
|
||
/// @notice Modifier enforcing a reentrancy lock that allows self-reentrancy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can an external party that is not msg.sender trigger self reentrancy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no theres only 1 entry point to the contract so once its locked only the locker has control |
||
modifier isNotLocked() { | ||
// Apply a reentrancy lock for all external callers | ||
if (msg.sender != address(this)) { | ||
if (lockedBy != NOT_LOCKED_FLAG) revert ContractLocked(); | ||
lockedBy = msg.sender; | ||
if (Locker.isLocked()) revert ContractLocked(); | ||
Locker.set(msg.sender); | ||
_; | ||
lockedBy = NOT_LOCKED_FLAG; | ||
Locker.set(address(0)); | ||
} else { | ||
// The contract is allowed to reenter itself, so the lock is not checked | ||
_; | ||
} | ||
} | ||
|
||
/// @notice Calculates the recipient address for a command | ||
/// @param recipient The recipient or recipient-flag for the command | ||
/// @return output The resultant recipient for the command | ||
function map(address recipient) internal view returns (address) { | ||
if (recipient == Constants.MSG_SENDER) { | ||
return lockedBy; | ||
} else if (recipient == Constants.ADDRESS_THIS) { | ||
return address(this); | ||
} else { | ||
return recipient; | ||
} | ||
/// @notice Function to be used instead of msg.sender, as the contract performs self-reentrancy and at | ||
/// times msg.sender == address(this). Instead _msgSender() returns the initiator of the lock | ||
function _msgSender() internal view returns (address) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this will have to override the _msgSender() in BaseActionsRouter.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when you add the v4 swap lib There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah it will, or maybe theyll both have to be overriden by 1 implementing function. Not 100% sure yet until i pull in v4 and see what compiles LOL |
||
return Locker.get(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.24; | ||
|
||
/// @notice A library to implement a reentrancy lock in transient storage. | ||
/// @dev Instead of storing a boolean, the locker's address is stored to allow the contract to know who locked the contract | ||
/// TODO: This library can be deleted when we have the transient keyword support in solidity. | ||
library Locker { | ||
// The slot holding the locker state, transiently. bytes32(uint256(keccak256("Locker")) - 1) | ||
bytes32 constant LOCKER_SLOT = 0x0e87e1788ebd9ed6a7e63c70a374cd3283e41cad601d21fbe27863899ed4a708; | ||
|
||
function set(address locker) internal { | ||
// The locker is always msg.sender or address(0) so does not need to be cleaned | ||
assembly ("memory-safe") { | ||
tstore(LOCKER_SLOT, locker) | ||
} | ||
} | ||
|
||
function get() internal view returns (address locker) { | ||
assembly ("memory-safe") { | ||
locker := tload(LOCKER_SLOT) | ||
} | ||
} | ||
|
||
function isLocked() internal view returns (bool) { | ||
return Locker.get() != address(0); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,12 +61,12 @@ | |
"typescript": "^3.7.3" | ||
}, | ||
"scripts": { | ||
"compile": "hardhat compile", | ||
"test": "hardhat test", | ||
"compile": "hardhat compile && forge build", | ||
"test:hardhat": "yarn compile && hardhat test", | ||
"test:gas": "UPDATE_SNAPSHOT=1 yarn test --grep gas", | ||
"test:all": "UPDATE_SNAPSHOT=1 yarn test", | ||
"test:all": "UPDATE_SNAPSHOT=1 yarn test:hardhat && forge test --isolate", | ||
"prettier:fix": "prettier --write '**/*.ts' && prettier --write '**/*.json'", | ||
"lint:fix": "yarn prettier:fix && forge fmt", | ||
"prettier": "prettier --check '**/*.ts' && forge fmt --check" | ||
"lint": "yarn prettier:fix && forge fmt", | ||
"lint:check": "prettier --check '**/*.ts' && forge fmt --check" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renamed some commands here |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.24; | ||
|
||
import 'forge-std/Test.sol'; | ||
import {Constants} from '../../contracts/libraries/Constants.sol'; | ||
import {Locker} from '../../contracts/libraries/Locker.sol'; | ||
|
||
contract LockerTest is Test { | ||
function test_fuzz_set_get(address locker1, address locker2, address locker3) public { | ||
assertEq(Locker.get(), address(0)); | ||
|
||
Locker.set(locker1); | ||
assertEq(Locker.get(), locker1); | ||
|
||
Locker.set(locker2); | ||
assertEq(Locker.get(), locker2); | ||
|
||
Locker.set(locker3); | ||
assertEq(Locker.get(), locker3); | ||
|
||
Locker.set(address(0)); | ||
assertEq(Locker.get(), address(0)); | ||
} | ||
hensha256 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function test_fuzz_isLocked(address locker) public { | ||
assertEq(Locker.get(), address(0)); | ||
assertEq(Locker.isLocked(), false); | ||
|
||
Locker.set(locker); | ||
// the contract is locked when the locker is not address(0) | ||
assertEq(Locker.isLocked(), locker != address(0)); | ||
|
||
Locker.set(address(0)); | ||
assertEq(Locker.isLocked(), false); | ||
} | ||
|
||
function test_lockerSlot() public { | ||
bytes32 expectedSlot = bytes32(uint256(keccak256('Locker')) - 1); | ||
assertEq(expectedSlot, Locker.LOCKER_SLOT); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was this broken before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no just renamed it because its running both
forge fmt
andprettier
so the namelint
makes more sense thanprettier