mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-10 22:58:08 -05:00
354 lines
14 KiB
Solidity
354 lines
14 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity ^0.8.15;
|
|
|
|
import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
|
|
import {Types} from 'contracts/libraries/constants/Types.sol';
|
|
|
|
import {ILensHandles} from 'contracts/interfaces/ILensHandles.sol';
|
|
import {ITokenHandleRegistry} from 'contracts/interfaces/ITokenHandleRegistry.sol';
|
|
import {ImmutableOwnable} from 'contracts/misc/ImmutableOwnable.sol';
|
|
|
|
/**
|
|
* @title PermissonlessCreator
|
|
* @author Lens Protocol
|
|
* @notice This is an ownable public proxy contract which is open for all.
|
|
*/
|
|
contract PermissionlessCreator is ImmutableOwnable {
|
|
ILensHandles public immutable LENS_HANDLES;
|
|
ITokenHandleRegistry public immutable TOKEN_HANDLE_REGISTRY;
|
|
|
|
// These should be configured through setters before being whitelisted in the LensHub.
|
|
uint128 private _profileCreationCost;
|
|
uint128 private _handleCreationCost;
|
|
uint8 private _handleLengthMin;
|
|
|
|
mapping(address => uint256) internal _credits;
|
|
mapping(address => bool) internal _isCreditProvider; // Credit providers can increase/decrease credits of users
|
|
mapping(address => bool) internal _isUntrusted;
|
|
mapping(uint256 => address) internal _profileCreatorUsingCredits; // The address that created the profile spending credits
|
|
|
|
modifier onlyCreditProviders() {
|
|
if (!_isCreditProvider[msg.sender]) {
|
|
revert OnlyCreditProviders();
|
|
}
|
|
_;
|
|
}
|
|
|
|
error OnlyCreditProviders();
|
|
error HandleAlreadyExists();
|
|
error InvalidFunds();
|
|
error InsufficientCredits();
|
|
error ProfileAlreadyLinked();
|
|
error HandleLengthNotAllowed();
|
|
error NotAllowed();
|
|
|
|
event HandleCreationPriceChanged(uint256 newPrice, uint256 timestamp);
|
|
event ProfileCreationPriceChanged(uint256 newPrice, uint256 timestamp);
|
|
event HandleLengthMinChanged(uint8 newMinLength, uint256 timestamp);
|
|
event CreditBalanceChanged(address indexed creditAddress, uint256 remainingCredits, uint256 timestamp);
|
|
event TrustStatusChanged(address indexed targetAddress, bool isUntrusted, uint256 timestamp);
|
|
event CreditProviderStatusChanged(address indexed creditProvider, bool isCreditProvider, uint256 timestamp);
|
|
|
|
event ProfileCreatedUsingCredits(uint256 indexed profileId, address indexed creator, uint256 timestamp);
|
|
event HandleCreatedUsingCredits(
|
|
uint256 indexed handleId,
|
|
string handle,
|
|
address indexed creator,
|
|
uint256 timestamp
|
|
);
|
|
|
|
constructor(
|
|
address owner,
|
|
address lensHub,
|
|
address lensHandles,
|
|
address tokenHandleRegistry
|
|
) ImmutableOwnable(owner, lensHub) {
|
|
LENS_HANDLES = ILensHandles(lensHandles);
|
|
TOKEN_HANDLE_REGISTRY = ITokenHandleRegistry(tokenHandleRegistry);
|
|
}
|
|
|
|
/////////////////////////// Permissionless payable creation functions //////////////////////////////////////////////
|
|
|
|
function createProfile(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
address[] calldata delegatedExecutors
|
|
) external payable returns (uint256) {
|
|
_validatePayment(_profileCreationCost);
|
|
// delegatedExecutors are only allowed if to == msg.sender
|
|
if (delegatedExecutors.length > 0 && createProfileParams.to != msg.sender) {
|
|
revert NotAllowed();
|
|
}
|
|
return _createProfile(createProfileParams, delegatedExecutors);
|
|
}
|
|
|
|
function createHandle(address to, string calldata handle) external payable returns (uint256) {
|
|
_validatePayment(_handleCreationCost);
|
|
_validateHandleLength(handle);
|
|
return LENS_HANDLES.mintHandle(to, handle);
|
|
}
|
|
|
|
function createProfileWithHandle(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
string calldata handle,
|
|
address[] calldata delegatedExecutors
|
|
) external payable returns (uint256, uint256) {
|
|
_validatePayment(_profileCreationCost + _handleCreationCost);
|
|
_validateHandleLength(handle);
|
|
// delegatedExecutors are only allowed if to == msg.sender
|
|
if (delegatedExecutors.length > 0 && createProfileParams.to != msg.sender) {
|
|
revert NotAllowed();
|
|
}
|
|
return _createProfileWithHandle(createProfileParams, handle, delegatedExecutors);
|
|
}
|
|
|
|
////////////////////////////// Credit based creation functions /////////////////////////////////////////////////////
|
|
|
|
function createProfileUsingCredits(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
address[] calldata delegatedExecutors
|
|
) external returns (uint256) {
|
|
_spendCredit(msg.sender);
|
|
uint256 profileId = _createProfile(createProfileParams, delegatedExecutors);
|
|
_profileCreatorUsingCredits[profileId] = msg.sender;
|
|
emit ProfileCreatedUsingCredits(profileId, msg.sender, block.timestamp);
|
|
return profileId;
|
|
}
|
|
|
|
function createProfileWithHandleUsingCredits(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
string calldata handle,
|
|
address[] calldata delegatedExecutors
|
|
) external returns (uint256, uint256) {
|
|
_spendCredit(msg.sender);
|
|
_validateHandleLength(handle);
|
|
(uint256 profileId, uint256 handleId) = _createProfileWithHandle(
|
|
createProfileParams,
|
|
handle,
|
|
delegatedExecutors
|
|
);
|
|
_profileCreatorUsingCredits[profileId] = msg.sender;
|
|
emit ProfileCreatedUsingCredits(profileId, msg.sender, block.timestamp);
|
|
emit HandleCreatedUsingCredits(handleId, handle, msg.sender, block.timestamp);
|
|
return (profileId, handleId);
|
|
}
|
|
|
|
function createHandleUsingCredits(address to, string calldata handle) external returns (uint256) {
|
|
_spendCredit(msg.sender);
|
|
_validateHandleLength(handle);
|
|
uint256 handleId = LENS_HANDLES.mintHandle(to, handle);
|
|
emit HandleCreatedUsingCredits(handleId, handle, msg.sender, block.timestamp);
|
|
return handleId;
|
|
}
|
|
|
|
////////////////////////////////////////// Base functions //////////////////////////////////////////////////////////
|
|
|
|
function _createProfile(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
address[] memory delegatedExecutors
|
|
) internal returns (uint256) {
|
|
uint256 profileId;
|
|
if (delegatedExecutors.length == 0) {
|
|
profileId = ILensHub(LENS_HUB).createProfile(createProfileParams);
|
|
} else {
|
|
// We mint the profile to this contract first, then apply delegates if defined
|
|
// This will not allow to initialize follow modules that require funds from the msg.sender,
|
|
// but we assume only simple follow modules should be set during profile creation.
|
|
// Complex ones can be set after the profile is created.
|
|
address destination = createProfileParams.to;
|
|
|
|
// Copy the struct from calldata to memory to make it mutable
|
|
Types.CreateProfileParams memory createProfileParamsMemory = createProfileParams;
|
|
createProfileParamsMemory.to = address(this);
|
|
|
|
profileId = ILensHub(LENS_HUB).createProfile(createProfileParamsMemory);
|
|
|
|
_addDelegatesToProfile(profileId, delegatedExecutors);
|
|
|
|
// keep the config if its been set
|
|
ILensHub(LENS_HUB).transferFromKeepingDelegates(address(this), destination, profileId);
|
|
}
|
|
return profileId;
|
|
}
|
|
|
|
function _createProfileWithHandle(
|
|
Types.CreateProfileParams calldata createProfileParams,
|
|
string calldata handle,
|
|
address[] memory delegatedExecutors
|
|
) private returns (uint256, uint256) {
|
|
// Copy the struct from calldata to memory to make it mutable
|
|
Types.CreateProfileParams memory createProfileParamsMemory = createProfileParams;
|
|
|
|
// We mint the handle & profile to this contract first, then link it to the profile and delegates if defined
|
|
// This will not allow to initialize follow modules that require funds from the msg.sender,
|
|
// but we assume only simple follow modules should be set during profile creation.
|
|
// Complex ones can be set after the profile is created.
|
|
address destination = createProfileParamsMemory.to;
|
|
|
|
createProfileParamsMemory.to = address(this);
|
|
|
|
uint256 profileId = ILensHub(LENS_HUB).createProfile(createProfileParamsMemory);
|
|
uint256 handleId = LENS_HANDLES.mintHandle(address(this), handle);
|
|
|
|
TOKEN_HANDLE_REGISTRY.link(handleId, profileId);
|
|
|
|
_addDelegatesToProfile(profileId, delegatedExecutors);
|
|
|
|
// Transfer the handle & profile to the destination
|
|
LENS_HANDLES.transferFrom(address(this), destination, handleId);
|
|
// keep the config if its been set
|
|
ILensHub(LENS_HUB).transferFromKeepingDelegates(address(this), destination, profileId);
|
|
|
|
return (profileId, handleId);
|
|
}
|
|
|
|
function _addDelegatesToProfile(uint256 profileId, address[] memory delegatedExecutors) private {
|
|
// set delegates if any
|
|
if (delegatedExecutors.length > 0) {
|
|
// Initialize an array of bools with the same length as delegatedExecutors
|
|
bool[] memory executorEnabled = new bool[](delegatedExecutors.length);
|
|
|
|
// Fill the array with `true`
|
|
for (uint256 i = 0; i < delegatedExecutors.length; i++) {
|
|
executorEnabled[i] = true;
|
|
}
|
|
|
|
ILensHub(LENS_HUB).changeDelegatedExecutorsConfig(profileId, delegatedExecutors, executorEnabled);
|
|
}
|
|
}
|
|
|
|
function _validateHandleLength(string calldata handle) private view {
|
|
if (!_isCreditProvider[msg.sender] && bytes(handle).length < _handleLengthMin) {
|
|
revert HandleLengthNotAllowed();
|
|
}
|
|
}
|
|
|
|
function _validatePayment(uint256 amount) private view {
|
|
if (msg.value < amount) {
|
|
revert InvalidFunds();
|
|
}
|
|
}
|
|
|
|
function _spendCredit(address account) private {
|
|
if (_isCreditProvider[msg.sender]) {
|
|
// Credit providers do not need credits.
|
|
return;
|
|
}
|
|
_credits[account] -= 1;
|
|
emit CreditBalanceChanged(account, _credits[account], block.timestamp);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// @notice Special function allowing to transfer a profile from one address to another, keeping the delegates.
|
|
/// @dev Requires the sender, a trusted credit-based creator, to approve the profile with this contract as spender.
|
|
function transferFromKeepingDelegates(address from, address to, uint256 tokenId) external {
|
|
if (_isUntrusted[msg.sender] || _profileCreatorUsingCredits[tokenId] != msg.sender) {
|
|
// If msg.sender trust was revoked or is not the original creator of the profile through credits, then fail.
|
|
revert NotAllowed();
|
|
}
|
|
|
|
ILensHub(LENS_HUB).transferFromKeepingDelegates(from, to, tokenId);
|
|
}
|
|
|
|
// Credit Provider functions
|
|
|
|
function increaseCredits(address account, uint256 amount) external onlyCreditProviders {
|
|
if (_isUntrusted[account]) {
|
|
// Cannot increase credits for an account with revoked trust.
|
|
revert NotAllowed();
|
|
}
|
|
_credits[account] += amount;
|
|
emit CreditBalanceChanged(account, _credits[account], block.timestamp);
|
|
}
|
|
|
|
function decreaseCredits(address account, uint256 amount) external onlyCreditProviders {
|
|
_credits[account] -= amount;
|
|
emit CreditBalanceChanged(account, _credits[account], block.timestamp);
|
|
}
|
|
|
|
// Owner functions
|
|
|
|
function withdrawFunds() external onlyOwner {
|
|
// Use call instead of transfer to provide more gas (otherwise it doesn't work with SAFE):
|
|
// https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/
|
|
(bool success, ) = OWNER.call{value: address(this).balance}('');
|
|
if (!success) {
|
|
revert();
|
|
}
|
|
}
|
|
|
|
function addCreditProvider(address creditProvider) external onlyOwner {
|
|
_isCreditProvider[creditProvider] = true;
|
|
emit CreditProviderStatusChanged(creditProvider, true, block.timestamp);
|
|
}
|
|
|
|
function removeCreditProvider(address creditProvider) external onlyOwner {
|
|
_isCreditProvider[creditProvider] = false;
|
|
emit CreditProviderStatusChanged(creditProvider, false, block.timestamp);
|
|
}
|
|
|
|
function setProfileCreationPrice(uint128 newPrice) external onlyOwner {
|
|
_profileCreationCost = newPrice;
|
|
emit ProfileCreationPriceChanged(newPrice, block.timestamp);
|
|
}
|
|
|
|
function setHandleCreationPrice(uint128 newPrice) external onlyOwner {
|
|
_handleCreationCost = newPrice;
|
|
emit HandleCreationPriceChanged(newPrice, block.timestamp);
|
|
}
|
|
|
|
function setHandleLengthMin(uint8 newMinLength) external onlyOwner {
|
|
_handleLengthMin = newMinLength;
|
|
emit HandleLengthMinChanged(newMinLength, block.timestamp);
|
|
}
|
|
|
|
function setTrustStatus(address targetAddress, bool setAsUntrusted) external onlyOwner {
|
|
if (setAsUntrusted == _isUntrusted[targetAddress]) {
|
|
// No change in trust status.
|
|
return;
|
|
}
|
|
if (setAsUntrusted && _credits[targetAddress] > 0) {
|
|
// If it is becoming unstrusted, current credits should be removed.
|
|
_credits[targetAddress] = 0;
|
|
emit CreditBalanceChanged(targetAddress, 0, block.timestamp);
|
|
}
|
|
_isUntrusted[targetAddress] = setAsUntrusted;
|
|
emit TrustStatusChanged(targetAddress, setAsUntrusted, block.timestamp);
|
|
}
|
|
|
|
// View functions
|
|
|
|
function getProfileWithHandleCreationPrice() external view returns (uint256) {
|
|
return _profileCreationCost + _handleCreationCost;
|
|
}
|
|
|
|
function getProfileCreationPrice() external view returns (uint256) {
|
|
return _profileCreationCost;
|
|
}
|
|
|
|
function getHandleCreationPrice() external view returns (uint256) {
|
|
return _handleCreationCost;
|
|
}
|
|
|
|
function getHandleLengthMin() external view returns (uint8) {
|
|
return _handleLengthMin;
|
|
}
|
|
|
|
function isUntrusted(address targetAddress) external view returns (bool) {
|
|
return _isUntrusted[targetAddress];
|
|
}
|
|
|
|
function isCreditProvider(address targetAddress) external view returns (bool) {
|
|
return _isCreditProvider[targetAddress];
|
|
}
|
|
|
|
function getCreditBalance(address targetAddress) external view returns (uint256) {
|
|
return _credits[targetAddress];
|
|
}
|
|
|
|
function getProfileCreatorUsingCredits(uint256 profileId) external view returns (address) {
|
|
return _profileCreatorUsingCredits[profileId];
|
|
}
|
|
}
|