···88/// Creation is off-chain and free. This contract only needs to be called when migrating
99/// to a new wrapped DID, transferring control, or deactivating.
1010contract CowRegistry {
1111-1211 /// @notice On-chain state for a single did:cow identifier.
1312 /// @dev The cowHash key is derived from the *initial* controller and wrapped DID,
1413 /// so these fields reflect the *current* state after any updates.
1514 /// controller and deactivated are packed into a single storage slot.
1615 struct Cow {
1716 address controller;
1818- bool deactivated;
1917 bool initialized;
1818+ bool deactivated;
2019 string wrappedDID;
2120 }
2221···4140 /// @notice Emitted when a cow's wrapped DID is updated.
4241 event WrappedDIDUpdated(bytes32 indexed cowHash, string wrappedDID);
43424444- /// @notice Update the wrapped DID for an already-registered cow.
4545- /// @dev Caller must be the current controller. Use updateWrappedDID if the cow
4646- /// may not yet be registered on-chain.
4747- /// @param _cowHash The cow's registry key, as returned by calculateCowHash.
4848- /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix.
4949- function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public {
5050- Cow storage cow = cows[_cowHash];
5151- if (!cow.initialized) revert NotInitialized();
4343+ /// @notice Derive the registry key for a did:cow identifier.
4444+ /// @param _controller The initial controller address.
4545+ /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix.
4646+ /// @return The keccak256 hash used as the key in the cows mapping.
4747+ function calculateCowHash(address _controller, string memory _wrappedDID) public pure returns (bytes32) {
4848+ return keccak256(abi.encodePacked(_controller, _wrappedDID));
4949+ }
5050+5151+ /// @dev Register a cow on-chain if not already present, then return its hash.
5252+ /// Reverts if the cow has been deactivated.
5353+ /// @param _controller The initial controller address from the did:cow identifier.
5454+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
5555+ /// @return cowHash The registry key for this did:cow identifier.
5656+ function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) {
5757+ cowHash = calculateCowHash(_controller, _wrappedDID);
5858+ Cow storage cow = cows[cowHash];
5259 if (cow.deactivated) revert AlreadyDeactivated();
5353- if (msg.sender != cow.controller) revert NotController();
5454- if (bytes(_wrappedDID).length == 0) revert EmptyWrappedDID();
5555-5656- cow.wrappedDID = _wrappedDID;
5757- emit WrappedDIDUpdated(_cowHash, _wrappedDID);
6060+ if (!cow.initialized) {
6161+ cow.initialized = true;
6262+ cow.controller = _controller;
6363+ cow.wrappedDID = _wrappedDID;
6464+ emit CowInitialized(cowHash, _controller, _wrappedDID);
6565+ }
6666+ return cowHash;
5867 }
59686069 /// @notice Transfer control of an already-registered cow to a new address.
···7483 emit ControllerUpdated(_cowHash, _controller);
7584 }
76858686+ /// @notice Update the wrapped DID for an already-registered cow.
8787+ /// @dev Caller must be the current controller. Use updateWrappedDID if the cow
8888+ /// may not yet be registered on-chain.
8989+ /// @param _cowHash The cow's registry key, as returned by calculateCowHash.
9090+ /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix.
9191+ function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public {
9292+ Cow storage cow = cows[_cowHash];
9393+ if (!cow.initialized) revert NotInitialized();
9494+ if (cow.deactivated) revert AlreadyDeactivated();
9595+ if (msg.sender != cow.controller) revert NotController();
9696+ if (bytes(_wrappedDID).length == 0) revert EmptyWrappedDID();
9797+9898+ cow.wrappedDID = _wrappedDID;
9999+ emit WrappedDIDUpdated(_cowHash, _wrappedDID);
100100+ }
101101+77102 /// @notice Permanently deactivate an already-registered cow.
78103 /// @dev Caller must be the current controller. Deactivation is irreversible.
79104 /// Use deactivate if the cow may not yet be registered on-chain.
···91116 emit CowDeactivated(_cowHash);
92117 }
931189494- /// @notice Derive the registry key for a did:cow identifier.
9595- /// @param _controller The initial controller address.
9696- /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix.
9797- /// @return The keccak256 hash used as the key in the cows mapping.
9898- function calculateCowHash(address _controller, string memory _wrappedDID) public pure returns (bytes32) {
9999- return keccak256(abi.encodePacked(_controller, _wrappedDID));
100100- }
101101-102102- /// @dev Register a cow on-chain if not already present, then return its hash.
103103- function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) {
104104- cowHash = calculateCowHash(_controller, _wrappedDID);
105105- Cow storage cow = cows[cowHash];
106106- if (cow.deactivated) revert AlreadyDeactivated();
107107- if (!cow.initialized) {
108108- cow.initialized = true;
109109- cow.controller = _controller;
110110- cow.wrappedDID = _wrappedDID;
111111- emit CowInitialized(cowHash, _controller, _wrappedDID);
112112- }
113113- return cowHash;
114114- }
115115-116116- /// @notice Resolve a did:cow identifier to its current wrapped DID and controller.
117117- /// @dev Returns the initial values if the cow has never been registered on-chain.
118118- /// @param _controller The initial controller address from the did:cow identifier.
119119- /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
120120- /// @return wrappedDID The current full wrapped DID (with "did:" prepended), or empty string if deactivated.
121121- /// @return controller The current controller address.
122122- function resolveCow(address _controller, string memory _wrappedDID) external view returns (string memory wrappedDID, address controller) {
123123- bytes32 cowHash = calculateCowHash(_controller, _wrappedDID);
124124- Cow storage cow = cows[cowHash];
125125- if (cow.deactivated) {
126126- return ("", address(0));
127127- }
128128- if (!cow.initialized) {
129129- return (string.concat("did:", _wrappedDID), _controller);
130130- }
131131- return (string.concat("did:", cow.wrappedDID), cow.controller);
132132- }
133133-134119 /// @notice Optionally pre-register a cow on-chain before its first update.
135120 /// @dev This is never strictly necessary — updateWrappedDID, updateController, and
136121 /// deactivate all register the cow automatically if needed.
···140125 _ensureCowInitialized(_controller, _wrappedDID);
141126 }
142127143143- /// @notice Update the wrapped DID, registering the cow on-chain if not already present.
128128+ /// @notice Transfer control to a new address, registering the cow on-chain if not already present.
144129 /// @param _controller The initial controller address from the did:cow identifier.
145130 /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
146146- /// @param _newWrappedDID The new wrapped DID, without the leading "did:" prefix.
147147- function updateWrappedDID(address _controller, string memory _wrappedDID, string memory _newWrappedDID) external {
131131+ /// @param _newController The new controller address.
132132+ function updateController(address _controller, string memory _wrappedDID, address _newController) external {
148133 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
149149- updateWrappedDIDByHash(cowHash, _newWrappedDID);
134134+ updateControllerByHash(cowHash, _newController);
150135 }
151136152152- /// @notice Transfer control to a new address, registering the cow on-chain if not already present.
137137+ /// @notice Update the wrapped DID, registering the cow on-chain if not already present.
153138 /// @param _controller The initial controller address from the did:cow identifier.
154139 /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
155155- /// @param _newController The new controller address.
156156- function updateController(address _controller, string memory _wrappedDID, address _newController) external {
140140+ /// @param _newWrappedDID The new wrapped DID, without the leading "did:" prefix.
141141+ function updateWrappedDID(address _controller, string memory _wrappedDID, string memory _newWrappedDID) external {
157142 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
158158- updateControllerByHash(cowHash, _newController);
143143+ updateWrappedDIDByHash(cowHash, _newWrappedDID);
159144 }
160145161146 /// @notice Permanently deactivate a cow, registering it on-chain if not already present.
···165150 function deactivate(address _controller, string memory _wrappedDID) external {
166151 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
167152 deactivateByHash(cowHash);
153153+ }
154154+155155+ /// @notice Resolve a did:cow identifier to its current wrapped DID and controller.
156156+ /// @dev Returns the initial values if the cow has never been registered on-chain.
157157+ /// @param _controller The initial controller address from the did:cow identifier.
158158+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
159159+ /// @return wrappedDID The current full wrapped DID (with "did:" prepended), or empty string if deactivated.
160160+ /// @return controller The current controller address.
161161+ function resolveCow(address _controller, string memory _wrappedDID)
162162+ external
163163+ view
164164+ returns (string memory wrappedDID, address controller)
165165+ {
166166+ bytes32 cowHash = calculateCowHash(_controller, _wrappedDID);
167167+ Cow storage cow = cows[cowHash];
168168+ if (cow.deactivated) {
169169+ return ("", address(0));
170170+ }
171171+ if (!cow.initialized) {
172172+ return (string.concat("did:", _wrappedDID), _controller);
173173+ }
174174+ return (string.concat("did:", cow.wrappedDID), cow.controller);
168175 }
169176}