GivingThanks

First Flight #28
Beginner FriendlyFoundry
100 EXP
View results
Submission Details
Severity: low
Invalid

Bug Report: Custom Errors for Gas Optimization in GivingThanks.sol and CharityRegistry.sol

Summary:
The contracts in both GivingThanks.sol and CharityRegistry.sol use require statements with string error messages, which are less gas-efficient compared to custom errors. Switching to custom errors will reduce gas costs and improve contract efficiency.

Vulnerability Details:
In both contracts, require statements are used with string messages, which consume more gas due to the storage of the error message strings. Custom errors introduced in Solidity 0.8.4 are more gas-efficient because they only consume gas when the error is actually triggered and don't store strings.

Impact:
The current approach with string-based require messages leads to unnecessary gas consumption when errors are thrown. Replacing these with custom errors will optimize the contract, reducing transaction costs and improving performance.

Tools Used:

  • Manual code review

Recommendations:

  1. Use custom errors instead of string-based error messages.

  2. Change the require statements to use revert with custom error messages.

  3. Modify the Solidity code to implement the custom errors

    Solution: GivingThanks.sol:

    Code Changes

    Replace the string-based error messages with custom errors:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./CharityRegistry.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
// Custom errors
error GivingThanks__CharityNotVerified();
error GivingThanks__FailedToSendEther();
contract GivingThanks is ERC721URIStorage {
CharityRegistry public registry;
uint256 public tokenCounter;
address public immutable i_owner;
constructor(address _registry) ERC721("DonationReceipt", "DRC") {
registry = CharityRegistry(_registry);
i_owner = msg.sender;
tokenCounter = 0;
}
function donate(address charity) public payable {
if (!registry.isVerified(charity)) revert GivingThanks__CharityNotVerified();
(bool sent, ) = charity.call{value: msg.value}("");
if (!sent) revert GivingThanks__FailedToSendEther();
_mint(msg.sender, tokenCounter);
// Create metadata for the tokenURI
string memory uri = _createTokenURI(
msg.sender,
block.timestamp,
msg.value
);
_setTokenURI(tokenCounter, uri);
tokenCounter += 1;
}
function _createTokenURI(
address donor,
uint256 date,
uint256 amount
) internal pure returns (string memory) {
// Create JSON metadata
string memory json = string(
abi.encodePacked(
'{"donor":"',
Strings.toHexString(uint160(donor), 20),
'","date":"',
Strings.toString(date),
'","amount":"',
Strings.toString(amount),
'"}'
)
);
// Encode in base64 using OpenZeppelin's Base64 library
string memory base64Json = Base64.encode(bytes(json));
// Return the data URL
return
string(
abi.encodePacked("data:application/json;base64,", base64Json)
);
}
function updateRegistry(address _registry) public {
registry = CharityRegistry(_registry);
}
}

Solution: CharityRegistry.sol

Code Changes

Replace the string-based error messages with custom errors:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Custom errors
error CharityRegistry__OnlyAdminCanVerify();
error CharityRegistry__CharityNotRegistered();
error CharityRegistry__OnlyAdminCanChangeAdmin();
contract CharityRegistry {
address public immutable i_admin;
mapping(address => bool) public s_verifiedCharities;
mapping(address => bool) public s_registeredCharities;
constructor() {
i_admin = msg.sender;
}
function registerCharity(address charity) public {
s_registeredCharities[charity] = true;
}
function verifyCharity(address charity) public {
if (msg.sender != i_admin) revert CharityRegistry__OnlyAdminCanVerify();
if (!s_registeredCharities[charity]) revert CharityRegistry__CharityNotRegistered();
s_verifiedCharities[charity] = true;
}
function isVerified(address charity) public view returns (bool) {
return s_verifiedCharities[charity];
}
function changeAdmin(address newAdmin) public {
if (msg.sender != i_admin) revert CharityRegistry__OnlyAdminCanChangeAdmin();
i_admin = newAdmin;
}
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.