Files
core/contracts/misc/PermissionlessCreator.sol
2024-04-02 13:07:04 -03:00

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];
}
}