Registry合約是Allo生態系的基本要件,使得創建和管理Profile 成為可能。
Profile 作為分散應用的基本實體,提供身份管理、訪問控制和互動功能。此合約配備了創建個Profile、更新元數據、管理所有權和處理資金恢復的機制。它利用外部庫、接口和內部函數,提供強大的功能,同時遵循安全和透明性。
import {Metadata} from "../libraries/Metadata.sol";
interface IRegistry {
// Profile struct宣告
struct Profile {
bytes32 id;
uint256 nonce;
string name;
Metadata metadata;
address owner;
address anchor;
}
//Events
event ProfileCreated(
bytes32 indexed profileId,
uint256 nonce, string name,
Metadata metadata,
address owner,
address anchor
);
event ProfileNameUpdated(bytes32 indexed profileId, string name, address anchor);
event ProfileMetadataUpdated(bytes32 indexed profileId, Metadata metadata);
event ProfileOwnerUpdated(bytes32 indexed profileId, address owner);
event ProfilePendingOwnerUpdated(bytes32 indexed profileId, address pendingOwner);
//View Functions
function getProfileById(bytes32 _profileId) external view returns (Profile memory profile);
function getProfileByAnchor(address _anchor) external view returns (Profile memory profile);
function isOwnerOrMemberOfProfile(bytes32 _profileId, address _account) external view
returns (bool isOwnerOrMemberOfProfile);
function isOwnerOfProfile(bytes32 _profileId, address _owner) external view returns (bool isOwnerOfProfile);
function isMemberOfProfile(bytes32 _profileId, address _member) external view returns (bool isMemberOfProfile);
//External/Public Functions
function createProfile(
uint256 _nonce,
string memory _name,
Metadata memory _metadata,
address _owner,
address[] memory _members
) external returns (bytes32 profileId);
function updateProfileName(bytes32 _profileId, string memory _name) external returns (address anchor);
function updateProfileMetadata(bytes32 _profileId, Metadata memory _metadata) external;
function updateProfilePendingOwner(bytes32 _profileId, address _pendingOwner) external;
function acceptProfileOwnership(bytes32 _profileId) external;
function addMembers(bytes32 _profileId, address[] memory _members) external;
function removeMembers(bytes32 _profileId, address[] memory _members) external;
function recoverFunds(address _token, address _recipient) external;
}
重要function
function _checkOnlyProfileOwner(bytes32 _profileId) internal view {
if (!_isOwnerOfProfile(_profileId, msg.sender)) revert UNAUTHORIZED();
}
function _isOwnerOfProfile(bytes32 _profileId, address _owner) internal view returns (bool) {
return profilesById[_profileId].owner == _owner;
}
//最重要 createProfile
function createProfile(
uint256 _nonce,
string memory _name,
Metadata memory _metadata,
address _owner,
address[] memory _members
) external returns (bytes32) {
// Generate a profile ID using a nonce and the msg.sender
bytes32 profileId = _generateProfileId(_nonce);
// Make sure the nonce is available
if (profilesById[profileId].anchor != address(0)) revert NONCE_NOT_AVAILABLE();
// Make sure the owner is not the zero address
if (_owner == address(0)) revert ZERO_ADDRESS();
// Create a new Profile instance, also generates the anchor address
Profile memory profile = Profile({
id: profileId,
nonce: _nonce,
name: _name,
metadata: _metadata,
owner: _owner,
anchor: _generateAnchor(profileId, _name)
});
profilesById[profileId] = profile;
anchorToProfileId[profile.anchor] = profileId;
// Assign roles for the profile members
uint256 memberLength = _members.length;
for (uint256 i; i < memberLength;) {
address member = _members[i];
// Will revert if any of the addresses are a zero address
if (member == address(0)) revert ZERO_ADDRESS();
// Grant the role to the member and emit the event for each member
_grantRole(profileId, member);
unchecked {
++i;
}
}
// Emit the event that the profile was created
emit ProfileCreated(profileId, profile.nonce, profile.name, profile.metadata, profile.owner, profile.anchor);
// Return the profile ID
return profileId;
}
//同步產生anchor合約
function _generateAnchor(bytes32 _profileId, string memory _name) internal returns (address anchor) {
bytes32 salt = keccak256(abi.encodePacked(_profileId, _name));
address preCalculatedAddress = CREATE3.getDeployed(salt);
// check if the contract already exists and if the profileId matches
if (preCalculatedAddress.code.length > 0) {
if (Anchor(payable(preCalculatedAddress)).profileId() != _profileId) revert ANCHOR_ERROR();
anchor = preCalculatedAddress;
} else {
// check if the contract has already been deployed by checking code size of address
bytes memory creationCode = abi.encodePacked(type(Anchor).creationCode, abi.encode(_profileId));
// Use CREATE3 to deploy the anchor contract
anchor = CREATE3.deploy(salt, creationCode, 0);
}
}
//ProfileID產生方式
/// @notice Generates the 'profileId' based on msg.sender and nonce
/// @dev Internal function used by 'createProfile()' to generate profileId.
/// @param _nonce Nonce provided by the caller to generate 'profileId'
/// @return 'profileId' The ID of the profile
function _generateProfileId(uint256 _nonce) internal view returns (bytes32) {
return keccak256(abi.encodePacked(_nonce, msg.sender));
}
//重要function
function recoverFunds(address _token, address _recipient) external onlyRole(ALLO_OWNER) {
if (_recipient == address(0)) revert ZERO_ADDRESS();
uint256 amount = _token == NATIVE ? address(this).balance : ERC20(_token).balanceOf(address(this));
_transferAmount(_token, _recipient, amount);
}
CREATE, CREATE2, CREATA3差異? 文章
CREATE
: 新合約地址是根據 sender_address 和 sender_nonce 決定。因為合約 nonce 只在部署合約時會從 1 開始遞增,且此合約的地址也須固定,因此想讓新合約在多鏈上有相同地址是不太容易管理的。address = keccak256(rlp([sender_address, sender_nonce]))[12:]
CREATE2
: 新合約地址是根據 init_code 決定而非 sender_nonce,也就是新合約地址在編譯完成後就確定了,而非根據部署時的鏈上 sender_nonce 決定。address = keccak256(0xff + sender_address + salt + keccak256(init_code))[12:]
CREATE3
:是新合約的地址只根據 sender_address 和 salt 決定。也就是就算個各鏈上新合約的 init_code 不同 (像是 constructor 參數),也能部署在相同位置。CREATE3 結合 CREATE 和 CREATE2 的特性來減少生成合約地址的變因,從最初的 nonce 到 init_code 進而只剩下 sender_address和 salt。相對的是開發者需要更注意其中的細節,像是 constructor 中的邏輯、部署時不要用錯新合約 init_code 否則地址就會被佔用、多餘的部署成本 (CREATE3 Factory & CREATE2 Proxy) 以及較複雜的流程。
預計補上這三個合約其他雜項solidity 包含: metadata.sol等位於libraries內的合約