-
Notifications
You must be signed in to change notification settings - Fork 183
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
Add Reentrancy-Guard modifier to external MP methods #1334
Conversation
…quest and updated tests Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
core/contracts/financial-templates/implementation/ExpiringMultiPartyCreator.sol
Outdated
Show resolved
Hide resolved
address internal finderAddress; | ||
|
||
constructor(address _finderAddress) public { | ||
finderAddress = _finderAddress; | ||
} | ||
|
||
function _registerContract(address[] memory parties, address contractToRegister) internal { | ||
function _registerContract(address[] memory parties, address contractToRegister) internal nonReentrant() { |
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.
Added because of call to registry.registerContract
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.
So just to double-check: reentrancy would occur here if the party registered was a smart contract with a hook it could re-enter at this point? what would this enable it to do within the context of the _registerContract
function?
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.
This is probably overkill, I was just putting nonReentrant
on functions that made external calls. The reetrancy would be if the registered Registry
address in the Finder
was a smart contract that attempted to re-enter.
I can't think of an actual exploit they would do, but I suppose re-entrancy is possible.
Registry registry = Registry(finder.getImplementationAddress(OracleInterfaces.Registry));
registry.registerContract(parties, contractToRegister);
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.
I will remove though because its not an external method
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
After discussions with the team, we concluded that in a perfect world we would add reentrancy-guards to all external methods, including view-only methods with the Currently, there are external methods in As of the time of posting, |
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
…ctors, has 94 bytes of bytecode left Signed-off-by: Nick Pai <npai.nyc@gmail.com>
I've added This leaves the EMPCreator with < 100 bytes of bytecode remaining. I'd still like to find a way to add |
modifier nonReentrant() { | ||
// On the first call to nonReentrant, _notEntered will be true | ||
require(_notEntered, "ReentrancyGuard: reentrant call"); | ||
|
||
// Any calls to nonReentrant after this point will fail | ||
_notEntered = false; | ||
|
||
_; | ||
|
||
// By storing the original value once again, a refund is triggered (see | ||
// https://eips.ethereum.org/EIPS/eip-2200) | ||
_notEntered = true; | ||
} |
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.
Would it help the bytecode size if we put all the logic in two (or just one) internal methods _preEntranceCheck()
and _postEntranceReset()
(the latter may not help/be necessary)? Obviously, it's messier, but it just wanted to try it in case it would slightly improve our bytecode issues (my worry is that the require call is taking a lot of bytecode and being copied around to every method).
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.
Wouldn't this have the same number of require statements per function call?
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.
The logic would be the same. But the way Solidity organizes the bytecode may be different. For instance, I think modifiers generally work this way:
function 1 start
modifier bytecode1 ( pre _; )
function 1 logic bytecode
modifier bytecode2 ( post _; )
function 1 end
function 2 start
modifier bytecode1 ( pre _; )
function 2 logic bytecode
modifier bytecode2 ( post _; )
function 2 end
...
The modifier bytecode would just be pasted into every function.
If you were to have the modifier call a function, it might look like this:
function 1 start
JUMP to modifier bytecode1
function 1 logic bytecode
JUMP to modifier bytecode2
function 1 end
function 1 start
JUMP to modifier bytecode1
function 1 logic bytecode
JUMP to modifier bytecode2
function 1 end
...
modifier bytecode1 (pre _; )
modifier bytecode2 (post _; )
In the latter, there's only one instruction to run each part of the modifier bytecode -- jumping to the function that the modifier calls. In the former, the entire modifier bytecode will be pasted for each function, bloating the bytecode. All the opcodes and data that make up that require statement may only be in one place in the bytecode rather than copied into every function. We've established in the past that require statements have a pretty big impact on the bytecode (especially when they have a message), so I thought eliminating the repetition may help -- although I'm not 100% sure of how solidity organizes this. It could require you changing the number of runs
in the optimizer to see the impact.
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.
Ok this would help, but the overall bytecode impact would be small, likely < 1000 bytes. We'll run into the bytecode limit either way once we add back require messages. I think we should think bigger picture about how to organize the code to save bytecode.
I do think this change would help some and I'll test it out.
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.
This saved ~800 bytes, great idea @mrice32 !
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
I've now applied the lock to all public methods including view-only methods. The one notable exception is |
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Seems reasonable. You could technically create an internal and external version of pfc to mitigate this issue, but since it's not really used for anything outside of the contract (only used for informational purposes), I don't really see a reason to do so, especially since that would give us less bytecode space to work with :) |
Yeah I thought about this too, it comes down to consistency versus bytecode. I'd opt for saving bytecode and I'll include a comment detailing why this public view function does not have t he modifier. |
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
SGTM. |
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
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.
Looks great! A few minor comments.
require(_notEntered, "ReentrancyGuard: reentrant call"); | ||
} | ||
|
||
function _postEntranceSet() internal { |
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.
nit: shouldn't this be _preEntranceSet()
? Since it's before you enter the function? Or am I misunderstanding the naming convention?
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.
yep typo on my part
constructor(ConstructorParams memory params) | ||
public | ||
Liquidatable(params) | ||
// nonReentrant() This modifier is already applied on the FeePayer constructor. |
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.
- Shouldn't the guard be applied at the outer-most layer rather than the inner most? Otherwise, can't you have re-entrancy once you leave the inner-most call?
- Does this fail if you add this here? Isn't the Liquidatable constructor called before the re-entrancy guard goes into effect?
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.
- I think your counterexample is correct. I applied it on the innermost because I didn't want someone to construct a
PricelessPositionManager.sol
orLiquidatable.sol
whose constructor wasn't guarded bynonReentrant()
. In practice this might not be super useful since we are forcing everyone to deployEMP.sol
's. - It doesn't actually fail if I add to
FeePayer
ANDEMP
, even though the bytecode does change (I verified). I ended up adding to just the top-level one inExpiringMultiParty.sol
.
@@ -156,6 +156,7 @@ contract Liquidatable is PricelessPositionManager { | |||
params.minSponsorTokens, | |||
params.timerAddress | |||
) | |||
// nonReentrant() This modifier is already applied on the FeePayer constructor. |
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.
Same comment as above.
) | ||
public | ||
FeePayer(_collateralAddress, _finderAddress, _timerAddress) | ||
// nonReentrant() This modifier is already applied on the FeePayer constructor. |
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.
Same comment as above.
* Note: We do not apply the nonReentrantView() modifier here because it is applied to `payRegularFees()` which will call `pfc()`. | ||
* We could have replaced added an internal `_pfc()` method to be called by `payRegularFees()` in order to make this `nonReentrantView()`, | ||
* but decided not to in order to save bytecode. |
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.
Now that we have the bytecode issue solved, should we do this just for consistency? Not super opinionated either way, but just wanted to mention it.
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.
Yeah thanks for the reminder. I added the modifier back and it ended up adding 25 bytes to the deployedBytecode FYI.
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
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.
Looks awesome -- one minor comment.
@@ -5,5 +5,5 @@ import "./Liquidatable.sol"; | |||
|
|||
|
|||
contract ExpiringMultiParty is Liquidatable { | |||
constructor(ConstructorParams memory params) public Liquidatable(params) {} | |||
constructor(ConstructorParams memory params) public Liquidatable(params) nonReentrant() {} |
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.
Does nonReentrant()
go into effect before or after the Liquidatable constructor gets called? Is it possible to put this modifier before the Liquidatable constructor is called? Or if it's not, maybe it makes sense to add nonReentrant()
to all the constructors (since they shouldn't interfere with one another)?
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.
The constructors do not appear to interfere with each other so I just went ahead and put it on all of them.
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
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.
One minor nit.
*/ | ||
function pfc() public override view returns (FixedPoint.Unsigned memory) { | ||
return super.pfc().add(_getFeeAdjustedCollateral(rawLiquidationCollateral)); | ||
function pfc() public override view nonReentrantView() returns (FixedPoint.Unsigned memory) { |
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.
nit: why not just implement this in FeePayer
? Then all the derived contracts would have to implement is _pfc()
? Just feels a little weird implementing this here since there's nothing barring the base class from just implementing it once.
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.
done good point
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
Signed-off-by: Nick Pai <npai.nyc@gmail.com>
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.
LGTM!
Fixes #1332
Fixes #1311
This leaves us with the following bytecode statistics: