Skip to content
Open
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
3 changes: 2 additions & 1 deletion protocol-contracts/staking/contracts/OperatorStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ contract OperatorStaking is ERC1363Upgradeable, ReentrancyGuardTransient, UUPSUp
bytes32 r,
bytes32 s
) public virtual returns (uint256) {
IERC20Permit(asset()).permit(msg.sender, address(this), assets, deadline, v, r, s);
// Use try-catch to prevent frontrun DOS attacks on permit (see ERC-2612)
try IERC20Permit(asset()).permit(msg.sender, address(this), assets, deadline, v, r, s) {} catch {}

return deposit(assets, receiver);
}
Expand Down
14 changes: 10 additions & 4 deletions protocol-contracts/staking/test/OperatorStaking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,26 @@ describe('OperatorStaking', function () {
.withArgs(this.delegatorNoApproval, this.mock, this.permitValue);
});

it('should revert if signature is invalid', async function () {
it('should revert with insufficient allowance if signature is invalid', async function () {
// With try-catch on permit, invalid signature causes permit to silently fail,
// then deposit fails with ERC20InsufficientAllowance since no approval exists
await expect(
this.mock
.connect(this.delegatorNoApproval)
.depositWithPermit(this.permitValue, this.delegatorNoApproval, this.permitDeadline, 0, this.r, this.s),
).to.be.revertedWithCustomError(this.token, 'ECDSAInvalidSignature');
).to.be.revertedWithCustomError(this.token, 'ERC20InsufficientAllowance');
});

it('should revert if signer is invalid', async function () {
it('should succeed if signer has existing approval even with invalid permit', async function () {
// With try-catch on permit, invalid signer causes permit to silently fail,
// but delegator1 already has approval so deposit succeeds
await expect(
this.mock
.connect(this.delegator1)
.depositWithPermit(this.permitValue, this.delegator1, this.permitDeadline, this.v, this.r, this.s),
).to.be.revertedWithCustomError(this.token, 'ERC2612InvalidSigner');
)
.to.emit(this.mock, 'Transfer')
.withArgs(ethers.ZeroAddress, this.delegator1, this.permitValue);
});
});

Expand Down