Skip to content

Commit

Permalink
Merge pull request lens-protocol#124 from aave/feat/main-profile-proxy
Browse files Browse the repository at this point in the history
Feat: Add A Main Profile Creation Proxy
  • Loading branch information
donosonaumczuk authored May 9, 2022
2 parents 14bc495 + ab442ea commit df2a15f
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 5 deletions.
2 changes: 2 additions & 0 deletions contracts/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
44 changes: 44 additions & 0 deletions contracts/misc/ProfileCreationProxy.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 2 deletions tasks/full-deploy-verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
UIDataProvider__factory,
ProfileFollowModule__factory,
RevertFollowModule__factory,
ProfileCreationProxy__factory,
} from '../typechain-types';
import { deployWithVerify, waitForTx } from './helpers/utils';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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++ }
),
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
23 changes: 21 additions & 2 deletions tasks/full-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
UIDataProvider__factory,
ProfileFollowModule__factory,
RevertFollowModule__factory,
ProfileCreationProxy__factory,
} from '../typechain-types';
import { deployContract, waitForTx } from './helpers/utils';

Expand All @@ -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,
Expand Down Expand Up @@ -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++ }
)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion tasks/testnet-full-deploy-verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test/helpers/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()',
Expand Down
137 changes: 137 additions & 0 deletions test/other/profile-creation-proxy.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

0 comments on commit df2a15f

Please sign in to comment.