mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
Merge pull request #124 from aave/feat/main-profile-proxy
Feat: Add A Main Profile Creation Proxy
This commit is contained in:
@@ -26,12 +26,14 @@ library Errors {
|
||||
error HandleTaken();
|
||||
error HandleLengthInvalid();
|
||||
error HandleContainsInvalidCharacters();
|
||||
error HandleFirstCharInvalid();
|
||||
error ProfileImageURILengthInvalid();
|
||||
error CallerNotFollowNFT();
|
||||
error CallerNotCollectNFT();
|
||||
error BlockNumberInvalid();
|
||||
error ArrayMismatch();
|
||||
error CannotCommentOnSelf();
|
||||
error NotWhitelisted();
|
||||
|
||||
// Module Errors
|
||||
error InitParamsInvalid();
|
||||
|
||||
44
contracts/misc/ProfileCreationProxy.sol
Normal file
44
contracts/misc/ProfileCreationProxy.sol
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.10;
|
||||
|
||||
import {ILensHub} from '../interfaces/ILensHub.sol';
|
||||
import {DataTypes} from '../libraries/DataTypes.sol';
|
||||
import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
|
||||
|
||||
/**
|
||||
* @title ProfileCreationProxy
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This is an ownable proxy contract that enforces ".lens" handle suffixes at profile creation.
|
||||
* Only the owner can create profiles.
|
||||
*/
|
||||
contract ProfileCreationProxy is Ownable {
|
||||
ILensHub immutable LENS_HUB;
|
||||
|
||||
constructor(address owner, ILensHub hub) {
|
||||
_transferOwnership(owner);
|
||||
LENS_HUB = hub;
|
||||
}
|
||||
|
||||
function proxyCreateProfile(DataTypes.CreateProfileData memory vars) external onlyOwner {
|
||||
uint256 handleLength = bytes(vars.handle).length;
|
||||
if (handleLength < 5) revert Errors.HandleLengthInvalid();
|
||||
|
||||
bytes1 firstByte = bytes(vars.handle)[0];
|
||||
if (firstByte == '-' || firstByte == '_' || firstByte == '.')
|
||||
revert Errors.HandleFirstCharInvalid();
|
||||
|
||||
for (uint256 i = 1; i < handleLength; ) {
|
||||
if (bytes(vars.handle)[i] == '.') revert Errors.HandleContainsInvalidCharacters();
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
vars.handle = string(abi.encodePacked(vars.handle, '.lens'));
|
||||
LENS_HUB.createProfile(vars);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
UIDataProvider__factory,
|
||||
ProfileFollowModule__factory,
|
||||
RevertFollowModule__factory,
|
||||
ProfileCreationProxy__factory,
|
||||
} from '../typechain-types';
|
||||
import { deployWithVerify, waitForTx } from './helpers/utils';
|
||||
|
||||
@@ -56,11 +57,13 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
const deployer = accounts[0];
|
||||
const governance = accounts[1];
|
||||
const treasuryAddress = accounts[2].address;
|
||||
const proxyAdminAddress = deployer.address;
|
||||
const profileCreatorAddress = deployer.address;
|
||||
|
||||
// Nonce management in case of deployment issues
|
||||
let deployerNonce = await ethers.provider.getTransactionCount(deployer.address);
|
||||
|
||||
console.log('\n\t -- Deploying Module Globals --');
|
||||
console.log('\n\t-- Deploying Module Globals --');
|
||||
const moduleGlobals = await deployWithVerify(
|
||||
new ModuleGlobals__factory(deployer).deploy(
|
||||
governance.address,
|
||||
@@ -143,7 +146,7 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
let proxy = await deployWithVerify(
|
||||
new TransparentUpgradeableProxy__factory(deployer).deploy(
|
||||
lensHubImpl.address,
|
||||
deployer.address,
|
||||
proxyAdminAddress,
|
||||
data,
|
||||
{ nonce: deployerNonce++ }
|
||||
),
|
||||
@@ -271,6 +274,15 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
'contracts/misc/UIDataProvider.sol:UIDataProvider'
|
||||
);
|
||||
|
||||
console.log('\n\t-- Deploying Profile Creation Proxy --');
|
||||
const profileCreationProxy = await deployWithVerify(
|
||||
new ProfileCreationProxy__factory(deployer).deploy(profileCreatorAddress, lensHub.address, {
|
||||
nonce: deployerNonce++,
|
||||
}),
|
||||
[deployer.address, lensHub.address],
|
||||
'contracts/misc/ProfileCreationProxy.sol:ProfileCreationProxy'
|
||||
);
|
||||
|
||||
// Whitelist the collect modules
|
||||
console.log('\n\t-- Whitelisting Collect Modules --');
|
||||
let governanceNonce = await ethers.provider.getTransactionCount(governance.address);
|
||||
@@ -327,6 +339,14 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
})
|
||||
);
|
||||
|
||||
// Whitelist the profile creation proxy
|
||||
console.log('\n\t-- Whitelisting Profile Creation Proxy --');
|
||||
await waitForTx(
|
||||
lensHub.whitelistProfileCreator(profileCreationProxy.address, true, {
|
||||
nonce: governanceNonce++,
|
||||
})
|
||||
);
|
||||
|
||||
// Save and log the addresses
|
||||
const addrs = {
|
||||
'lensHub proxy': lensHub.address,
|
||||
@@ -351,6 +371,7 @@ task('full-deploy-verify', 'deploys the entire Lens Protocol with explorer verif
|
||||
// 'approval follow module': approvalFollowModule.address,
|
||||
'follower only reference module': followerOnlyReferenceModule.address,
|
||||
'UI data provider': uiDataProvider.address,
|
||||
'Profile creation proxy': profileCreationProxy.address,
|
||||
};
|
||||
const json = JSON.stringify(addrs, null, 2);
|
||||
console.log(json);
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
UIDataProvider__factory,
|
||||
ProfileFollowModule__factory,
|
||||
RevertFollowModule__factory,
|
||||
ProfileCreationProxy__factory,
|
||||
} from '../typechain-types';
|
||||
import { deployContract, waitForTx } from './helpers/utils';
|
||||
|
||||
@@ -40,11 +41,13 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
const deployer = accounts[0];
|
||||
const governance = accounts[1];
|
||||
const treasuryAddress = accounts[2].address;
|
||||
const proxyAdminAddress = deployer.address;
|
||||
const profileCreatorAddress = deployer.address;
|
||||
|
||||
// Nonce management in case of deployment issues
|
||||
let deployerNonce = await ethers.provider.getTransactionCount(deployer.address);
|
||||
|
||||
console.log('\n\t -- Deploying Module Globals --');
|
||||
console.log('\n\t-- Deploying Module Globals --');
|
||||
const moduleGlobals = await deployContract(
|
||||
new ModuleGlobals__factory(deployer).deploy(
|
||||
governance.address,
|
||||
@@ -113,7 +116,7 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
let proxy = await deployContract(
|
||||
new TransparentUpgradeableProxy__factory(deployer).deploy(
|
||||
lensHubImpl.address,
|
||||
deployer.address,
|
||||
proxyAdminAddress,
|
||||
data,
|
||||
{ nonce: deployerNonce++ }
|
||||
)
|
||||
@@ -211,6 +214,13 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
})
|
||||
);
|
||||
|
||||
console.log('\n\t-- Deploying Profile Creation Proxy --');
|
||||
const profileCreationProxy = await deployContract(
|
||||
new ProfileCreationProxy__factory(deployer).deploy(profileCreatorAddress, lensHub.address, {
|
||||
nonce: deployerNonce++,
|
||||
})
|
||||
);
|
||||
|
||||
// Whitelist the collect modules
|
||||
console.log('\n\t-- Whitelisting Collect Modules --');
|
||||
let governanceNonce = await ethers.provider.getTransactionCount(governance.address);
|
||||
@@ -271,6 +281,14 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
.whitelistCurrency(currency.address, true, { nonce: governanceNonce++ })
|
||||
);
|
||||
|
||||
// Whitelist the profile creation proxy
|
||||
console.log('\n\t-- Whitelisting Profile Creation Proxy --');
|
||||
await waitForTx(
|
||||
lensHub.whitelistProfileCreator(profileCreationProxy.address, true, {
|
||||
nonce: governanceNonce++,
|
||||
})
|
||||
);
|
||||
|
||||
// Save and log the addresses
|
||||
const addrs = {
|
||||
'lensHub proxy': lensHub.address,
|
||||
@@ -295,6 +313,7 @@ task('full-deploy', 'deploys the entire Lens Protocol').setAction(async ({}, hre
|
||||
// 'approval follow module': approvalFollowModule.address,
|
||||
'follower only reference module': followerOnlyReferenceModule.address,
|
||||
'UI data provider': uiDataProvider.address,
|
||||
'Profile creation proxy': profileCreationProxy.address,
|
||||
};
|
||||
const json = JSON.stringify(addrs, null, 2);
|
||||
console.log(json);
|
||||
|
||||
@@ -62,7 +62,7 @@ task(
|
||||
// Nonce management in case of deployment issues
|
||||
let deployerNonce = await ethers.provider.getTransactionCount(deployer.address);
|
||||
|
||||
console.log('\n\t -- Deploying Module Globals --');
|
||||
console.log('\n\t-- Deploying Module Globals --');
|
||||
const moduleGlobals = await deployWithVerify(
|
||||
new ModuleGlobals__factory(deployer).deploy(
|
||||
governance.address,
|
||||
|
||||
@@ -22,6 +22,7 @@ export const ERRORS = {
|
||||
INVALID_HANDLE_LENGTH: 'HandleLengthInvalid()',
|
||||
INVALID_IMAGE_URI_LENGTH: 'ProfileImageURILengthInvalid()',
|
||||
HANDLE_CONTAINS_INVALID_CHARACTERS: 'HandleContainsInvalidCharacters()',
|
||||
HANDLE_FIRST_CHARACTER_INVALID: 'HandleFirstCharInvalid()',
|
||||
NOT_FOLLOW_NFT: 'CallerNotFollowNFT()',
|
||||
NOT_COLLECT_NFT: 'CallerNotCollectNFT()',
|
||||
BLOCK_NUMBER_INVALID: 'BlockNumberInvalid()',
|
||||
|
||||
137
test/other/profile-creation-proxy.spec.ts
Normal file
137
test/other/profile-creation-proxy.spec.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import '@nomiclabs/hardhat-ethers';
|
||||
import { expect } from 'chai';
|
||||
import { ZERO_ADDRESS } from '../helpers/constants';
|
||||
import { ERRORS } from '../helpers/errors';
|
||||
import { ProfileCreationProxy, ProfileCreationProxy__factory } from '../../typechain-types';
|
||||
import {
|
||||
deployer,
|
||||
FIRST_PROFILE_ID,
|
||||
governance,
|
||||
lensHub,
|
||||
makeSuiteCleanRoom,
|
||||
MOCK_FOLLOW_NFT_URI,
|
||||
MOCK_PROFILE_URI,
|
||||
user,
|
||||
userAddress,
|
||||
deployerAddress,
|
||||
} from '../__setup.spec';
|
||||
import { BigNumber } from 'ethers';
|
||||
import { TokenDataStructOutput } from '../../typechain-types/LensHub';
|
||||
import { getTimestamp } from '../helpers/utils';
|
||||
|
||||
makeSuiteCleanRoom('Profile Creation Proxy', function () {
|
||||
const REQUIRED_SUFFIX = '.lens';
|
||||
const MINIMUM_LENGTH = 5;
|
||||
|
||||
let profileCreationProxy: ProfileCreationProxy;
|
||||
beforeEach(async function () {
|
||||
profileCreationProxy = await new ProfileCreationProxy__factory(deployer).deploy(
|
||||
deployerAddress,
|
||||
lensHub.address
|
||||
);
|
||||
await expect(
|
||||
lensHub.connect(governance).whitelistProfileCreator(profileCreationProxy.address, true)
|
||||
).to.not.be.reverted;
|
||||
});
|
||||
|
||||
context('Negatives', function () {
|
||||
it('Should fail to create profile if handle length before suffix does not reach minimum length', async function () {
|
||||
const handle = 'a'.repeat(MINIMUM_LENGTH - 1);
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: handle,
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.INVALID_HANDLE_LENGTH);
|
||||
});
|
||||
|
||||
it('Should fail to create profile if handle contains an invalid character before the suffix', async function () {
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: 'dots.are.invalid',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.HANDLE_CONTAINS_INVALID_CHARACTERS);
|
||||
});
|
||||
|
||||
it('Should fail to create profile if handle starts with a dash, underscore or period', async function () {
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: '.abcdef',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.HANDLE_FIRST_CHARACTER_INVALID);
|
||||
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: '-abcdef',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.HANDLE_FIRST_CHARACTER_INVALID);
|
||||
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: '_abcdef',
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.be.revertedWith(ERRORS.HANDLE_FIRST_CHARACTER_INVALID);
|
||||
});
|
||||
});
|
||||
|
||||
context('Scenarios', function () {
|
||||
it('Should be able to create a profile using the whitelisted proxy, received NFT should be valid', async function () {
|
||||
let timestamp: any;
|
||||
let owner: string;
|
||||
let totalSupply: BigNumber;
|
||||
let profileId: BigNumber;
|
||||
let mintTimestamp: BigNumber;
|
||||
let tokenData: TokenDataStructOutput;
|
||||
const validHandleBeforeSuffix = 'v_al-id';
|
||||
const expectedHandle = 'v_al-id'.concat(REQUIRED_SUFFIX);
|
||||
|
||||
await expect(
|
||||
profileCreationProxy.proxyCreateProfile({
|
||||
to: userAddress,
|
||||
handle: validHandleBeforeSuffix,
|
||||
imageURI: MOCK_PROFILE_URI,
|
||||
followModule: ZERO_ADDRESS,
|
||||
followModuleInitData: [],
|
||||
followNFTURI: MOCK_FOLLOW_NFT_URI,
|
||||
})
|
||||
).to.not.be.reverted;
|
||||
|
||||
timestamp = await getTimestamp();
|
||||
owner = await lensHub.ownerOf(FIRST_PROFILE_ID);
|
||||
totalSupply = await lensHub.totalSupply();
|
||||
profileId = await lensHub.getProfileIdByHandle(expectedHandle);
|
||||
mintTimestamp = await lensHub.mintTimestampOf(FIRST_PROFILE_ID);
|
||||
tokenData = await lensHub.tokenDataOf(FIRST_PROFILE_ID);
|
||||
expect(owner).to.eq(userAddress);
|
||||
expect(totalSupply).to.eq(FIRST_PROFILE_ID);
|
||||
expect(profileId).to.eq(FIRST_PROFILE_ID);
|
||||
expect(mintTimestamp).to.eq(timestamp);
|
||||
expect(tokenData.owner).to.eq(userAddress);
|
||||
expect(tokenData.mintTimestamp).to.eq(timestamp);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user