Lines of code
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/Referral.sol#L8
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/Referral.sol#L16
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/CustomReferralConfiguration.sol#L5
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/CustomReferralConfiguration.sol#L16
Impact
Even though the TreeProxy Pattern is designed to be compatible with EIP-7201, when determining the storage slots for CustomReferralConfiguration
and Referral
, this standard is not implemented. This can lead to potential vulnerabilities in the contract, including:
Inability to correctly manage storage slots, which may result in data corruption.
Increased risk of unauthorized access to sensitive data.
Potential for unexpected behavior in contract interactions, leading to loss of funds or other critical failures.
Proof of Concept
To follow EIP-7201 a contract needs to encode the storage location with the following formular:
keccak256(abi.encode(uint256(keccak256(bytes(id))) - 1)) & ~bytes32(uint256(0xff))
Both CustomReferralConfiguration
and Referral
do not use this pattern. Instead they use a simple string for determining the storage location:
string internal constant CUSTOM_REFERRAL_CONFIGURATION_DOMAIN = "fi.zaros.CustomReferralConfiguration";
…
function load(string memory customReferralCode)
internal
pure
returns (Data storage customReferralConfigurationTestnet)
{
bytes32 slot = keccak256(abi.encode(CUSTOM_REFERRAL_CONFIGURATION_DOMAIN, customReferralCode));
assembly {
customReferralConfigurationTestnet.slot := slot
}
}
Recommended Mitigation Steps
To follow the EIP 7201 standard make sure to encode the string identifier properly for CustomReferralConfiguration:
library CustomReferralConfiguration {
- string internal constant CUSTOM_REFERRAL_CONFIGURATION_DOMAIN = "fi.zaros.CustomReferralConfiguration";
+ string internal constant CUSTOM_REFERRAL_CONFIGURATION_DOMAIN = keccak256(abi.encode(uint256(keccak256("fi.zaros.CustomReferralConfiguration ")) - 1)) & ~bytes32(uint256(0xff));
struct Data {
address referrer;
}
function load(string memory customReferralCode)
internal
pure
returns (Data storage customReferralConfigurationTestnet)
{
bytes32 slot = keccak256(abi.encode(CUSTOM_REFERRAL_CONFIGURATION_DOMAIN, customReferralCode));
assembly {
customReferralConfigurationTestnet.slot := slot
}
}
}
And for Referral:
library Referral {
- string internal constant REFERRAL_DOMAIN = "fi.zaros.Referral";
+ string internal constant REFERRAL_DOMAIN = keccak256(abi.encode(uint256(keccak256("fi.zaros. Referral")) - 1)) & ~bytes32(uint256(0xff));
struct Data {
bytes referralCode;
bool isCustomReferralCode;
}
function load(address accountOwner) internal pure returns (Data storage referralTestnet) {
bytes32 slot = keccak256(abi.encode(REFERRAL_DOMAIN, accountOwner));
assembly {
referralTestnet.slot := slot
}
}
function getReferrerAddress(Data storage self) internal view returns (address referrer) {
if (!self.isCustomReferralCode) {
referrer = abi.decode(self.referralCode, (address));
} else {
referrer = CustomReferralConfiguration.load(string(self.referralCode)).referrer;
}
}
}