# **PasarHQ Smart Contract Development Documentation**
This document serves as an introduction to Pasar, outlining the core problems it addresses in the e-commerce space and the innovative, blockchain-powered solutions it provides.
# The Problem in Traditional E-commerce
The digital marketplace, while convenient, is plagued by a fundamental lack of trust. The core problem Pasar addresses is the disparity between "what I ordered vs. what I got." This issue stems from a few key areas:
Buyer Vulnerability: Buyers are often at risk of receiving counterfeit, damaged, or incorrect items, with little recourse beyond lengthy and often ineffective dispute processes.
Lack of Trust: Without a trustworthy third-party intermediary, transactions can be fraught with uncertainty, especially between new or international parties.
Opaque Systems: Traditional e-commerce platforms can have complex, non-transparent rules for payments, disputes, and seller-to-seller interactions, leaving both parties feeling powerless.
Limited Transparency: There is often no public, verifiable record of a transaction, making it difficult to prove a sequence of events in a dispute.
# **Pasar's Proffered Solution**
Pasar is a marketplace platform designed to bridge this trust gap by leveraging blockchain technology. Its solution is a hybrid platform that is user-friendly for both Web2 and Web3 users, focusing on security, transparency, and accountability. Key features of the solution include:
Escrow Protection: All transactions are protected by a secure escrow system. Funds are held in a secure state until a delivery is verified and confirmed, ensuring that sellers are paid only when buyers are satisfied.
Robust Delivery Verification: The platform implements a robust system to verify that the received product matches the description, directly addressing the "what I ordered vs. what I got" problem.
Multi-Tiered Dispute Resolution: In the event of a disagreement, Pasar offers a clear, multi-tiered dispute resolution process. It starts with an AI-powered layer and can escalate to an admin-level review, ensuring fair and timely outcomes.
Seamless Onboarding: Pasar's AI-enhanced smart wallets, named Xena, simplify the seller onboarding process, making it accessible to those with limited blockchain experience.
Personal Shopping Assistant (Xiara): Xiara is an AI-powered personal shopping assistant that helps users discover products, provides recommendations, and guides them through the purchase journey, creating a highly personalized and efficient shopping experience.
Integrated Logistics (PasarMove): PasarMove is a fully integrated logistics solution. It ensures seamless, reliable, and secure delivery of products, directly addressing one of the most critical pain points in e-commerce: getting the right product to the right place at the right time.
Flexible Payment Options: The platform supports both traditional fiat and various crypto payments, providing flexibility and catering to a broad user base.
# **How Smart Contracts are the Solution**
**Smart contracts are the technological backbone that makes Pasar's solution possible. They are self-executing, decentralized, and immutable pieces of code that live on the blockchain. They solve the aforementioned problems by:
Automating Trust: Smart contracts eliminate the need for a trusted third party. The logic for holding funds in escrow, releasing them upon delivery, and managing penalties is encoded directly into the contract, which cannot be changed once deployed. This removes the possibility of human error or a malicious intermediary.
Ensuring Escrow Protection: The escrow functionality is handled directly by a smart contract. Funds are locked in a contract address and are only released to the seller when the contract's conditions (e.g., buyer confirmation) are met.
Providing Transparency and Verifiability: Every transaction and state change is recorded on the blockchain. This public, immutable ledger provides complete transparency and a verifiable history that can be used to resolve disputes fairly.
Enforcing Dispute Resolution: The smart contract can be designed to automatically handle parts of the dispute resolution process. For example, if a buyer and seller agree on a refund percentage, the contract can be triggered to release the corresponding funds automatically.
Powering Decentralized Governance: As seen in the PasarAdmin contract, smart contracts can also be used to create a transparent, timelocked governance system for the protocol itself, ensuring that critical changes and upgrades are reviewed and approved by the community.
Integrating with Off-Chain Services: While services like Xiara and PasarMove operate primarily off-chain, their critical interactions can be tied to smart contracts. For instance, a payment confirmation on a smart contract can trigger a delivery request to the PasarMove logistics network, or a contract can log key events related to a transaction that were facilitated by Xiara. This connection ensures the immutability and verifiability of key milestones in the buyer's journey.**
# **# What is Pasar?**
` Pasar is an e-commerce platform solving the 'what I ordered vs what I got' problem by leveraging blockchain and AI to make online shopping seamless, secure, and transparent.`
# **Core Contracts**
***The following is a list of the key smart contracts for the Pasar platform and a brief description of their purpose and functionality:
PasarRegistry.sol: This contract is responsible for seller onboarding and for the creation of the AI-enhanced smart wallets (Xena). It manages the mapping of off-chain seller IDs to their on-chain wallet addresses.
Functions: registerSeller().
Interoperability: The backend system uses this contract to register new sellers. The Xena smart wallets created by this contract are then used by the backend to perform on-chain transactions on behalf of the seller, as detailed in the Pasar Technical Arch For Model.pdf.
PasarEscrow.sol: This is the central contract for facilitating trustless asset transfers. It securely holds buyer funds (specifically ERC-20 stablecoins) until predefined delivery or dispute conditions are met.
Functions: lockFunds(), releaseFundsToCryptoSeller(), releaseFundsToPlatformForFiatSeller(), refundBuyer(), and initiateDispute().
Interoperability: The Backend and ML layers can interact with this contract to initiate transactions and check the state of an order. For instance, the Backend can call lockFunds() when a buyer pays. The releaseFundsToPlatformForFiatSeller() function is critical for the off-chain backend to reconcile fiat payouts.
PasarToken.sol: This is the native ERC-20 fungible token of the Pasar ecosystem. It is used for potential governance, staking rewards, or discounted service fees.
Functions: Standard ERC-20 functions (transfer(), approve(), balanceOf(), etc.).
Interoperability: The Backend can interact with this contract to display user token balances or facilitate rewards.
PasarAdmin.sol: This contract serves as the central governance and upgrade management hub for the Pasar protocol. It enforces a timelock on all critical upgrades and administrative actions, preventing rushed changes.
Functions: scheduleUpgrade(), cancelUpgrade(), performUpgrade(), scheduleRoleChange(), and pause().
Interoperability: The contract is compatible with Chainlink Automation, allowing for the automated execution of upgrades after their timelock has expired. Backend and ML layers can use this contract to check the status of pending upgrades and verify the integrity of the protocol.
AccessManager.sol: This contract provides a high-level, granular Role-Based Access Control (RBAC) system and emergency functionalities for the entire smart contract ecosystem.
Functions: grantRole(), revokeRole(), pause(), and unpause().
Interoperability: This contract is crucial for security and governance. The backend and the AI security agent, Shogun, have a privileged role (PLATFORM_SERVICE_ROLE) that allows them to interact with other contracts. Shogun, for example, can trigger the pause() function on the AccessManager contract to temporarily halt platform operations in the event of a severe security threat.
PasarDispute.sol: This contract manages the on-chain state and verdict execution for disputes. It provides the final, undeniable record of a dispute's resolution.
Functions: openDispute(), submitVerdict(), and executeVerdict().
Interoperability: The Backend, after processing input from the AI-powered dispute resolution engine and/or human admins, calls submitVerdict() to record the final decision. The executeVerdict() function then triggers the corresponding action on the PasarEscrow contract, ensuring the verdict is executed immutably.
PasarVault.sol: This contract is designed to manage various platform-level funds and assets, such as a platform treasury or funds held for specific operations.
Functions: depositFunds(), withdrawFunds(), and manageAssets().
Interoperability: The Backend can interact with this contract to manage platform-level funds securely and transparently.
VaultManager.sol: This is a privileged control contract for managing and overseeing other contracts that handle user funds. It centralizes control over the key financial contracts.
Functions: setEscrowContract(), setPasarThriftContract(), emergencyPause(), and emergencyWithdraw().
Interoperability: This contract is a critical security layer. It allows a privileged role (managed by AccessManager) to update the addresses of other financial contracts or perform emergency actions, ensuring platform adaptability and security.
PasarThrift.sol: This contract allows buyers to set and achieve micro-savings goals for future purchases. It securely holds a user's stablecoins and manages their savings goals.
Functions: setSavingsGoal(), deposit(), and withdrawGoalFunds().
Interoperability: The Backend interacts with this contract to allow users to manage their savings goals from the frontend. The withdrawGoalFunds() function is used to transfer a user's savings to the PasarEscrow contract when they are ready to make a purchase.
PresaleToken.sol: This contract would be used to manage the initial distribution of tokens before they are made available to the public.
Functions: buyToken(), claimToken(), and closePresale().
Interoperability: The Backend would interact with this contract during a token presale event to manage user purchases and claims.**
# **Explanation Of Contracts**
# **1. PasarAdmin**
PasarAdmin.sol is the governance backbone of the Pasar protocol, responsible for:
Proxy Contract Upgrades: Managing upgrades for multiple proxy contracts (e.g., marketplace or escrow contracts) with a 2-day timelock to ensure transparency.
Role Management: Controlling administrative roles (ADMIN_ROLE and DEFAULT_ADMIN_ROLE) with a custom timelocked process for granting, revoking, or canceling permissions.
Automation: Integrating Chainlink Keepers for automated upgrade execution.
Security: Enforcing pausability, reentrancy protection, and access controls to safeguard governance operations.
It works in tandem with other Pasar contracts (e.g., for trading, escrow, or token management) to maintain a secure and upgradable protocol ecosystem.
Step 1: Define Contract Requirements
Objective: Create a governance contract that manages upgrades and roles for the Pasar protocol with strong security guarantees. Requirements:
Support upgrading multiple proxy contracts with a timelock.
Allow role-based administration with timelocked changes.
Integrate Chainlink Automation for automated upgrades.
Include security features like pausability, reentrancy protection, and access control.
Ensure auditability through events and transparent state management.
Design Choices:
Use OpenZeppelin’s TimelockController for upgrade management to leverage its battle-tested timelock functionality.
Implement a custom role management system to avoid bytes memory to bytes calldata type mismatch issues encountered with TimelockController’s role scheduling.
Integrate ReentrancyGuard and Pausable for security.
Use Chainlink’s AutomationCompatibleInterface for automated upgrade execution.
Define a 2-day timelock (MIN_DELAY) for upgrades and role changes to balance security and usability.
Add a 1-hour cooldown (UPKEEP_COOLDOWN) for automated upkeep to prevent excessive executions.
Step 2: Set Up Contract Structure
Objective: Establish the contract’s foundation with imports, inheritance, and state variables. Code Snippet:
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
contract PasarAdmin is TimelockController, AutomationCompatibleInterface, ReentrancyGuard, Pausable {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
struct PendingUpgrade {
address target;
address newImplementation;
uint256 scheduleTime;
bool exists;
}
struct PendingRoleChange {
address account;
bytes32 role;
bool grant;
uint256 scheduleTime;
bool exists;
}
mapping(address => PendingUpgrade) public pendingUpgrades;
address[] public upgradeQueue;
mapping(bytes32 => PendingRoleChange) public pendingRoleChanges;
uint256 public constant MIN_DELAY = 2 days;
address public immutable chainlinkKeeper;
uint256 public lastUpkeepTime;
uint256 public constant UPKEEP_COOLDOWN = 1 hours;
}
```
Why Written This Way:
Imports and Inheritance: Inherit from TimelockController for upgrade timelocks, AutomationCompatibleInterface for Chainlink integration, ReentrancyGuard for security, and Pausable for emergency controls. These are industry-standard libraries to reduce development time and ensure reliability.
Roles: Define ADMIN_ROLE for upgrade management and use DEFAULT_ADMIN_ROLE (inherited from TimelockController) for role and pause control, ensuring clear separation of privileges.
State Variables:
PendingUpgrade and pendingUpgrades track upgrades for multiple proxies, with upgradeQueue enabling iteration for automation.
PendingRoleChange and pendingRoleChanges manage timelocked role changes, using bytes32 operation IDs for uniqueness.
MIN_DELAY (2 days) ensures transparency for governance actions.
chainlinkKeeper (immutable) and lastUpkeepTime with UPKEEP_COOLDOWN (1 hour) manage automated upkeep securely.
Security Enforcements:
Immutable chainlinkKeeper prevents unauthorized upkeep calls.
ReentrancyGuard protects against reentrancy attacks.
Pausable allows halting operations in emergencies. Intended Purpose: Provide a robust foundation for governance, with state variables to track upgrades and role changes, and security features to protect the protocol.
Step 3: Implement Constructor
Objective: Initialize the contract with roles, timelock settings, and Chainlink Keeper configuration. Code Snippet:
```
constructor(
address[] memory proposers,
address[] memory executors,
address admin,
address _chainlinkKeeper
) TimelockController(MIN_DELAY, proposers, executors, admin) {
if (_chainlinkKeeper == address(0)) revert InvalidAddress();
chainlinkKeeper = _chainlinkKeeper;
_grantRole(ADMIN_ROLE, admin);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
```
Why Written This Way:
Parameters: Accept proposers and executors for TimelockController compatibility, admin for initial role assignment, and _chainlinkKeeper for automation.
Initialization: Pass MIN_DELAY to TimelockController to set the upgrade timelock. Grant ADMIN_ROLE and DEFAULT_ADMIN_ROLE to admin for full control.
Validation: Check for zero-address _chainlinkKeeper to prevent misconfiguration.
Immutable Keeper: Store chainlinkKeeper as immutable to save gas and ensure it cannot be changed post-deployment. Security Enforcements:
InvalidAddress error prevents deploying with an invalid keeper address.
Role assignment ensures only the admin can perform governance actions initially. Intended Purpose: Securely initialize the contract with governance roles and automation settings, ensuring a trusted admin and valid keeper.
Step 4: Implement Upgrade Management
Objective: Enable scheduling, canceling, and executing upgrades for multiple proxy contracts with timelock and automation support. Code Snippet (Key Functions):
```
function scheduleUpgrade(address target, address newImplementation) external onlyRole(ADMIN_ROLE) whenNotPaused {
if (target == address(0) || newImplementation == address(0)) revert InvalidAddress();
if (pendingUpgrades[target].exists) revert UpgradeAlreadyPending(target);
uint256 scheduledTime = block.timestamp + MIN_DELAY;
pendingUpgrades[target] = PendingUpgrade({
target: target,
newImplementation: newImplementation,
scheduleTime: scheduledTime,
exists: true
});
upgradeQueue.push(target);
emit UpgradeScheduled(target, newImplementation, scheduledTime, msg.sender);
}
function performUpgrade(address target) external onlyRole(ADMIN_ROLE) whenNotPaused {
_performUpgrade(target, false);
}
function _performUpgrade(address target, bool isAutomated) internal nonReentrant {
PendingUpgrade memory upgrade = pendingUpgrades[target];
if (!upgrade.exists) revert NoUpgradePending(target);
if (block.timestamp < upgrade.scheduleTime) revert UpgradeTooEarly(target);
delete pendingUpgrades[target];
for (uint256 i = 0; i < upgradeQueue.length; i++) {
if (upgradeQueue[i] == target) {
upgradeQueue[i] = upgradeQueue[upgradeQueue.length - 1];
upgradeQueue.pop();
break;
}
}
(bool success, bytes memory data) = target.call(
abi.encodeWithSignature("upgradeTo(address)", upgrade.newImplementation)
);
if (!success) revert UpgradeFailed(target, data);
emit UpgradeExecuted(target, upgrade.newImplementation, block.timestamp, msg.sender, isAutomated);
}
```
Why Written This Way:
scheduleUpgrade:
Validates inputs to prevent zero addresses (InvalidAddress) and duplicate upgrades (UpgradeAlreadyPending).
Stores upgrade details in pendingUpgrades and adds target to upgradeQueue for automation.
Uses block.timestamp + MIN_DELAY for a 2-day timelock
Emits UpgradeScheduled for transparency.
performUpgrade:
Public function for manual execution, delegating to _performUpgrade with isAutomated = false.
Restricted to ADMIN_ROLE and whenNotPaused.
_performUpgrade:
Internal function handling both manual and automated execution (isAutomated flag).
Verifies upgrade existence and timelock expiration.
Clears pendingUpgrades and removes from upgradeQueue using swap-and-pop for gas efficiency.
Uses a low-level call to upgradeTo for compatibility with standard proxy contracts (e.g., OpenZeppelin’s TransparentUpgradeableProxy).
Emits UpgradeExecuted with execution details. Security Enforcements:
onlyRole(ADMIN_ROLE) restricts upgrade operations to authorized users.
whenNotPaused prevents upgrades during emergencies.
nonReentrant in _performUpgrade prevents reentrancy attacks during low-level calls.
InvalidAddress, NoUpgradePending, UpgradeTooEarly, and UpgradeFailed errors ensure robust input validation and error handling. Intended Purpose: Enable secure, timelocked upgrades for multiple proxy contracts, with support for manual and automated execution, ensuring transparency and compatibility with Pasar’s marketplace contracts.
Step 5: Implement Chainlink Automation
Objective: Automate upgrade execution using Chainlink Keepers with a cooldown mechanism. Code Snippet:
```
function checkUpkeep(bytes calldata /* checkData */) external view override returns (bool upkeepNeeded, bytes memory performData) {
for (uint256 i = 0; i < upgradeQueue.length; i++) {
address target = upgradeQueue[i];
PendingUpgrade memory upgrade = pendingUpgrades[target];
if (upgrade.exists && block.timestamp >= upgrade.scheduleTime) {
return (true, abi.encode(target));
}
}
return (false, "");
}
function performUpkeep(bytes calldata performData) external override onlyChainlinkKeeper whenNotPaused {
if (block.timestamp < lastUpkeepTime + UPKEEP_COOLDOWN) revert UpkeepCooldownActive();
lastUpkeepTime = block.timestamp;
emit UpkeepPerformed(lastUpkeepTime);
address target = abi.decode(performData, (address));
_performUpgrade(target, true);
}
modifier onlyChainlinkKeeper() {
if (msg.sender != chainlinkKeeper) revert OnlyChainlinkKeeper();
_;
}
```
Why Written This Way:
checkUpkeep:
Iterates upgradeQueue to find a ready upgrade (exists and timelock expired).
Returns true and encodes the target address for performUpkeep.
Ignores checkData as it’s not needed for Pasar’s use case.
performUpkeep:
Restricted to chainlinkKeeper via onlyChainlinkKeeper modifier.
Enforces UPKEEP_COOLDOWN (1 hour) to prevent excessive executions.
Updates lastUpkeepTime and emits UpkeepPerformed for auditability.
Decodes target and calls _performUpgrade with isAutomated = true.
onlyChainlinkKeeper:
Ensures only the registered Chainlink Keeper can trigger automated upgrades. Security Enforcements:
onlyChainlinkKeeper prevents unauthorized upkeep calls.
whenNotPaused halts automation during emergencies.
UpkeepCooldownActive ensures a 1-hour gap between upkeeps, mitigating spam or abuse.
Encoding/decoding target ensures only valid upgrades are processed. Intended Purpose: Automate the execution of timelocked upgrades using Chainlink Keepers, reducing manual intervention while maintaining security and rate limiting.
Step 6: Implement Custom Role Management
Objective: Create a timelocked role management system without relying on TimelockController to avoid bytes memory to bytes calldata type mismatch issues. Code Snippet:
```
struct PendingRoleChange {
address account;
bytes32 role;
bool grant;
uint256 scheduleTime;
bool exists;
}
mapping(bytes32 => PendingRoleChange) public pendingRoleChanges;
function scheduleRoleChange(address account, bytes32 role, bool grant)
external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused {
if (account == address(0)) revert InvalidAddress();
bytes32 operationId = keccak256(
abi.encode(account, role, grant, block.timestamp)
);
pendingRoleChanges[operationId] = PendingRoleChange({
account: account,
role: role,
grant: grant,
scheduleTime: block.timestamp + MIN_DELAY,
exists: true
});
emit RoleChangeScheduled(account, role, grant, block.timestamp + MIN_DELAY, operationId);
}
function executeRoleChange(bytes32 operationId)
external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused {
PendingRoleChange memory change = pendingRoleChanges[operationId];
if (!change.exists) revert RoleChangeNotReady(operationId);
if (block.timestamp < change.scheduleTime) revert RoleChangeNotReady(operationId);
delete pendingRoleChanges[operationId];
if (change.grant) {
_grantRole(change.role, change.account);
} else {
_revokeRole(change.role, change.account);
}
emit RoleChangeExecuted(change.account, change.role, change.grant);
}
function cancelRoleChange(bytes32 operationId)
external onlyRole(DEFAULT_ADMIN_ROLE) whenNotPaused {
PendingRoleChange memory change = pendingRoleChanges[operationId];
if (!change.exists) revert RoleChangeNotReady(operationId);
delete pendingRoleChanges[operationId];
emit RoleChangeScheduled(change.account, change.role, change.grant, 0, operationId);
}
function isRoleChangeReady(bytes32 operationId) public view returns (bool ready) {
PendingRoleChange memory change = pendingRoleChanges[operationId];
return change.exists && block.timestamp >= change.scheduleTime;
}
```
Why Written This Way:
Custom Role Management: Initially, role management used TimelockController’s schedule and execute, but bytes memory to bytes calldata type mismatches caused compilation errors. The custom system uses a PendingRoleChange struct and pendingRoleChanges mapping to track timelocked role changes internally.
scheduleRoleChange:
Generates a unique operationId using keccak256(abi.encode(account, role, grant, block.timestamp)) to avoid collisions.
Stores role change details with a 2-day timelock (MIN_DELAY).
Emits RoleChangeScheduled for transparency.
executeRoleChange:
Verifies existence and timelock expiration.
Clears pendingRoleChanges before execution to prevent reentrancy.
Calls _grantRole or _revokeRole (inherited from TimelockController) to update roles.
Emits RoleChangeExecuted.
cancelRoleChange:
Allows canceling pending role changes, enhancing governance flexibility.
Emits RoleChangeScheduled with scheduleTime = 0 to indicate cancellation.
isRoleChangeReady:
Provides a view function for external tools to check timelock status. Security Enforcements:
onlyRole(DEFAULT_ADMIN_ROLE) restricts role management to trusted admins.
whenNotPaused prevents role changes during emergencies.
InvalidAddress ensures valid account inputs.
RoleChangeNotReady prevents execution of non-existent or premature operations.
Clearing pendingRoleChanges before role changes prevents reentrancy.
Unique operationId ensures no conflicts in role change tracking. Intended Purpose: Provide a secure, timelocked role management system that avoids type mismatch issues, supports granting/revoking roles, and allows cancellation for governance flexibility.
Step 7: Implement Pause Functionality
Objective: Enable emergency pausing to halt operations during critical issues. Code Snippet:
```
function pause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_pause();
}
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
```
Why Written This Way:
Simplicity: Use OpenZeppelin’s Pausable for battle-tested pause/unpause logic.
`Access Control: Restrict to DEFAULT_ADMIN_ROLE to prevent unauthorized pausing.`
Integration: The whenNotPaused modifier is applied to all state-changing functions (scheduleUpgrade, performUpgrade, scheduleRoleChange, etc.) to halt operations when paused. Security Enforcements:
`onlyRole(DEFAULT_ADMIN_ROLE) ensures only trusted admins can pause/unpause.`
whenNotPaused modifier protects against unauthorized actions during a pause. Intended Purpose: Provide an emergency stop mechanism to halt upgrades and role changes in response to vulnerabilities or governance decisions, ensuring protocol safety.
Step 8: Implement Events and Errors
Objective: Enhance auditability and error handling for transparency and debugging. Code Snippet:
```
error InvalidAddress();
error UpgradeAlreadyPending(address target);
error NoUpgradePending(address target);
error UpgradeTooEarly(address target);
error UpgradeFailed(address target, bytes data);
error OnlyChainlinkKeeper();
error UpkeepCooldownActive();
error RoleChangeNotReady(bytes32 operationId);
event UpgradeScheduled(address indexed target, address indexed newImplementation, uint256 scheduleTime, address indexed caller);
event UpgradeExecuted(address indexed target, address indexed newImplementation, uint256 executedAt, address indexed caller, bool isAutomated);
event UpgradeCancelled(address indexed target, address indexed newImplementation, address indexed caller);
event RoleChangeScheduled(address indexed account, bytes32 indexed role, bool grant, uint256 scheduleTime, bytes32 operationId);
event RoleChangeExecuted(address indexed account, bytes32 indexed role, bool grant);
event UpkeepPerformed(uint256 lastUpkeepTime);
event ChainlinkKeeperUpdated(address indexed keeper);
```
Why Written This Way:
Custom Errors: Use Solidity custom errors for gas-efficient, descriptive error handling. Each error targets a specific failure case (e.g., InvalidAddress, RoleChangeNotReady) to aid debugging.
> **Events: Emit detailed events for all significant actions:**
UpgradeScheduled, UpgradeExecuted, and UpgradeCancelled track upgrade lifecycle.
RoleChangeScheduled and RoleChangeExecuted track role changes, with scheduleTime = 0 for cancellations.
UpkeepPerformed logs automation activity.
ChainlinkKeeperUpdated is reserved for future keeper updates.
Indexed Parameters: Use indexed for key fields (e.g., target, account, role) to enable efficient event filtering off-chain. Security Enforcements:
> Errors provide clear feedback for invalid operations, preventing silent failures.
Events ensure all governance actions are auditable, supporting transparency in Pasar’s decentralized marketplace. Intended Purpose: Provide robust error handling and event logging to facilitate debugging, monitoring, and governance transparency.
Step 9: Add Monitoring Functions
Objective: Enable off-chain tools to monitor pending upgrades and role changes. Code Snippet:
```
function getUpgradeQueue() external view returns (address[] memory) {
return upgradeQueue;
}
function isRoleChangeReady(bytes32 operationId) public view returns (bool ready) {
PendingRoleChange memory change = pendingRoleChanges[operationId];
return change.exists && block.timestamp >= change.scheduleTime;
}
```
Why Written This Way:
> getUpgradeQueue: Returns the upgradeQueue array to list all proxies with pending upgrades, useful for monitoring and automation.
isRoleChangeReady: Checks if a role change is ready, enabling external tools to track timelock status.
View Functions: Both are view to minimize gas costs and allow off-chain queries. Security Enforcements:
No state changes ensure these functions are safe for public access.
Transparent state access supports decentralized governance monitoring. Intended Purpose: Provide visibility into pending upgrades and role changes, enabling off-chain tools and users to monitor Pasar’s governance state.
Step 10: Address Compilation Issues
Objective: Resolve bytes memory to bytes calldata type mismatch errors encountered in early role management attempts. Development Process:
Initial Approach: Used TimelockController’s schedule, execute, and hashOperation for role changes, encoding grantRole/revokeRole calls with abi.encodeWithSignature.
Issue: Compilation errors (Error 9553) due to passing bytes memory (from abi.encodeWithSignature) to functions expecting bytes calldata.
Attempts:
> Tried helper functions (_scheduleRoleChange, _executeRoleChange) to encapsulate calls, but hashOperation still caused errors.
Considered converting bytes memory to bytes calldata via assembly, but this was complex and error-prone.
> Solution: Replaced TimelockController-based role management with a custom system using PendingRoleChange and pendingRoleChanges, directly calling _grantRole/_revokeRole after timelock verification. Why Custom Role Management:
>
Avoids bytes memory to bytes calldata issues by eliminating TimelockController calls for roles.
Simplifies logic and reduces gas costs by using internal role change execution.
Adds cancelRoleChange for flexibility, not possible with TimelockController. Security Enforcements:
Custom system retains 2-day timelock, onlyRole(DEFAULT_ADMIN_ROLE), and reentrancy protection.
Unique operationId ensures no conflicts in role change tracking. Intended Purpose: Ensure robust, error-free role management that aligns with Pasar’s governance needs while maintaining security and simplicity.
Security Enforcements Summary
Access Control: onlyRole(ADMIN_ROLE) for upgrades, onlyRole(DEFAULT_ADMIN_ROLE) for role management and pausing, onlyChainlinkKeeper for automation.
Timelock: 2-day MIN_DELAY for upgrades and role changes ensures transparency and time for community review.
Reentrancy Protection: nonReentrant modifier in _performUpgrade and clearing pendingRoleChanges before role changes prevent reentrancy attacks.
Pausability: whenNotPaused modifier halts operations during emergencies, with pause/unpause restricted to DEFAULT_ADMIN_ROLE.
Input Validation: Custom errors (InvalidAddress, UpgradeAlreadyPending, etc.) ensure robust input checks.
Cooldown: UPKEEP_COOLDOWN (1 hour) prevents excessive automated upkeep calls.
Event Logging: Comprehensive events enable auditing of all governance actions.
Intended Functionality
Upgrade Management: Securely upgrade multiple proxy contracts (e.g., for Pasar’s marketplace logic) with a timelock, supporting manual and automated execution.
Role Management: Grant or revoke roles (ADMIN_ROLE, DEFAULT_ADMIN_ROLE) with a 2-day timelock, allowing cancellation for governance flexibility.
Automation: Use Chainlink Keepers to execute upgrades automatically, reducing manual intervention.
Security: Protect the protocol with access controls, timelocks, reentrancy protection, and pausability.
Transparency: Provide events and view functions for monitoring governance actions, aligning with Pasar’s decentralized ethos.
Testing Recommendations
Upgrade Tests:
Test scheduling, executing, and canceling upgrades for multiple proxies.
Verify timelock enforcement and automation via checkUpkeep/performUpkeep.
Test error cases (e.g., zero addresses, premature execution).
Role Management Tests:
Test scheduling, executing, and canceling role changes.
Verify operationId uniqueness and timelock enforcement.
Test edge cases (e.g., non-existent operationId, duplicate schedules).
Security Tests:
Test access control restrictions (onlyRole, onlyChainlinkKeeper).
Test pausing/unpausing and reentrancy scenarios.
Verify cooldown enforcement for upkeep.
Tools: Use Hardhat or Foundry with mock proxy contracts and Chainlink Keeper simulations.
Future Improvements
Automated Role Changes: Extend checkUpkeep/performUpkeep to process pendingRoleChanges.
Queue Optimization: Replace upgradeQueue with a doubly-linked list for gas efficiency with large queues.
Monitoring: Add getPendingRoleChanges to list all pending role changes.
Configurability: Allow governance to adjust MIN_DELAY or UPKEEP_COOLDOWN via timelocked proposals.
Conclusion
The PasarAdmin.sol contract was developed to provide secure, transparent, and decentralized governance for the Pasar protocol. By combining TimelockController for upgrades, a custom role management system, Chainlink Automation, and robust security features, it ensures Pasar’s marketplace contracts can be upgraded and administered securely. This documentation captures the iterative design process, addressing challenges like type mismatches and ensuring alignment with Pasar’s goal of a trustless, global marketplace.