Platform: Code4rena
Start Date: 07/06/2022
Pot Size: $75,000 USDC
Total HM: 11
Participants: 77
Period: 7 days
Judge: gzeon
Total Solo HM: 7
Id: 124
League: ETH
Rank: 52/77
Findings: 1
Award: $89.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xkatana, 0xmint, Bronicle, Chom, Cityscape, Deivitto, Funen, GimelSec, GreyArt, IllIllI, JC, Lambda, Meera, Nethermind, Picodes, PierrickGT, Ruhum, Sm4rty, Tadashi, TerrierLover, TomJ, Trumpero, Waze, _Adam, antonttc, ayeslick, c3phas, catchup, cccz, cloudjunky, cryptphi, csanuragjain, delfin454000, dipp, ellahi, fatherOfBlocks, hake, hansfriese, hyh, joestakey, jonah1005, kenzo, minhquanym, oyc_109, sach1r0, saian, simon135, slywaters, sorrynotsorry, sseefried, unforgiven, xiaoming90, z3s, zzzitron
89.1872 USDC - $89.19
The idea behind a 6/30/90/360 measure for WEEK/MONTH/QUARTER/YEAR was really interesting. But it's also misleading. Perhaps call them "Notional Weeks", "Notional Months". Thinking of a month as being 30 days and a year as 360 days is not such a stretch but a 6 day week is 14.3% different from reality.
An alternative might be 7 day weeks, 28 day "months", 13 months a year, and 1 special day -- New Years Day -- that is part of no month. (In a leap year have two such special days.) 7*4*13 + 1 = 365
.
getDecodedID
-> getDecodedId
(to be consistent with getCurrencyId
)The magic numbers used in functions to encode trades, e.g. EncodeDecode.sol:76-79 are error prone.
Perhaps define some global constants near the definition of enum TradeActionType
(Types.sol:18-34) as follows:
uint256 constant LendMinImpliedRateOffset = 120; uint256 constant LendFCashAmountOffset = LendMinImpliedRateOffset + 32; uint256 constant LendMarketIndexOffset = LendFCashAmountOffset + 88; uint256 constant LendTradeActionTypeOffset = LendMarketIndex + 8;
and then rewrite the relevant part as follows:
action[0].trades[0] = bytes32( (uint256(uint8(TradeActionType.Borrow)) << LendTradeActionTypeOffset) | (uint256(marketIndex) << LendMarketIndexOffset) | (uint256(fCashAmount) << LendFCashAmountOffset) | (uint256(maxImpliedRate) << LendMinImpliedRateOffset)
maxImpliedRate
Struct RedeemOpts says a maxImpliedRate
of zero means no limit but NotionalTradeModule._redeem uses type(uint32).max
.
wfCashERC4626.deposit
and wfCashERC4626.mint
Change the comment Will revert if matured
to Will revert if matured or has idiosyncratic maturity
since it will also revert under those circumstances.
wfCashERC4626._safeNegInt88
is not safefunction _safeNegInt88(uint256 x) private pure returns (int88) { int256 y = -int256(x); require(int256(type(int88).min) <= y); return int88(y); }
Because of the way Two's Complement arithmetic works this function will actually return positive values for x >= 2**255 + 2*87 + 1
.
This becomes more clear when expressed in hexadecimal: x >= 0x8000000000000000000000000000000000000000008000000000000000000001
.
In fact, this particular value is the largest positive value that could be returned from _safeNegInt88
as it is currently written.
Looking back through commits to the original (non-competition) repo we see that the function was used in functions convertToAssets
and convertToShares
which would have been a problem but is no longer.
Instead of focusing on types we will talk purely in terms of bit-patterns -- expressed in hexadecimal for brevity -- throughout this PoC to keep things clear.
x
have bit-pattern 0x8000000000000000000000000000000000000000008000000000000000000001
.int256(x)
has the same bit-pattern-int256(x)
flips all the bits and adds one. Thus y
has bit-pattern 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFFFF
.0
.int256(type(int88).min)
is a negative number soint256(type(int88).min) <= y
int88(y)
will keep the sign-bit and discard the next 255 - 87
bits giving us the bit-pattern of 7FFFFFFFFFFFFFFFFFFFFF
.2**87 - 1
in Two's Complement arithmetic.Many different values can produce this same value. All that is required is that the first bit of the number is 1
and the final 88 bits have bit-pattern 0x8000000000000000000001
e.g. 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8000000000000000000001
will produce the same output as will 0xCAFEBABECAFEBABECAFEBABECAFEBABECAFEBABEEE8000000000000000000001
Manual Inspection
Assuming the intention of the function was to return the negative of any number up to 2**87 - 1
it should be written as:
function _safeNegInt88(uint256 x) private pure returns (int88) { require(x < 2**87, "too big!"); return -int88(int256(x)); }