Platform: Code4rena
Start Date: 22/09/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 133
Period: 3 days
Judge: 0xean
Total Solo HM: 2
Id: 165
League: ETH
Rank: 86/133
Findings: 1
Award: $28.01
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rotcivegaf
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xf15ers, 8olidity, Aymen0909, B2, Bahurum, Bnke0x0, Ch_301, CodingNameKiki, Deivitto, Diana, Funen, IllIllI, JC, JLevick, KIntern_NA, Lambda, OptimismSec, PaludoX0, RockingMiles, Rolezn, Sm4rty, Soosh, Tagir2003, Tointer, TomJ, Triangle, Trust, V_B, Waze, Yiko, __141345__, a12jmx, ajtra, asutorufos, ayeslick, aysha, bbuddha, bharg4v, bobirichman, brgltd, bytera, c3phas, cryptostellar5, cryptphi, csanuragjain, datapunk, delfin454000, durianSausage, exd0tpy, gogo, got_targ, jag, joestakey, karanctf, ladboy233, leosathya, lukris02, mics, millersplanet, natzuu, neko_nyaa, obront, oyc_109, parashar, peritoflores, rbserver, ret2basic, rokinot, ronnyx2017, rvierdiiev, sach1r0, seyni, sikorico, slowmoses, tnevler, yasir, yongskiws
28.0141 USDC - $28.01
https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/sfrxETH.sol#L69 https://github.com/code-423n4/2022-09-frax/blob/55ea6b1ef3857a277e2f47d42029bc0f3d6f9173/src/sfrxETH.sol#L85
The sfrxETH.sol
contract has depositWithSignature()
and mintWithSignature()
functions, with the goal of allowing one user to submit a transaction on behalf of another user with a properly signed message.
However, as it's implemented, these two functions require the owner to submit the transaction themselves, making them useless and removing the functionality from the protocol.
In order to create the approval for the deposit, depositWithSignature()
calls asset.permit(msg.sender, address(this), amount, deadline, v, r, s);
In the OpenZeppelin ERC20Permit contract used, permit()
is as follows:
function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual override { require(block.timestamp <= deadline, "ERC20Permit: expired deadline"); bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); require(signer == owner, "ERC20Permit: invalid signature"); _approve(owner, spender, value); }
As you can see, the first argument is the owner, which must match the signer of the message. If this is the case, the contract is confident that the owner did in fact approve the spending.
However, in this case, msg.sender
is passed to the function. This means the function will only approve the transfer if the owner themselves sends the message, which defeats the purpose of having a signature (since the transaction itself is signed by the owner).
Checking the test suite to see how this slipped past, you can look to frxETH_sfrxETH_combo.t.sol:test_DepositWithSignatureLimitedPermit()
. In it, we call vm.prank(owner)
before testing the function, which allows the function to work as expected. If any other user called the function with owner
's signature, the function would fail.
Manual Review, Foundry
Add owner
as an argument to depositWithSignature()
and mintWithSignature()
and use that address as the first argument to asset.permit()
.
#0 - FortisFortuna
2022-09-26T02:09:05Z
It does save the owner from having to approve
. We could fix permit()
I guess to use a variable instead of just assuming msg.sender
#1 - 0xean
2022-10-13T21:38:11Z
downgrading to QA, the ability to deposit / mint with a single tx is definitely a benefit to the system