Merge pull request #124 from aave/feat/main-profile-proxy

Feat: Add A Main Profile Creation Proxy
This commit is contained in:
Alan
2022-05-09 17:24:45 +01:00
committed by GitHub
7 changed files with 229 additions and 5 deletions

View File

@@ -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();

View 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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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()',

View 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);
});
});
});