50284 sc insight incorrect erc7201 storage implementation in core factory contracts
Submitted on Jul 23rd 2025 at 12:17:16 UTC by @AasifUsmani for Attackathon | Plume Network
Report ID: #50284
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/arc/src/ArcTokenFactory.sol
Impacts:
Protocol insolvency
Temporary freezing of funds for at least 1 hour
Description
Brief/Intro
The ArcTokenFactory and RestrictionsFactory contracts claim to implement ERC7201 namespaced storage via @custom:storage-location erc7201: annotations but use incorrect storage slot calculations that violate the ERC7201 specification. This creates potential storage collision risks and is a standards compliance issue in core Plume Chain infrastructure contracts.
Vulnerability Details
Root Cause Analysis
Both factory contracts contain identical violations of the ERC7201 standard.
ArcTokenFactory.sol (simplified excerpt):
/// @custom:storage-location erc7201:arc.factory.storage
struct FactoryStorage {
// ... storage fields
}
// ❌ VIOLATION: Uses simple keccak256 instead of ERC7201 formula
bytes32 private constant FACTORY_STORAGE_LOCATION = keccak256("arc.factory.storage");
function _getFactoryStorage() private pure returns (FactoryStorage storage fs) {
bytes32 position = FACTORY_STORAGE_LOCATION;
assembly {
fs.slot := position
}
}RestrictionsFactory.sol (simplified excerpt):
/// @custom:storage-location erc7201:restrictions.factory.storage
struct FactoryStorage {
// ... storage fields
}
// ❌ VIOLATION: Uses simple keccak256 instead of ERC7201 formula
bytes32 private constant FACTORY_STORAGE_LOCATION = keccak256("restrictions.factory.storage");
function _getFactoryStorage() private pure returns (FactoryStorage storage fs) {
bytes32 position = FACTORY_STORAGE_LOCATION;
assembly {
fs.slot := position
}
}ERC7201 Standard Requirements
According to EIP-7201, the correct storage slot calculation must be:
// Correct ERC7201 implementation:
bytes32 private constant FACTORY_STORAGE_LOCATION =
keccak256(abi.encode(uint256(keccak256("arc.factory.storage")) - 1)) & ~bytes32(uint256(0xff));The ERC7201 formula ensures collision resistance through:
Double hashing: keccak256(abi.encode(uint256(keccak256(namespace)) - 1))
Byte masking: & ~bytes32(uint256(0xff)) ensures the last byte is 0x00
Offset subtraction: -1 prevents direct collision with the inner hash
Impact Details
Storage Collision Risks
Because the contracts use a plain keccak256(namespace) as the storage slot:
It can collide with storage slots used by common libraries (e.g., OpenZeppelin
AccessControlUpgradeable,UUPSUpgradeable,Initializable).During upgrades, overlapping slots could corrupt state leading to broken behavior or loss of funds.
System-Level Impact
These are core infrastructure contracts:
ArcTokenFactory manages creation and upgrades of ARC tokens.
RestrictionsFactory manages restriction module deployments.
Storage corruption in either can:
Break token creation/upgrades
Corrupt restriction module mappings
Cause system-wide degradation and require emergency migrations
Standards Compliance Issue
Contracts claim ERC7201 compliance via annotations but do not implement its slot calculation correctly. This misleads developers/auditors and breaks tooling that expects ERC7201 namespaces.
Required Actions
References
ArcTokenFactory: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/ArcTokenFactory.sol#L62
RestrictionsFactory: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/arc/src/restrictions/RestrictionsFactory.sol#L43
EIP - 7201 documentation and mandates: https://eips.ethereum.org/EIPS/eip-7201
Proof of Concept
Was this helpful?