- Status: Accepted
- Scope:
packages/evm-module·Profile.recreateProfile - Related: PRD "Recreate Profile for an existing Identity (testnet recovery)"
After a testnet ProfileStorage redeploy, some nodes have an on-chain
Identity but no Profile. recreateProfile re-attaches a Profile
to such an Identity, reusing the existing identityId so that the
surviving identityId-keyed staking, conviction and sharding state
stays addressable.
Genesis createProfile is whitelist-gated and called by an
Operational key on a brand-new identity with zero stake. Recreate
is different: it acts on an identityId that may already carry
third-party delegated stake, and it sets the initial operator fee.
recreateProfile(address operationalWallet, …) is gated onlyWhitelisted
and resolves + enforces the admin check in-body:
- The caller passes the node's Operational wallet — operators know this
(it is the node's running key); the numeric
identityIdis internal and often unknown. The contract resolvesidentityId = IdentityStorage.getIdentityId(operationalWallet). - It then calls
_checkAdmin(identityId):msg.sendermust hold that identity's Admin key. Authorization is the Admin key, exactly as before — the operational wallet is only an identifier, never the authorizer. A zero/unknown wallet resolves toid 0(no admin) and reverts, which also proves theIdentityexists. - The existing whitelist gate is preserved.
(Original draft took uint72 identityId directly, gated
onlyAdmin(identityId). Changed to the operational-wallet form for
operator ergonomics — admins rarely know the numeric id — without
weakening authorization: the admin key is still enforced, just after
in-body resolution rather than via the modifier.)
An Operational ("hot") key must not be able to set the operator fee on a stake-bearing node. A compromised hot key could otherwise re-price the node's operator fee against its delegators. Restricting recovery to the Admin key removes that vector while still letting honest operators recover their nodes with a single transaction.
- Operators must control each bricked Identity's original Admin key to recover (operationally verified, off-chain).
- If testnet whitelisting is enabled, the bricked identities' Admin wallets must be whitelisted before recovery — the genesis flow whitelisted the Operational caller instead.
- Sharding-table consistency (enforced).
ShardingTableStoragesurvives a ProfileStorage-only redeploy and cachesnodeIdperidentityId.recreateProfiletherefore reverts (NodeIdShardingMismatch) if the node is still in the ring (nodeExists(identityId)) and the suppliednodeIddiffers from the surviving ring entry. This is a read-only check — recovery deliberately does not rewrite ring state (out of scope; would touch ShardingTable). Honest recovery (same node, samenodeId) is unaffected; only a divergentnodeIdis refused.
- Operator-fee history is not recoverable. The pre-redeploy operator-fee
schedule was Profile-resident and is gone.
recreateProfileseeds a single fresh fee at recovery time (like genesiscreateProfile). For any unclaimed pre-recovery epochs,StakingV10._claimresolves the historical split viagetOperatorFeePercentageByTimestampReverse, which now falls back to the new recovery-time fee — i.e. recovery can retroactively change reward splits for those epochs. This is accepted as a known testnet limitation: the data is unrecoverable on-chain, and a real (mainnet) event of this kind would be handled by a state migration, not this recovery path. Operationally: settle/claim all pre-recovery epochs before recovering, or accept reward drift for unclaimed ones.