Skip to content

fix(security): enforce onlyOwner on migration initializers, restore native blocklist modifiers, and guard Celo fee caller - #683

Open
mertcano wants to merge 1 commit into
circlefin:masterfrom
mertcano:mertcano-patch-1
Open

mertcano wants to merge 1 commit into
circlefin:masterfrom
mertcano:mertcano-patch-1

Conversation

@mertcano

@mertcano mertcano commented Oct 1, 2026

Copy link
Copy Markdown

Summary of Changes

Addresses Critical, High, and Medium findings from the workspace security audit in stablecoin-evm[cite: 27, 32, 37]:

  1. Migration Initializer Access Control (FiatTokenV2, FiatTokenV2_1, FiatTokenV2_2 - Critical): Added onlyOwner access control, zero-address validation, and strict version monotonicity checks to prevent unauthorized re-initialization, balance sweeping, and EIP-712 domain corruption[cite: 32, 59, 60, 61].
  2. Native-Fiat Blocklist Modifier Enforcement (NativeFiatTokenV2_2 - High): Restored notBlacklisted modifiers across transfer, transferFrom, and all authorization overloads ((v, r, s) and (bytes signature)), closing blocklist bypass vectors[cite: 32, 62].
  3. Celo Fee Caller Zero-Address Guard (FiatTokenCeloV2_2 - Medium): Restricted updateFeeCaller against address(0) to protect the Celo gas lifecycle and added FeeCallerChanged event emission for auditability[cite: 37, 58].
  4. UpgradeabilityProxy Architectural Documentation (Reviewed & Dismissed): Added explicit architectural notes recording why transparent proxies omit on-chain proxiableUUID() checks[cite: 44, 63].

Vulnerability Analysis

  • Unprotected Initializers (Critical - Cat 2):

    • initializeV2, initializeV2_1, and initializeV2_2 relied strictly on _initializedVersion without caller authorization[cite: 32].
    • Following proxy upgrade, an attacker could call initializeV2_1 to drain contract-held USDC to an arbitrary lostAndFound address, or invoke initializeV2_2 to modify the token symbol (corrupting EIP-712 domain separators) and freeze victim accounts[cite: 32].
  • Dropped Blocklist Modifiers (High - Cat 14):

    • Overrides in NativeFiatTokenV2_2 dropped parent notBlacklisted modifiers, allowing blocklisted accounts to transfer native-fiat tokens directly or through signed authorizations[cite: 32].
  • Zero-Address Fee Caller (Medium - Cat 8):

    • Passing address(0) to updateFeeCaller permanently broke debitGasFees/creditGasFees, bricking gas fee payments for Celo transactions[cite: 37, 58].

Key Remediations

  • contracts/v2/FiatTokenV2.sol:
    • Added onlyOwner and require(initialized && _initializedVersion == 0, ...) to initializeV2[cite: 59].
  • contracts/v2/FiatTokenV2_1.sol:
    • Added onlyOwner, require(_initializedVersion == 1, ...), and require(lostAndFound != address(0), ...) to initializeV2_1[cite: 60].
  • contracts/v2/FiatTokenV2_2.sol:
    • Added onlyOwner and require(_initializedVersion == 2, ...) to initializeV2_2[cite: 61].
  • contracts/v2/NativeFiatTokenV2_2.sol:
    • Restored notBlacklisted(msg.sender) notBlacklisted(to) on transfer.
    • Restored notBlacklisted(from) notBlacklisted(to) on transferFrom and all transferWithAuthorization / receiveWithAuthorization variants[cite: 62].
  • contracts/v2/celo/FiatTokenCeloV2_2.sol:
    • Added require(_newFeeCaller != address(0), "FiatTokenCeloV2_2: zero fee caller") and emitted FeeCallerChanged(_newFeeCaller).

Verification

  • Compiled with pinned solc 0.6.12 via standard-JSON: 0 errors, 0 warnings across all 7 targets (FiatTokenV2, V2_1, V2_2, NativeFiatTokenV2_2, FiatTokenCeloV2_2, FiatTokenV1, FiatTokenProxy).

…ative blocklist modifiers, and guard Celo fee caller

### Summary of Changes
Addresses **Critical**, **High**, and **Medium** findings from the workspace security audit in `stablecoin-evm`[cite: 27, 32, 37]:
1. **Migration Initializer Access Control (`FiatTokenV2`, `FiatTokenV2_1`, `FiatTokenV2_2` - Critical):** Added `onlyOwner` access control, zero-address validation, and strict version monotonicity checks to prevent unauthorized re-initialization, balance sweeping, and EIP-712 domain corruption[cite: 32, 59, 60, 61].
2. **Native-Fiat Blocklist Modifier Enforcement (`NativeFiatTokenV2_2` - High):** Restored `notBlacklisted` modifiers across `transfer`, `transferFrom`, and all authorization overloads (`(v, r, s)` and `(bytes signature)`), closing blocklist bypass vectors[cite: 32, 62].
3. **Celo Fee Caller Zero-Address Guard (`FiatTokenCeloV2_2` - Medium):** Restricted `updateFeeCaller` against `address(0)` to protect the Celo gas lifecycle and added `FeeCallerChanged` event emission for auditability[cite: 37, 58].
4. **UpgradeabilityProxy Architectural Documentation (Reviewed & Dismissed):** Added explicit architectural notes recording why transparent proxies omit on-chain `proxiableUUID()` checks[cite: 44, 63].

---

### Vulnerability Analysis

- **Unprotected Initializers (Critical - Cat 2):**
  - `initializeV2`, `initializeV2_1`, and `initializeV2_2` relied strictly on `_initializedVersion` without caller authorization[cite: 32].
  - Following proxy upgrade, an attacker could call `initializeV2_1` to drain contract-held USDC to an arbitrary `lostAndFound` address, or invoke `initializeV2_2` to modify the token symbol (corrupting EIP-712 domain separators) and freeze victim accounts[cite: 32].

- **Dropped Blocklist Modifiers (High - Cat 14):**
  - Overrides in `NativeFiatTokenV2_2` dropped parent `notBlacklisted` modifiers, allowing blocklisted accounts to transfer native-fiat tokens directly or through signed authorizations[cite: 32].

- **Zero-Address Fee Caller (Medium - Cat 8):**
  - Passing `address(0)` to `updateFeeCaller` permanently broke `debitGasFees`/`creditGasFees`, bricking gas fee payments for Celo transactions[cite: 37, 58].

---

### Key Remediations

- **`contracts/v2/FiatTokenV2.sol`:**
  - Added `onlyOwner` and `require(initialized && _initializedVersion == 0, ...)` to `initializeV2`[cite: 59].
- **`contracts/v2/FiatTokenV2_1.sol`:**
  - Added `onlyOwner`, `require(_initializedVersion == 1, ...)`, and `require(lostAndFound != address(0), ...)` to `initializeV2_1`[cite: 60].
- **`contracts/v2/FiatTokenV2_2.sol`:**
  - Added `onlyOwner` and `require(_initializedVersion == 2, ...)` to `initializeV2_2`[cite: 61].
- **`contracts/v2/NativeFiatTokenV2_2.sol`:**
  - Restored `notBlacklisted(msg.sender) notBlacklisted(to)` on `transfer`.
  - Restored `notBlacklisted(from) notBlacklisted(to)` on `transferFrom` and all `transferWithAuthorization` / `receiveWithAuthorization` variants[cite: 62].
- **`contracts/v2/celo/FiatTokenCeloV2_2.sol`:**
  - Added `require(_newFeeCaller != address(0), "FiatTokenCeloV2_2: zero fee caller")` and emitted `FeeCallerChanged(_newFeeCaller)`.

---

### Verification
- Compiled with pinned `solc 0.6.12` via standard-JSON: 0 errors, 0 warnings across all 7 targets (`FiatTokenV2`, `V2_1`, `V2_2`, `NativeFiatTokenV2_2`, `FiatTokenCeloV2_2`, `FiatTokenV1`, `FiatTokenProxy`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant