Skip to content

Refactor Paymaster Contract for Upgradeability and Flexibility #176

Description

@dappsar

1. 📋 Context

The current implementation of ChatterPayPaymaster.sol has several hardcoded values and lacks mechanisms for future upgrades or operational flexibility:

Component Current Behavior Limitations
owner Set once in constructor (msg.sender) No transferOwnership() → permanently fixed
backendSigner Set once in constructor Cannot be updated after deployment
entryPoint Set once in constructor Cannot be updated
Upgradeability None Cannot use proxy patterns like UUPS

This fixed configuration poses maintainability and operational risks, especially if contracts like EntryPoint evolve or signer rotation is needed.


2. 🧩 Proposed Solution Strategy

Refactor ChatterPayPaymaster to support:

  • Upgradeability (via UUPS pattern)
  • Mutable admin-controlled configuration
  • Trackable changes (on-chain metadata for sync)
  • Optional renouncement of ownership for decentralization

Key Elements

Feature Strategy Notes
Ownership Use OwnableUpgradeable from OpenZeppelin Enables transferOwnership() and renounceOwnership()
Upgradeability Add initialize() method with initializer modifier Enables proxy support via UUPS
Config Management Add setters for entryPoint and backendSigner Controlled by onlyOwner
Change Tracking Add configVersion uint counter Can be indexed off-chain
Security Use modifiers and emit events Standard best practice

3. ⛽️ Gas Considerations

Operation Estimated Gas Impact
transferOwnership() ~50k
setEntryPoint() ~20k
setBackendSigner() ~20k
configVersion++ ~5k
initialize() (1-time call) ~100k

Negligible cost for flexibility and future-proofing.


4. 📌 Recommended Implementation

Contracts to Use

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

Base Structure

contract ChatterPayPaymaster is Initializable, OwnableUpgradeable {
    address public backendSigner;
    IEntryPoint public entryPoint;
    uint256 public configVersion;

    function initialize(address _entryPoint, address _backendSigner, address _owner) public initializer {
        require(_entryPoint != address(0), "Invalid EntryPoint");
        require(_backendSigner != address(0), "Invalid Signer");
        __Ownable_init(_owner);
        entryPoint = IEntryPoint(_entryPoint);
        backendSigner = _backendSigner;
        configVersion = 1;
    }

    function setBackendSigner(address newSigner) external onlyOwner {
        require(newSigner != address(0), "Invalid Signer");
        backendSigner = newSigner;
        configVersion++;
    }

    function setEntryPoint(address newEntryPoint) external onlyOwner {
        require(newEntryPoint != address(0), "Invalid EntryPoint");
        entryPoint = IEntryPoint(newEntryPoint);
        configVersion++;
    }
}

ℹ️ __Ownable_init(_owner) requires OwnableUpgradeable from OZ >= v4.9. Use a compatible version.


5. ✅ Benefits

  • Supports signer rotation and EntryPoint upgrades.
  • Compatible with UUPS and other proxy standards.
  • Enables audit-friendly state tracking (configVersion).
  • Prepares for decentralized handover (renounceOwnership()).
  • Aligned with OpenZeppelin best practices.

6. 🔍 Auditor Notes

  • Ensure initialize() is protected with initializer modifier.
  • Avoid external calls in constructors or initializer.
  • Validate non-zero addresses for critical setters.
  • Monitor configVersion to detect off-chain drift.
  • Consider emitting ConfigUpdated events for better traceability.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request
No fields configured for Feature.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions