Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
docs: add twin transfer documentation and a new ADR
  • Loading branch information
sameh-farouk committed Sep 3, 2025
commit dc758cf830db4c6b34be981e47a3a3ea5b515781
69 changes: 69 additions & 0 deletions docs/architecture/0024-twin-ownership-transfer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 24. Twin Ownership Transfer in pallet-tfgrid

Date: 2025-09-03

## Status

Accepted

## Context

Operators occasionally need to transfer ownership of an existing Twin to a different account (e.g., organization handover, compromised keys, or account restructuring). Previously there was no safe, on-chain mediated process to move Twin ownership while preserving accounting invariants and preventing hijacks.

## Decision

Introduce a two-step, time-bound transfer protocol implemented in `pallet-tfgrid` that:

- Requires the prospective new owner to initiate the request (prevents unsolicited hijacks).
- Enforces that the new owner has accepted Terms & Conditions and does not already own a Twin.
- Requires explicit acceptance by the current owner before expiry.
- Repatriates all reserved balance from old owner to new owner on acceptance.

### Dispatchables

- `request_twin_transfer(origin=new_account, twin_id, new_account)`
- Origin must match `new_account`.
- Validates preconditions and creates a pending transfer with expiry.
- Emits `TwinTransferRequested { twin_id, old_account, new_account }`.

- `accept_twin_transfer(origin=old_account, request_id)`
- Origin must be the current (old) owner of the Twin.
- Requires the request to be pending and unexpired.
- Moves reserved balance from old to new as reserved, updates Twin owner and indexes, completes the request.
- Emits `TwinOwnershipTransferred { twin_id, old_account, new_account }` and `TwinUpdated(Twin)`.

### Storage

- `TwinTransferRequests: RequestId -> TwinTransferRequest` (status, twin_id, old_account, new_account, expiry)
- `PendingTransferByTwin: TwinId -> RequestId` (enforces one pending request per Twin)
- `TwinTransferRequestID: u64` (monotonic counter)

### Types

- `TransferStatus` enum: `Pending | Completed`

### Expiry

- Requests expire after a fixed window (using HOURS from `tfchain_support::constants::time`). Acceptance after expiry fails.

## Security Considerations

- New owner must initiate request (prevents current owner from pushing ownership without consent of new owner).
- New owner must have signed T&C and must not own another Twin (prevents multi-ownership and aligns with usage rules).
- Current owner must accept while request is pending and before expiry.
- On acceptance, reserved balance is repatriated using `repatriate_reserved(..., BalanceStatus::Reserved)`; failures are tolerated but the pallet attempts best-effort transfer before ownership move.

## Consequences

- Clear, auditable transfer trail via events.
- Compatible with existing Twin lifecycle; no changes to Twin schema.

## Backwards Compatibility & Migration

- New storage items are additive.
- No migration of existing state required.

## References

- Implementation: `substrate-node/pallets/pallet-tfgrid/src/twin_transfer.rs`
- Extrinsics wiring: `substrate-node/pallets/pallet-tfgrid/src/lib.rs` (call_index 40, 41)
48 changes: 48 additions & 0 deletions docs/misc/twin_transfer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Twin Ownership Transfer

This document explains the twin ownership transfer flow in the `pallet-tfgrid` pallet and the main events and errors.

## Overview

- The new owner initiates the transfer via `request_twin_transfer(twin_id, new_account)`.
- The current owner accepts via `accept_twin_transfer(request_id)`.
- On success, ownership of the twin moves to the new account, reserved balances are repatriated, and indices are updated.

## Dispatchables

- request_twin_transfer(origin, twin_id, new_account)
- Origin must be the new account.
- Emits `TwinTransferRequested { twin_id, old_account, new_account }`.
- accept_twin_transfer(origin, request_id)
- Origin must be the current (old) owner of the twin.
- Emits `TwinOwnershipTransferred { twin_id, old_account, new_account }` and `TwinUpdated(Twin)`.

## Preconditions

- Twin must exist.
- New account must have accepted Terms & Conditions (`user_accept_tc`).
- New account must not already own a twin.
- Only one pending transfer per twin.
- Acceptance must happen before expiry (request has an expiry block window).

## Common Errors

- UserDidNotSignTermsAndConditions: new account did not accept T&C.
- TwinTransferRequestMustBeFromNewAccount: request must be signed by the new account.
- TwinTransferNewAccountHasTwin: new account already has a twin.
- TwinTransferPendingExists: a pending transfer already exists for this twin.
- TwinTransferRequestNotFound: request ID does not exist.
- TwinTransferRequestAlreadyCompleted: request already completed/cannot be accepted again.
- TwinTransferRequestExpired: acceptance after expiry is rejected.
- UnauthorizedToUpdateTwin: accept extrinsic not signed by current owner.

## Events

- TwinTransferRequested
- TwinOwnershipTransferred
- TwinUpdated

## Notes

- Reserved balance of the old owner is repatriated to the new owner as reserved during acceptance.
- A simple integration test is available under `substrate-node/tests/integration_tests.robot` ("Test Twin Transfer Flow").