Skip to content

Logic issues in event mapping handlers #210

@sameh-farouk

Description

@sameh-farouk

Context

Follow-up to #208 (data corruption bugs). During the same audit, several logic issues were identified in the mapping handlers. These are incorrect behaviors that don't cause data loss but produce wrong state in the indexed database.

Each item has been verified against both the indexer code and the tfchain pallet source.

L1: Contract state defaults to OutOfFunds instead of handling GracePeriod

File: contracts.tsupdateNodeContract() / updateNameContract()

The switch on ctr.state.__kind only handles Created and Deleted. The default is OutOfFunds, so a GracePeriod state would be misindexed as OutOfFunds.

Mitigated in practice: Grace period transitions use separate dedicated events (ContractGracePeriodStarted/Ended) which are handled correctly. The chain's _update_node_contract also rejects updates during grace period (CannotUpdateContractInGraceState). Still, the default should be correct.

Impact: LOW

L2: nodeContractCanceled only releases one public IP per contract

File: contracts.tsnodeContractCanceled()

Uses ctx.store.get(PublicIp, { where: { contractId } }) which returns one record. If a contract has multiple public IPs, only one gets its contractId reset to 0. The rest remain orphaned.

Compare with contractCreated which correctly uses ctx.store.find() (returns all matching records) to assign IPs.

Chain confirmation: NodeContract.public_ips_list is a BoundedVec<PublicIP> — contracts can have multiple IPs.

Impact: HIGH — orphaned IPs appear reserved in GraphQL but are actually free on-chain.

L3: contractUpdated doesn't handle RentContract or update solutionProviderID

File: contracts.tscontractUpdated()

The handler only looks for NodeContract and NameContract. Three gaps:

  1. RentContract updates dropped: attach_solution_provider_id extrinsic (solution_provider.rs:89) emits ContractUpdated for any contract type, including RentContracts. The indexer silently ignores these.
  2. NodeContract solutionProviderID not updated: updateNodeContract() reads deployment hash/data/state but ignores solution_provider_id.
  3. NameContract solutionProviderID not updated: Same gap. Note: NameContracts can only get a solution provider via attach_solution_provider_id (creation hardcodes None).

Context: Solution provider payment distribution was removed from chain billing in Aug 2024 (commit 541e5b6). The feature is structurally intact (providers can be created, approved, attached) but functionally dormant — no payments are distributed. NameContracts never made business sense for solution providers (pure DNS registration with no value-add).

Impact: LOW (feature is non-functional on payment side), but the indexed data is incorrect.

L4: Unsafe type cast of raw event args for dedicatedFarm

File: farms.tsfarmUpdated()

let farm = item.event.args as Farm
if (farm.dedicatedFarm) {
    savedFarm.dedicatedFarm = farm.dedicatedFarm
}

Casts raw Substrate event args directly to the TypeORM Farm model. The event args structure may not match the model shape (different field names, types). This works by coincidence if the field name aligns, but is fragile and type-unsafe.

Impact: MEDIUM — could silently fail or read wrong data if event structure changes.


Related: #208 (bug fixes), PR #209

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions