From f27fadd05a99c3da2213f98b3f68147ed1bfb197 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Wed, 29 Oct 2025 02:16:39 +0300 Subject: [PATCH 1/4] script --- script/RevokeRecallRole.s.sol | 374 ++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 script/RevokeRecallRole.s.sol diff --git a/script/RevokeRecallRole.s.sol b/script/RevokeRecallRole.s.sol new file mode 100644 index 0000000..4ca1756 --- /dev/null +++ b/script/RevokeRecallRole.s.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.26; + +import {Recall} from "../src/token/Recall.sol"; +import {Script, console} from "forge-std/Script.sol"; + +/** + * @title RevokeRecallRole Script + * @notice Forge script to revoke roles from accounts on a deployed Recall contract + * @dev This script provides multiple ways to revoke roles: + * 1. Single role revocation from one account + * 2. Batch revocation of multiple roles from one account + * 3. Revocation of one role from multiple accounts + * + * USAGE EXAMPLES: + * + * 1. Using environment variables: + * PROXY_ADDR=0x... ROLE_TYPE=MINTER ACCOUNT=0x... \ + * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --private-key $PRIVATE_KEY + * + * 2. Using function parameters: + * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --private-key $PRIVATE_KEY \ + * -s "run(address,string,address)" \ + * 0xPROXY_ADDRESS "MINTER" 0xACCOUNT_ADDRESS + * + * 3. Batch revoke multiple roles from one account: + * forge script script/RevokeRecallRole.s.sol:BatchRevokeScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --private-key $PRIVATE_KEY + * + * 4. Revoke one role from multiple accounts: + * forge script script/RevokeRecallRole.s.sol:MultiAccountRevokeScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --private-key $PRIVATE_KEY + * + * ENVIRONMENT VARIABLES: + * - PROXY_ADDR: Address of the deployed Recall proxy contract (required) + * - ROLE_TYPE: Role to revoke - "ADMIN", "MINTER", or "PAUSER" (required for single revoke) + * - ACCOUNT: Address to revoke the role from (required for single revoke) + * - ALLOW_SELF_ADMIN_REVOKE: Set to "true" to allow revoking ADMIN_ROLE from yourself (default: false) + * - PRIVATE_KEY: Private key of account with ADMIN_ROLE (required for broadcast) + * + * SAFETY FEATURES: + * - Verifies caller has ADMIN_ROLE before attempting revocation + * - Checks if target account has the role before revoking + * - Prevents accidental self-revocation of ADMIN_ROLE (unless explicitly allowed) + * - Displays role status before and after revocation + */ +contract RevokeRoleScript is Script { + // Custom errors + error InvalidRoleType(string roleType); + error CallerNotAdmin(address caller); + error AccountDoesNotHaveRole(address account, bytes32 role); + error CannotRevokeSelfAdmin(); + error InvalidProxyAddress(); + error InvalidAccountAddress(); + + /** + * @notice Main entry point using environment variables + * @dev Reads PROXY_ADDR, ROLE_TYPE, and ACCOUNT from environment + */ + function run() public { + address proxyAddr = vm.envAddress("PROXY_ADDR"); + string memory roleType = vm.envString("ROLE_TYPE"); + address account = vm.envAddress("ACCOUNT"); + + run(proxyAddr, roleType, account); + } + + /** + * @notice Main entry point with direct parameters + * @param proxyAddr Address of the deployed Recall proxy contract + * @param roleType Role to revoke: "ADMIN", "MINTER", or "PAUSER" + * @param account Address to revoke the role from + */ + function run(address proxyAddr, string memory roleType, address account) public { + // Validate inputs + if (proxyAddr == address(0)) revert InvalidProxyAddress(); + if (account == address(0)) revert InvalidAccountAddress(); + + Recall recall = Recall(proxyAddr); + bytes32 role = getRoleHash(recall, roleType); + + console.log("=== Recall Role Revocation ==="); + console.log("Proxy Address:", proxyAddr); + console.log("Role Type:", roleType); + console.log("Account:", account); + console.log("Caller:", msg.sender); + console.log(""); + + // Check caller has ADMIN_ROLE + if (!recall.hasRole(recall.ADMIN_ROLE(), msg.sender)) { + revert CallerNotAdmin(msg.sender); + } + console.log("✓ Caller has ADMIN_ROLE"); + + // Check if account has the role + if (!recall.hasRole(role, account)) { + revert AccountDoesNotHaveRole(account, role); + } + console.log("✓ Account has the role"); + + // Safety check: prevent self-revocation of ADMIN_ROLE unless explicitly allowed + if (role == recall.ADMIN_ROLE() && account == msg.sender) { + bool allowSelfRevoke = vm.envOr("ALLOW_SELF_ADMIN_REVOKE", false); + if (!allowSelfRevoke) { + revert CannotRevokeSelfAdmin(); + } + console.log("⚠ WARNING: Revoking ADMIN_ROLE from yourself!"); + } + + // Display current role status + console.log(""); + console.log("Current Role Status:"); + displayRoleStatus(recall, account); + + // Perform revocation + vm.startBroadcast(); + recall.revokeRole(role, account); + vm.stopBroadcast(); + + console.log(""); + console.log("✓ Role revoked successfully"); + console.log(""); + console.log("Updated Role Status:"); + displayRoleStatus(recall, account); + } + + /** + * @notice Get the bytes32 hash for a role type string + * @param recall The Recall contract instance + * @param roleType Role type string: "ADMIN", "MINTER", or "PAUSER" + * @return The bytes32 role hash + */ + function getRoleHash(Recall recall, string memory roleType) internal view returns (bytes32) { + bytes32 roleHash = keccak256(bytes(roleType)); + + if (roleHash == keccak256("ADMIN")) { + return recall.ADMIN_ROLE(); + } else if (roleHash == keccak256("MINTER")) { + return recall.MINTER_ROLE(); + } else if (roleHash == keccak256("PAUSER")) { + return recall.PAUSER_ROLE(); + } else { + revert InvalidRoleType(roleType); + } + } + + /** + * @notice Display the role status for an account + * @param recall The Recall contract instance + * @param account The account to check + */ + function displayRoleStatus(Recall recall, address account) internal view { + bool hasAdmin = recall.hasRole(recall.ADMIN_ROLE(), account); + bool hasMinter = recall.hasRole(recall.MINTER_ROLE(), account); + bool hasPauser = recall.hasRole(recall.PAUSER_ROLE(), account); + + console.log(" ADMIN_ROLE:", hasAdmin ? "YES" : "NO"); + console.log(" MINTER_ROLE:", hasMinter ? "YES" : "NO"); + console.log(" PAUSER_ROLE:", hasPauser ? "YES" : "NO"); + } +} + +/** + * @title BatchRevokeScript + * @notice Script to revoke multiple roles from a single account + * @dev Environment variables: + * - PROXY_ADDR: Recall proxy address + * - ACCOUNT: Account to revoke roles from + * - REVOKE_ADMIN: "true" to revoke ADMIN_ROLE (optional) + * - REVOKE_MINTER: "true" to revoke MINTER_ROLE (optional) + * - REVOKE_PAUSER: "true" to revoke PAUSER_ROLE (optional) + */ +contract BatchRevokeScript is Script { + function run() public { + address proxyAddr = vm.envAddress("PROXY_ADDR"); + address account = vm.envAddress("ACCOUNT"); + + bool revokeAdmin = vm.envOr("REVOKE_ADMIN", false); + bool revokeMinter = vm.envOr("REVOKE_MINTER", false); + bool revokePauser = vm.envOr("REVOKE_PAUSER", false); + + Recall recall = Recall(proxyAddr); + + console.log("=== Batch Role Revocation ==="); + console.log("Proxy Address:", proxyAddr); + console.log("Account:", account); + console.log("Caller:", msg.sender); + console.log(""); + console.log("Roles to revoke:"); + console.log(" ADMIN_ROLE:", revokeAdmin ? "YES" : "NO"); + console.log(" MINTER_ROLE:", revokeMinter ? "YES" : "NO"); + console.log(" PAUSER_ROLE:", revokePauser ? "YES" : "NO"); + console.log(""); + + // Display current status + console.log("Current Role Status:"); + displayRoleStatus(recall, account); + + vm.startBroadcast(); + + if (revokeAdmin && recall.hasRole(recall.ADMIN_ROLE(), account)) { + if (account == msg.sender) { + bool allowSelfRevoke = vm.envOr("ALLOW_SELF_ADMIN_REVOKE", false); + require(allowSelfRevoke, "Cannot revoke ADMIN_ROLE from yourself"); + } + recall.revokeRole(recall.ADMIN_ROLE(), account); + console.log("✓ Revoked ADMIN_ROLE"); + } + + if (revokeMinter && recall.hasRole(recall.MINTER_ROLE(), account)) { + recall.revokeRole(recall.MINTER_ROLE(), account); + console.log("✓ Revoked MINTER_ROLE"); + } + + if (revokePauser && recall.hasRole(recall.PAUSER_ROLE(), account)) { + recall.revokeRole(recall.PAUSER_ROLE(), account); + console.log("✓ Revoked PAUSER_ROLE"); + } + + vm.stopBroadcast(); + + console.log(""); + console.log("Updated Role Status:"); + displayRoleStatus(recall, account); + } + + function displayRoleStatus(Recall recall, address account) internal view { + bool hasAdmin = recall.hasRole(recall.ADMIN_ROLE(), account); + bool hasMinter = recall.hasRole(recall.MINTER_ROLE(), account); + bool hasPauser = recall.hasRole(recall.PAUSER_ROLE(), account); + + console.log(" ADMIN_ROLE:", hasAdmin ? "YES" : "NO"); + console.log(" MINTER_ROLE:", hasMinter ? "YES" : "NO"); + console.log(" PAUSER_ROLE:", hasPauser ? "YES" : "NO"); + } +} + +/** + * @title MultiAccountRevokeScript + * @notice Script to revoke a single role from multiple accounts + * @dev Environment variables: + * - PROXY_ADDR: Recall proxy address + * - ROLE_TYPE: Role to revoke ("ADMIN", "MINTER", or "PAUSER") + * - ACCOUNTS: Comma-separated list of addresses (e.g., "0x123...,0x456...,0x789...") + */ +contract MultiAccountRevokeScript is Script { + function run() public { + address proxyAddr = vm.envAddress("PROXY_ADDR"); + string memory roleType = vm.envString("ROLE_TYPE"); + string memory accountsStr = vm.envString("ACCOUNTS"); + + Recall recall = Recall(proxyAddr); + bytes32 role = getRoleHash(recall, roleType); + + console.log("=== Multi-Account Role Revocation ==="); + console.log("Proxy Address:", proxyAddr); + console.log("Role Type:", roleType); + console.log("Caller:", msg.sender); + console.log(""); + + // Parse comma-separated addresses + address[] memory accounts = parseAddresses(accountsStr); + console.log("Number of accounts:", accounts.length); + console.log(""); + + vm.startBroadcast(); + + for (uint256 i = 0; i < accounts.length; i++) { + address account = accounts[i]; + console.log("Processing account", i + 1, ":", account); + + if (recall.hasRole(role, account)) { + // Safety check for self-admin revocation + if (role == recall.ADMIN_ROLE() && account == msg.sender) { + bool allowSelfRevoke = vm.envOr("ALLOW_SELF_ADMIN_REVOKE", false); + if (!allowSelfRevoke) { + console.log(" ⚠ Skipping: Cannot revoke ADMIN_ROLE from yourself"); + continue; + } + } + + recall.revokeRole(role, account); + console.log(" ✓ Role revoked"); + } else { + console.log(" ⚠ Skipping: Account does not have the role"); + } + } + + vm.stopBroadcast(); + console.log(""); + console.log("✓ Multi-account revocation completed"); + } + + function getRoleHash(Recall recall, string memory roleType) internal view returns (bytes32) { + bytes32 roleHash = keccak256(bytes(roleType)); + + if (roleHash == keccak256("ADMIN")) { + return recall.ADMIN_ROLE(); + } else if (roleHash == keccak256("MINTER")) { + return recall.MINTER_ROLE(); + } else if (roleHash == keccak256("PAUSER")) { + return recall.PAUSER_ROLE(); + } else { + revert("Invalid role type"); + } + } + + function parseAddresses(string memory addressesStr) internal pure returns (address[] memory) { + // Simple parser for comma-separated addresses + // Count commas to determine array size + bytes memory strBytes = bytes(addressesStr); + uint256 count = 1; + for (uint256 i = 0; i < strBytes.length; i++) { + if (strBytes[i] == ",") { + count++; + } + } + + address[] memory addresses = new address[](count); + uint256 index = 0; + uint256 start = 0; + + for (uint256 i = 0; i <= strBytes.length; i++) { + if (i == strBytes.length || strBytes[i] == ",") { + // Extract substring + bytes memory addrBytes = new bytes(i - start); + for (uint256 j = 0; j < i - start; j++) { + addrBytes[j] = strBytes[start + j]; + } + + // Trim whitespace and convert to address + string memory addrStr = string(addrBytes); + addresses[index] = vm.parseAddress(trim(addrStr)); + index++; + start = i + 1; + } + } + + return addresses; + } + + function trim(string memory str) internal pure returns (string memory) { + bytes memory strBytes = bytes(str); + uint256 start = 0; + uint256 end = strBytes.length; + + // Trim leading whitespace + while (start < end && (strBytes[start] == " " || strBytes[start] == "\t")) { + start++; + } + + // Trim trailing whitespace + while (end > start && (strBytes[end - 1] == " " || strBytes[end - 1] == "\t")) { + end--; + } + + bytes memory trimmed = new bytes(end - start); + for (uint256 i = 0; i < end - start; i++) { + trimmed[i] = strBytes[start + i]; + } + + return string(trimmed); + } +} \ No newline at end of file From e27d4cd64b089363c5987055c8093e93accf1ae8 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Wed, 29 Oct 2025 02:18:23 +0300 Subject: [PATCH 2/4] readme --- script/RevokeRecallRole.README.md | 327 ++++++++++++++++++++++++++++++ script/RevokeRecallRole.s.sol | 59 +++++- 2 files changed, 378 insertions(+), 8 deletions(-) create mode 100644 script/RevokeRecallRole.README.md diff --git a/script/RevokeRecallRole.README.md b/script/RevokeRecallRole.README.md new file mode 100644 index 0000000..190b5ef --- /dev/null +++ b/script/RevokeRecallRole.README.md @@ -0,0 +1,327 @@ +# Recall Role Revocation Script + +This directory contains Forge scripts for revoking roles on deployed Recall token contracts. + +## Overview + +The `RevokeRecallRole.s.sol` script provides three different contracts for role management: + +1. **RevokeRoleScript** - Revoke a single role from one account +2. **BatchRevokeScript** - Revoke multiple roles from one account +3. **MultiAccountRevokeScript** - Revoke one role from multiple accounts + +## Prerequisites + +- Foundry installed and configured +- Access to an account with `ADMIN_ROLE` on the Recall contract +- The deployed Recall proxy contract address + +## Available Roles + +The Recall contract has three roles: +- `ADMIN` - Can authorize upgrades, unpause, and manage other roles +- `MINTER` - Can mint new tokens +- `PAUSER` - Can pause the contract + +## Usage + +### 1. Single Role Revocation + +Revoke one role from one account. + +#### With Private Key + +```bash +PROXY_ADDR=0x1234... \ +ROLE_TYPE=MINTER \ +ACCOUNT=0x5678... \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --private-key $PRIVATE_KEY +``` + +#### With Ledger Hardware Wallet + +```bash +PROXY_ADDR=0x1234... \ +ROLE_TYPE=MINTER \ +ACCOUNT=0x5678... \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +**Ledger Setup:** +1. Connect your Ledger device via USB +2. Unlock the device with your PIN +3. Open the Ethereum app on your Ledger +4. Run the command above +5. Review and confirm the transaction on your Ledger device + +#### With Trezor Hardware Wallet + +```bash +PROXY_ADDR=0x1234... \ +ROLE_TYPE=MINTER \ +ACCOUNT=0x5678... \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --trezor \ + --sender 0xYourTrezorAddress +``` + +#### Using Function Parameters (Alternative) + +Instead of environment variables, you can pass parameters directly: + +```bash +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress \ + -s "run(address,string,address)" \ + 0xProxyAddress "MINTER" 0xAccountToRevoke +``` + +### 2. Batch Role Revocation + +Revoke multiple roles from a single account in one transaction. + +```bash +PROXY_ADDR=0x1234... \ +ACCOUNT=0x5678... \ +REVOKE_ADMIN=false \ +REVOKE_MINTER=true \ +REVOKE_PAUSER=true \ +forge script script/RevokeRecallRole.s.sol:BatchRevokeScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +### 3. Multi-Account Role Revocation + +Revoke one role from multiple accounts. + +```bash +PROXY_ADDR=0x1234... \ +ROLE_TYPE=MINTER \ +ACCOUNTS="0x5678...,0x9abc...,0xdef0..." \ +forge script script/RevokeRecallRole.s.sol:MultiAccountRevokeScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +## Hardware Wallet Configuration + +### Custom HD Derivation Path + +If you need to use a non-standard derivation path: + +```bash +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --hd-paths "m/44'/60'/0'/0/1" \ + --sender 0xYourLedgerAddress +``` + +### Multiple Ledger Accounts + +To use a specific account from your Ledger: + +```bash +# List available accounts +cast wallet list --ledger + +# Use specific account +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --hd-paths "m/44'/60'/0'/0/2" \ + --sender 0xYourSpecificAddress +``` + +## Dry Run (Simulation) + +Test the script without broadcasting transactions by omitting the `--broadcast` flag: + +```bash +PROXY_ADDR=0x1234... \ +ROLE_TYPE=MINTER \ +ACCOUNT=0x5678... \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +This will simulate the transaction and show you what would happen without actually executing it. + +## Safety Features + +### 1. Admin Role Self-Revocation Protection + +By default, the script prevents you from revoking your own `ADMIN_ROLE` to avoid locking yourself out. To override this: + +```bash +ALLOW_SELF_ADMIN_REVOKE=true \ +PROXY_ADDR=0x1234... \ +ROLE_TYPE=ADMIN \ +ACCOUNT=0xYourOwnAddress \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://your-rpc-url \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +### 2. Pre-flight Checks + +The script performs several checks before executing: +- Verifies the caller has `ADMIN_ROLE` +- Confirms the target account has the role to be revoked +- Displays current role status before and after revocation +- Prevents self-admin revocation (unless explicitly allowed) + +### 3. Verbose Logging + +All scripts provide detailed console output showing: +- Contract addresses +- Role types and accounts +- Current role status +- Transaction results +- Updated role status + +## Environment Variables Reference + +| Variable | Required | Description | Example | +|----------|----------|-------------|---------| +| `PROXY_ADDR` | Yes | Recall proxy contract address | `0x1234...` | +| `ROLE_TYPE` | Yes* | Role to revoke: ADMIN, MINTER, or PAUSER | `MINTER` | +| `ACCOUNT` | Yes* | Address to revoke role from | `0x5678...` | +| `ACCOUNTS` | Yes** | Comma-separated addresses | `0x123...,0x456...` | +| `REVOKE_ADMIN` | No | Revoke ADMIN_ROLE (batch only) | `true` or `false` | +| `REVOKE_MINTER` | No | Revoke MINTER_ROLE (batch only) | `true` or `false` | +| `REVOKE_PAUSER` | No | Revoke PAUSER_ROLE (batch only) | `true` or `false` | +| `ALLOW_SELF_ADMIN_REVOKE` | No | Allow self-admin revocation | `true` or `false` | +| `PRIVATE_KEY` | No*** | Private key (if not using hardware wallet) | `0xabc...` | + +\* Required for `RevokeRoleScript` +\** Required for `MultiAccountRevokeScript` +\*** Not needed when using `--ledger` or `--trezor` + +## Troubleshooting + +### Ledger Not Detected + +```bash +# Check if Ledger is connected +cast wallet list --ledger + +# If not detected, try: +# 1. Reconnect the device +# 2. Unlock with PIN +# 3. Open Ethereum app +# 4. Enable "Contract data" in Ethereum app settings +``` + +### Transaction Rejected on Device + +Make sure: +1. The Ethereum app is open (not Bitcoin or another app) +2. "Contract data" is enabled in the Ethereum app settings +3. You're using the correct derivation path +4. The device firmware is up to date + +### "Caller Not Admin" Error + +The address you're using doesn't have `ADMIN_ROLE`. Verify: +```bash +cast call $PROXY_ADDR "hasRole(bytes32,address)(bool)" \ + $(cast keccak "ADMIN_ROLE") \ + $YOUR_ADDRESS \ + --rpc-url $RPC_URL +``` + +### "Account Does Not Have Role" Error + +The target account doesn't have the role you're trying to revoke. Check current roles: +```bash +cast call $PROXY_ADDR "hasRole(bytes32,address)(bool)" \ + $(cast keccak "MINTER_ROLE") \ + $TARGET_ADDRESS \ + --rpc-url $RPC_URL +``` + +## Examples + +### Example 1: Remove Minter from Old Admin + +```bash +# Using Ledger +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \ +ROLE_TYPE=MINTER \ +ACCOUNT=0x1234567890123456789012345678901234567890 \ +forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + --rpc-url https://api.calibration.node.glif.io/rpc/v1 \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +### Example 2: Remove All Roles from Compromised Account + +```bash +# Using Ledger +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \ +ACCOUNT=0x1234567890123456789012345678901234567890 \ +REVOKE_ADMIN=true \ +REVOKE_MINTER=true \ +REVOKE_PAUSER=true \ +forge script script/RevokeRecallRole.s.sol:BatchRevokeScript \ + --rpc-url https://api.calibration.node.glif.io/rpc/v1 \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +### Example 3: Revoke Minter from Multiple Test Accounts + +```bash +# Using Ledger +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \ +ROLE_TYPE=MINTER \ +ACCOUNTS="0x1111...,0x2222...,0x3333..." \ +forge script script/RevokeRecallRole.s.sol:MultiAccountRevokeScript \ + --rpc-url https://api.calibration.node.glif.io/rpc/v1 \ + --broadcast \ + --ledger \ + --sender 0xYourLedgerAddress +``` + +## Security Best Practices + +1. **Always dry run first** - Test without `--broadcast` to verify behavior +2. **Use hardware wallets** - Ledger/Trezor provide better security than private keys +3. **Verify addresses** - Double-check all addresses before broadcasting +4. **Keep admin access** - Don't revoke all admin roles without a replacement +5. **Document changes** - Keep records of role changes for audit purposes +6. **Test on testnet** - Try on testnet before mainnet operations + +## Support + +For issues or questions: +- Check the [Foundry documentation](https://book.getfoundry.sh/) +- Review the [Ledger Ethereum app guide](https://support.ledger.com/hc/en-us/articles/360009576554) +- Examine the script source code for detailed comments \ No newline at end of file diff --git a/script/RevokeRecallRole.s.sol b/script/RevokeRecallRole.s.sol index 4ca1756..c32d3a9 100644 --- a/script/RevokeRecallRole.s.sol +++ b/script/RevokeRecallRole.s.sol @@ -14,39 +14,82 @@ import {Script, console} from "forge-std/Script.sol"; * * USAGE EXAMPLES: * - * 1. Using environment variables: + * 1. Using environment variables with private key: * PROXY_ADDR=0x... ROLE_TYPE=MINTER ACCOUNT=0x... \ * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ * --rpc-url $RPC_URL \ * --broadcast \ * --private-key $PRIVATE_KEY * - * 2. Using function parameters: + * 2. Using Ledger hardware wallet: + * PROXY_ADDR=0x... ROLE_TYPE=MINTER ACCOUNT=0x... \ + * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --ledger \ + * --sender 0xYOUR_LEDGER_ADDRESS + * + * Note: You can also specify the HD derivation path: + * --ledger --hd-paths "m/44'/60'/0'/0/0" + * + * 3. Using Trezor hardware wallet: + * PROXY_ADDR=0x... ROLE_TYPE=MINTER ACCOUNT=0x... \ * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ * --rpc-url $RPC_URL \ * --broadcast \ - * --private-key $PRIVATE_KEY \ + * --trezor \ + * --sender 0xYOUR_TREZOR_ADDRESS + * + * 4. Using function parameters with Ledger: + * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --ledger \ + * --sender 0xYOUR_LEDGER_ADDRESS \ * -s "run(address,string,address)" \ * 0xPROXY_ADDRESS "MINTER" 0xACCOUNT_ADDRESS * - * 3. Batch revoke multiple roles from one account: + * 5. Batch revoke multiple roles from one account (with Ledger): * forge script script/RevokeRecallRole.s.sol:BatchRevokeScript \ * --rpc-url $RPC_URL \ * --broadcast \ - * --private-key $PRIVATE_KEY + * --ledger \ + * --sender 0xYOUR_LEDGER_ADDRESS * - * 4. Revoke one role from multiple accounts: + * 6. Revoke one role from multiple accounts (with Ledger): * forge script script/RevokeRecallRole.s.sol:MultiAccountRevokeScript \ * --rpc-url $RPC_URL \ * --broadcast \ - * --private-key $PRIVATE_KEY + * --ledger \ + * --sender 0xYOUR_LEDGER_ADDRESS + * + * 7. Dry run (simulate without broadcasting) with Ledger: + * PROXY_ADDR=0x... ROLE_TYPE=MINTER ACCOUNT=0x... \ + * forge script script/RevokeRecallRole.s.sol:RevokeRoleScript \ + * --rpc-url $RPC_URL \ + * --ledger \ + * --sender 0xYOUR_LEDGER_ADDRESS + * (Note: omit --broadcast flag for simulation only) * * ENVIRONMENT VARIABLES: * - PROXY_ADDR: Address of the deployed Recall proxy contract (required) * - ROLE_TYPE: Role to revoke - "ADMIN", "MINTER", or "PAUSER" (required for single revoke) * - ACCOUNT: Address to revoke the role from (required for single revoke) * - ALLOW_SELF_ADMIN_REVOKE: Set to "true" to allow revoking ADMIN_ROLE from yourself (default: false) - * - PRIVATE_KEY: Private key of account with ADMIN_ROLE (required for broadcast) + * - PRIVATE_KEY: Private key of account with ADMIN_ROLE (only if not using hardware wallet) + * + * HARDWARE WALLET SUPPORT: + * This script fully supports hardware wallets (Ledger, Trezor) via Foundry's built-in flags: + * - --ledger: Use Ledger hardware wallet + * - --trezor: Use Trezor hardware wallet + * - --sender: Specify the address from your hardware wallet (required with --ledger/--trezor) + * - --hd-paths: Specify custom HD derivation path (optional, default: "m/44'/60'/0'/0/0") + * + * When using a hardware wallet: + * 1. Connect your device and unlock it + * 2. Open the Ethereum app on your device + * 3. Run the script with --ledger or --trezor flag + * 4. Confirm the transaction on your device when prompted * * SAFETY FEATURES: * - Verifies caller has ADMIN_ROLE before attempting revocation From b00444ca7a35954242dfa6595523f1843169dd81 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Wed, 29 Oct 2025 02:44:59 +0300 Subject: [PATCH 3/4] Justfile --- .env.example | 91 ++++++++++++++ JUSTFILE_USAGE.md | 248 ++++++++++++++++++++++++++++++++++++++ justfile | 294 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 633 insertions(+) create mode 100644 .env.example create mode 100644 JUSTFILE_USAGE.md create mode 100644 justfile diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5edb754 --- /dev/null +++ b/.env.example @@ -0,0 +1,91 @@ +# Recall Role Revocation Configuration +# Copy this file to .env and fill in your values + +# ============================================ +# REQUIRED: Contract and Network Configuration +# ============================================ + +# Address of the deployed Recall proxy contract +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb + +# RPC URL for the network +RPC_URL=https://api.calibration.node.glif.io/rpc/v1 + +# ============================================ +# REQUIRED: Authentication Method (choose one) +# ============================================ + +# Option 1: Use Ledger Hardware Wallet (RECOMMENDED) +USE_LEDGER=true +SENDER=0xYourLedgerAddress +# Optional: Custom HD derivation path (default: m/44'/60'/0'/0/0) +# HD_PATH=m/44'/60'/0'/0/1 + +# Option 2: Use Trezor Hardware Wallet +# USE_TREZOR=true +# SENDER=0xYourTrezorAddress + +# Option 3: Use Private Key (NOT RECOMMENDED for production) +# PRIVATE_KEY=0xYourPrivateKeyHere + +# ============================================ +# REQUIRED: Role Revocation Parameters +# ============================================ + +# For single role revocation (just revoke-role) +ROLE_TYPE=MINTER +ACCOUNT=0xAccountToRevokeFrom + +# For batch revocation (just revoke-batch) +# REVOKE_ADMIN=false +# REVOKE_MINTER=true +# REVOKE_PAUSER=true + +# For multi-account revocation (just revoke-multi) +# ACCOUNTS=0x1111...,0x2222...,0x3333... + +# ============================================ +# OPTIONAL: Execution Settings +# ============================================ + +# Set to true to broadcast transactions (default: false for dry-run) +BROADCAST=false + +# Set to true for verbose output +VERBOSE=false + +# Set to true to allow revoking ADMIN_ROLE from yourself (dangerous!) +ALLOW_SELF_ADMIN_REVOKE=false + +# ============================================ +# EXAMPLE CONFIGURATIONS +# ============================================ + +# Example 1: Revoke MINTER role using Ledger (dry-run) +# PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +# RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +# USE_LEDGER=true +# SENDER=0xYourLedgerAddress +# ROLE_TYPE=MINTER +# ACCOUNT=0xAccountToRevoke +# BROADCAST=false + +# Example 2: Batch revoke all roles using Ledger (broadcast) +# PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +# RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +# USE_LEDGER=true +# SENDER=0xYourLedgerAddress +# ACCOUNT=0xAccountToRevoke +# REVOKE_ADMIN=true +# REVOKE_MINTER=true +# REVOKE_PAUSER=true +# BROADCAST=true + +# Example 3: Revoke MINTER from multiple accounts using Ledger +# PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +# RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +# USE_LEDGER=true +# SENDER=0xYourLedgerAddress +# ROLE_TYPE=MINTER +# ACCOUNTS=0x1111...,0x2222...,0x3333... +# BROADCAST=true \ No newline at end of file diff --git a/JUSTFILE_USAGE.md b/JUSTFILE_USAGE.md new file mode 100644 index 0000000..089475a --- /dev/null +++ b/JUSTFILE_USAGE.md @@ -0,0 +1,248 @@ +# Just Commands for Recall Role Revocation + +Quick reference guide for using the `just` commands to revoke roles on the Recall contract. + +## Prerequisites + +1. Install `just`: https://github.com/casey/just#installation + ```bash + # macOS + brew install just + + # Linux + cargo install just + ``` + +2. Copy `.env.example` to `.env` and configure: + ```bash + cp .env.example .env + # Edit .env with your values + ``` + +## Available Commands + +### List all commands +```bash +just +# or +just --list +``` + +### Validate your .env file +```bash +just validate-env +``` + +### Check role status for an account +```bash +just check-roles 0xAccountAddress +``` + +### Revoke a single role (dry-run) +```bash +# Configure .env first with ROLE_TYPE and ACCOUNT +just dry-run +``` + +### Revoke a single role (broadcast) +```bash +# Set BROADCAST=true in .env, then: +just revoke-role +``` + +### Batch revoke multiple roles +```bash +# Configure REVOKE_ADMIN, REVOKE_MINTER, REVOKE_PAUSER in .env +just revoke-batch +``` + +### Revoke role from multiple accounts +```bash +# Configure ACCOUNTS in .env (comma-separated) +just revoke-multi +``` + +## Quick Start Examples + +### Example 1: Revoke MINTER role using Ledger (Safe Dry-Run First) + +1. Create/edit `.env`: +```bash +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +USE_LEDGER=true +SENDER=0xYourLedgerAddress +ROLE_TYPE=MINTER +ACCOUNT=0xAccountToRevoke +BROADCAST=false +``` + +2. Validate configuration: +```bash +just validate-env +``` + +3. Check current roles: +```bash +just check-roles 0xAccountToRevoke +``` + +4. Dry run (simulate): +```bash +just dry-run +``` + +5. If everything looks good, broadcast: +```bash +# Edit .env: set BROADCAST=true +just revoke-role +``` + +### Example 2: Remove all roles from compromised account + +1. Edit `.env`: +```bash +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +USE_LEDGER=true +SENDER=0xYourLedgerAddress +ACCOUNT=0xCompromisedAccount +REVOKE_ADMIN=true +REVOKE_MINTER=true +REVOKE_PAUSER=true +BROADCAST=false +``` + +2. Dry run: +```bash +just revoke-batch +``` + +3. Broadcast: +```bash +# Edit .env: set BROADCAST=true +just revoke-batch +``` + +### Example 3: Revoke MINTER from multiple test accounts + +1. Edit `.env`: +```bash +PROXY_ADDR=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb +RPC_URL=https://api.calibration.node.glif.io/rpc/v1 +USE_LEDGER=true +SENDER=0xYourLedgerAddress +ROLE_TYPE=MINTER +ACCOUNTS=0x1111...,0x2222...,0x3333... +BROADCAST=true +``` + +2. Execute: +```bash +just revoke-multi +``` + +## Configuration Options + +### Authentication Methods + +**Ledger (Recommended):** +```bash +USE_LEDGER=true +SENDER=0xYourLedgerAddress +# Optional custom path: +# HD_PATH=m/44'/60'/0'/0/1 +``` + +**Trezor:** +```bash +USE_TREZOR=true +SENDER=0xYourTrezorAddress +``` + +**Private Key (Not Recommended):** +```bash +PRIVATE_KEY=0xYourPrivateKey +``` + +### Role Types + +- `ADMIN` - Can manage roles and authorize upgrades +- `MINTER` - Can mint new tokens +- `PAUSER` - Can pause the contract + +### Safety Options + +```bash +# Prevent accidental self-admin revocation (default: false) +ALLOW_SELF_ADMIN_REVOKE=false + +# Dry-run mode - simulate without broadcasting (default: false) +BROADCAST=false + +# Verbose output for debugging +VERBOSE=true +``` + +## Workflow Best Practices + +1. **Always validate first:** + ```bash + just validate-env + ``` + +2. **Check current state:** + ```bash + just check-roles 0xTargetAccount + ``` + +3. **Dry-run before broadcasting:** + ```bash + just dry-run + ``` + +4. **Review the output carefully** + +5. **Set BROADCAST=true and execute:** + ```bash + just revoke-role + ``` + +6. **Verify the result:** + ```bash + just check-roles 0xTargetAccount + ``` + +## Troubleshooting + +### "Error: PROXY_ADDR not set in .env" +Make sure you've created a `.env` file from `.env.example` and filled in the required values. + +### "Error: Must set either USE_LEDGER=true, USE_TREZOR=true, or PRIVATE_KEY" +You need to specify an authentication method in your `.env` file. + +### Ledger not detected +1. Connect and unlock your Ledger +2. Open the Ethereum app +3. Enable "Contract data" in Ethereum app settings +4. Try the command again + +### "Caller Not Admin" error +The address you're using (SENDER) doesn't have ADMIN_ROLE. Check with: +```bash +just check-roles $YOUR_SENDER_ADDRESS +``` + +## Security Notes + +- ⚠️ **Always dry-run first** with `BROADCAST=false` +- ✅ **Use hardware wallets** (Ledger/Trezor) instead of private keys +- ✅ **Double-check addresses** before broadcasting +- ✅ **Test on testnet** before mainnet operations +- ⚠️ **Don't revoke all admin roles** without a replacement admin + +## Additional Resources + +- Full documentation: [`script/RevokeRecallRole.README.md`](script/RevokeRecallRole.README.md) +- Script source: [`script/RevokeRecallRole.s.sol`](script/RevokeRecallRole.s.sol) +- Just documentation: https://just.systems/man/en/ \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 0000000..668943e --- /dev/null +++ b/justfile @@ -0,0 +1,294 @@ +# Justfile for Recall contract operations +# Requires: just (https://github.com/casey/just) +# Usage: just revoke-role + +set dotenv-load := true + +# Default recipe - show available commands +default: + @just --list + +# Revoke a single role from an account (reads from .env) +revoke-role: + #!/usr/bin/env bash + set -euo pipefail + + # Check required environment variables + if [ -z "${PROXY_ADDR:-}" ]; then + echo "Error: PROXY_ADDR not set in .env" + exit 1 + fi + if [ -z "${ROLE_TYPE:-}" ]; then + echo "Error: ROLE_TYPE not set in .env" + exit 1 + fi + if [ -z "${ACCOUNT:-}" ]; then + echo "Error: ACCOUNT not set in .env" + exit 1 + fi + if [ -z "${RPC_URL:-}" ]; then + echo "Error: RPC_URL not set in .env" + exit 1 + fi + + # Build the forge command + CMD="forge script script/RevokeRecallRole.s.sol:RevokeRoleScript --rpc-url $RPC_URL" + + # Add broadcast flag if BROADCAST is set to true + if [ "${BROADCAST:-false}" = "true" ]; then + CMD="$CMD --broadcast" + fi + + # Add authentication method + if [ "${USE_LEDGER:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_LEDGER=true" + exit 1 + fi + CMD="$CMD --ledger --sender $SENDER" + + # Add custom HD path if specified + if [ -n "${HD_PATH:-}" ]; then + CMD="$CMD --hd-paths $HD_PATH" + fi + elif [ "${USE_TREZOR:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_TREZOR=true" + exit 1 + fi + CMD="$CMD --trezor --sender $SENDER" + elif [ -n "${PRIVATE_KEY:-}" ]; then + CMD="$CMD --private-key $PRIVATE_KEY" + else + echo "Error: Must set either USE_LEDGER=true, USE_TREZOR=true, or PRIVATE_KEY" + exit 1 + fi + + # Add verbosity if requested + if [ "${VERBOSE:-false}" = "true" ]; then + CMD="$CMD -vvvv" + fi + + echo "Executing: $CMD" + echo "" + eval $CMD + +# Batch revoke multiple roles from one account (reads from .env) +revoke-batch: + #!/usr/bin/env bash + set -euo pipefail + + # Check required environment variables + if [ -z "${PROXY_ADDR:-}" ]; then + echo "Error: PROXY_ADDR not set in .env" + exit 1 + fi + if [ -z "${ACCOUNT:-}" ]; then + echo "Error: ACCOUNT not set in .env" + exit 1 + fi + if [ -z "${RPC_URL:-}" ]; then + echo "Error: RPC_URL not set in .env" + exit 1 + fi + + # Build the forge command + CMD="forge script script/RevokeRecallRole.s.sol:BatchRevokeScript --rpc-url $RPC_URL" + + # Add broadcast flag if BROADCAST is set to true + if [ "${BROADCAST:-false}" = "true" ]; then + CMD="$CMD --broadcast" + fi + + # Add authentication method + if [ "${USE_LEDGER:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_LEDGER=true" + exit 1 + fi + CMD="$CMD --ledger --sender $SENDER" + if [ -n "${HD_PATH:-}" ]; then + CMD="$CMD --hd-paths $HD_PATH" + fi + elif [ "${USE_TREZOR:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_TREZOR=true" + exit 1 + fi + CMD="$CMD --trezor --sender $SENDER" + elif [ -n "${PRIVATE_KEY:-}" ]; then + CMD="$CMD --private-key $PRIVATE_KEY" + else + echo "Error: Must set either USE_LEDGER=true, USE_TREZOR=true, or PRIVATE_KEY" + exit 1 + fi + + if [ "${VERBOSE:-false}" = "true" ]; then + CMD="$CMD -vvvv" + fi + + echo "Executing: $CMD" + echo "" + eval $CMD + +# Revoke one role from multiple accounts (reads from .env) +revoke-multi: + #!/usr/bin/env bash + set -euo pipefail + + # Check required environment variables + if [ -z "${PROXY_ADDR:-}" ]; then + echo "Error: PROXY_ADDR not set in .env" + exit 1 + fi + if [ -z "${ROLE_TYPE:-}" ]; then + echo "Error: ROLE_TYPE not set in .env" + exit 1 + fi + if [ -z "${ACCOUNTS:-}" ]; then + echo "Error: ACCOUNTS not set in .env" + exit 1 + fi + if [ -z "${RPC_URL:-}" ]; then + echo "Error: RPC_URL not set in .env" + exit 1 + fi + + # Build the forge command + CMD="forge script script/RevokeRecallRole.s.sol:MultiAccountRevokeScript --rpc-url $RPC_URL" + + # Add broadcast flag if BROADCAST is set to true + if [ "${BROADCAST:-false}" = "true" ]; then + CMD="$CMD --broadcast" + fi + + # Add authentication method + if [ "${USE_LEDGER:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_LEDGER=true" + exit 1 + fi + CMD="$CMD --ledger --sender $SENDER" + if [ -n "${HD_PATH:-}" ]; then + CMD="$CMD --hd-paths $HD_PATH" + fi + elif [ "${USE_TREZOR:-false}" = "true" ]; then + if [ -z "${SENDER:-}" ]; then + echo "Error: SENDER address required when USE_TREZOR=true" + exit 1 + fi + CMD="$CMD --trezor --sender $SENDER" + elif [ -n "${PRIVATE_KEY:-}" ]; then + CMD="$CMD --private-key $PRIVATE_KEY" + else + echo "Error: Must set either USE_LEDGER=true, USE_TREZOR=true, or PRIVATE_KEY" + exit 1 + fi + + if [ "${VERBOSE:-false}" = "true" ]; then + CMD="$CMD -vvvv" + fi + + echo "Executing: $CMD" + echo "" + eval $CMD + +# Dry run - simulate without broadcasting +dry-run: + #!/usr/bin/env bash + set -euo pipefail + + echo "Running in DRY RUN mode (no transactions will be broadcast)" + echo "" + + # Temporarily override BROADCAST + export BROADCAST=false + just revoke-role + +# Show current role status for an account +check-roles ACCOUNT: + #!/usr/bin/env bash + set -euo pipefail + + if [ -z "${PROXY_ADDR:-}" ]; then + echo "Error: PROXY_ADDR not set in .env" + exit 1 + fi + if [ -z "${RPC_URL:-}" ]; then + echo "Error: RPC_URL not set in .env" + exit 1 + fi + + echo "Checking roles for account: {{ACCOUNT}}" + echo "Proxy address: $PROXY_ADDR" + echo "" + + ADMIN_ROLE=$(cast keccak "ADMIN_ROLE") + MINTER_ROLE=$(cast keccak "MINTER_ROLE") + PAUSER_ROLE=$(cast keccak "PAUSER_ROLE") + + echo -n "ADMIN_ROLE: " + cast call $PROXY_ADDR "hasRole(bytes32,address)(bool)" $ADMIN_ROLE {{ACCOUNT}} --rpc-url $RPC_URL + + echo -n "MINTER_ROLE: " + cast call $PROXY_ADDR "hasRole(bytes32,address)(bool)" $MINTER_ROLE {{ACCOUNT}} --rpc-url $RPC_URL + + echo -n "PAUSER_ROLE: " + cast call $PROXY_ADDR "hasRole(bytes32,address)(bool)" $PAUSER_ROLE {{ACCOUNT}} --rpc-url $RPC_URL + +# Validate .env file has required variables +validate-env: + #!/usr/bin/env bash + set -euo pipefail + + echo "Validating .env file..." + echo "" + + ERRORS=0 + + # Check required variables + if [ -z "${PROXY_ADDR:-}" ]; then + echo "❌ PROXY_ADDR is not set" + ERRORS=$((ERRORS + 1)) + else + echo "✓ PROXY_ADDR: $PROXY_ADDR" + fi + + if [ -z "${RPC_URL:-}" ]; then + echo "❌ RPC_URL is not set" + ERRORS=$((ERRORS + 1)) + else + echo "✓ RPC_URL: $RPC_URL" + fi + + # Check authentication method + if [ "${USE_LEDGER:-false}" = "true" ]; then + echo "✓ Authentication: Ledger" + if [ -z "${SENDER:-}" ]; then + echo "❌ SENDER is required when USE_LEDGER=true" + ERRORS=$((ERRORS + 1)) + else + echo "✓ SENDER: $SENDER" + fi + elif [ "${USE_TREZOR:-false}" = "true" ]; then + echo "✓ Authentication: Trezor" + if [ -z "${SENDER:-}" ]; then + echo "❌ SENDER is required when USE_TREZOR=true" + ERRORS=$((ERRORS + 1)) + else + echo "✓ SENDER: $SENDER" + fi + elif [ -n "${PRIVATE_KEY:-}" ]; then + echo "✓ Authentication: Private Key" + else + echo "❌ No authentication method set (USE_LEDGER, USE_TREZOR, or PRIVATE_KEY)" + ERRORS=$((ERRORS + 1)) + fi + + echo "" + if [ $ERRORS -eq 0 ]; then + echo "✓ .env file is valid" + else + echo "❌ Found $ERRORS error(s) in .env file" + exit 1 + fi \ No newline at end of file From ff9ed29b7faf1f520d5df989ec0df0599517bd14 Mon Sep 17 00:00:00 2001 From: Sergey Ukustov Date: Wed, 29 Oct 2025 19:12:16 +0300 Subject: [PATCH 4/4] unicode --- script/RevokeRecallRole.s.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/script/RevokeRecallRole.s.sol b/script/RevokeRecallRole.s.sol index c32d3a9..448ad46 100644 --- a/script/RevokeRecallRole.s.sol +++ b/script/RevokeRecallRole.s.sol @@ -143,13 +143,13 @@ contract RevokeRoleScript is Script { if (!recall.hasRole(recall.ADMIN_ROLE(), msg.sender)) { revert CallerNotAdmin(msg.sender); } - console.log("✓ Caller has ADMIN_ROLE"); + console.log("[OK] Caller has ADMIN_ROLE"); // Check if account has the role if (!recall.hasRole(role, account)) { revert AccountDoesNotHaveRole(account, role); } - console.log("✓ Account has the role"); + console.log("[OK] Account has the role"); // Safety check: prevent self-revocation of ADMIN_ROLE unless explicitly allowed if (role == recall.ADMIN_ROLE() && account == msg.sender) { @@ -157,7 +157,7 @@ contract RevokeRoleScript is Script { if (!allowSelfRevoke) { revert CannotRevokeSelfAdmin(); } - console.log("⚠ WARNING: Revoking ADMIN_ROLE from yourself!"); + console.log("[WARNING] Revoking ADMIN_ROLE from yourself!"); } // Display current role status @@ -171,7 +171,7 @@ contract RevokeRoleScript is Script { vm.stopBroadcast(); console.log(""); - console.log("✓ Role revoked successfully"); + console.log("[OK] Role revoked successfully"); console.log(""); console.log("Updated Role Status:"); displayRoleStatus(recall, account); @@ -257,17 +257,17 @@ contract BatchRevokeScript is Script { require(allowSelfRevoke, "Cannot revoke ADMIN_ROLE from yourself"); } recall.revokeRole(recall.ADMIN_ROLE(), account); - console.log("✓ Revoked ADMIN_ROLE"); + console.log("[OK] Revoked ADMIN_ROLE"); } if (revokeMinter && recall.hasRole(recall.MINTER_ROLE(), account)) { recall.revokeRole(recall.MINTER_ROLE(), account); - console.log("✓ Revoked MINTER_ROLE"); + console.log("[OK] Revoked MINTER_ROLE"); } if (revokePauser && recall.hasRole(recall.PAUSER_ROLE(), account)) { recall.revokeRole(recall.PAUSER_ROLE(), account); - console.log("✓ Revoked PAUSER_ROLE"); + console.log("[OK] Revoked PAUSER_ROLE"); } vm.stopBroadcast(); @@ -327,21 +327,21 @@ contract MultiAccountRevokeScript is Script { if (role == recall.ADMIN_ROLE() && account == msg.sender) { bool allowSelfRevoke = vm.envOr("ALLOW_SELF_ADMIN_REVOKE", false); if (!allowSelfRevoke) { - console.log(" ⚠ Skipping: Cannot revoke ADMIN_ROLE from yourself"); + console.log(" [WARNING] Skipping: Cannot revoke ADMIN_ROLE from yourself"); continue; } } recall.revokeRole(role, account); - console.log(" ✓ Role revoked"); + console.log(" [OK] Role revoked"); } else { - console.log(" ⚠ Skipping: Account does not have the role"); + console.log(" [WARNING] Skipping: Account does not have the role"); } } vm.stopBroadcast(); console.log(""); - console.log("✓ Multi-account revocation completed"); + console.log("[OK] Multi-account revocation completed"); } function getRoleHash(Recall recall, string memory roleType) internal view returns (bytes32) {