feat: Profile Migration Fork Tests

This commit is contained in:
vicnaum
2023-03-03 11:50:03 +01:00
parent 6e394ba7e0
commit b3ec5fd8ab
11 changed files with 304 additions and 71 deletions

2
.gitignore vendored
View File

@@ -24,8 +24,6 @@ coverage.json
.coverage_contracts
lcov*
addresses.json
# Compiler files
forge-cache/
out/

46
addresses.json Normal file
View File

@@ -0,0 +1,46 @@
{
"mainnet": {
"chainId": 137,
"network": "polygon",
"LensHubProxy": "0xDb46d1Dc155634FbC732f92E853b10B288AD5a1d",
"LensHubImplementation": "0x96f1ba24294ffe0dfcd832d8376da4a4645a4cd6",
"ModuleGlobals": "0x3Df697FF746a60CBe9ee8D47555c88CB66f03BB9",
"FreeCollectModule": "0x23b9467334bEb345aAa6fd1545538F3d54436e96",
"PoolAddressesProvider": "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
"MultirecipientFeeCollectModule": "0xfa9da21d0a18c7b7de4566481c1e8952371f880a",
"StepwiseCollectModule": "0x210c94877dcfceda8238acd382372278844a54d7",
"ERC4626FeeCollectModule": "0x394fd100b13197787023ef4cf318cde456545db7",
"AaveFeeCollectModule": "0xa94713d0688c8a483c3352635cec4e0ce88d6a29",
"TokenGatedReferenceModule": "0x3d7f4f71a90fe5a9d13fab2716080f2917cf88f3"
},
"testnet": {
"chainId": 80001,
"network": "mumbai",
"LensHubProxy": "0x60Ae865ee4C725cd04353b5AAb364553f56ceF82",
"LensHubImplementation": "0x45cf9Ba12b43F6c8B7148E06A6f84c5B9ad3Dd44",
"ModuleGlobals": "0x1353aAdfE5FeD85382826757A95DE908bd21C4f9",
"FreeCollectModule": "0x0BE6bD7092ee83D44a6eC1D949626FeE48caB30c",
"PoolAddressesProvider": "0x5343b5bA672Ae99d627A1C87866b8E53F47Db2E6",
"MultirecipientFeeCollectModule": "0x99d6c3eabf05435e851c067d2c3222716f7fcfe5",
"StepwiseCollectModule": "0x7a7b8e7699e0492da1d3c7eab7e2f3bf1065aa40",
"ERC4626FeeCollectModule": "0x79697402bd2caa19a53d615fb1a30a98e35b84d5",
"AaveFeeCollectModule": "0x912860ed4ed6160c48a52d52fcab5c059d34fe5a",
"TokenGatedReferenceModule": "0xb4ba8dccd35bd3dcc5d58dbb9c7dff9c9268add9",
"InteractionLogic": "0x845242e2Cd249af8D4f0D7085DefEAc3381815E3"
},
"sandbox": {
"chainId": 80001,
"network": "mumbai",
"LensHubProxy": "0x7582177F9E536aB0b6c721e11f383C326F2Ad1D5",
"LensHubImplementation": "0x1b30F214c192EF4B7F8c9926c47C4161016955DA",
"ModuleGlobals": "0xcbCC5b9611d22d11403373432642Df9Ef7Dd81AD",
"FreeCollectModule": "0x11C45Cbc6fDa2dbe435C0079a2ccF9c4c7051595",
"PoolAddressesProvider": "0x5343b5bA672Ae99d627A1C87866b8E53F47Db2E6",
"MultirecipientFeeCollectModule": "0x1cff6c45b0de2fff70670ef4dc67a92a1ccfe0bb",
"StepwiseCollectModule": "0x6928d6127dfa0da401737e6ff421fcf62d5617a3",
"ERC4626FeeCollectModule": "0x31126c602cf88193825a99dcd1d17bf1124b1b4f",
"AaveFeeCollectModule": "0x666e06215747879ee68b3e5a317dcd8411de1897",
"TokenGatedReferenceModule": "0x86d35562ceb9f10d7c2c23c098dfeacb02f53853",
"MockSandboxGovernance": "0x1677d9cC4861f1C85ac7009d5F06f49c928CA2AD"
}
}

View File

@@ -5,9 +5,11 @@ pragma solidity ^0.8.19;
contract ImmutableOwnable {
address immutable OWNER;
address immutable LENS_HUB;
address immutable MIGRATOR;
error OnlyOwner();
error OnlyOwnerOrHub();
error OnlyOwnerOrHubOrMigrator();
modifier onlyOwner() {
if (msg.sender != OWNER) {
@@ -23,8 +25,16 @@ contract ImmutableOwnable {
_;
}
constructor(address owner, address lensHub) {
modifier onlyOwnerOrHubOrMigrator() {
if (msg.sender != OWNER && msg.sender != LENS_HUB && msg.sender != MIGRATOR) {
revert OnlyOwnerOrHubOrMigrator();
}
_;
}
constructor(address owner, address lensHub, address migrator) {
OWNER = owner;
LENS_HUB = lensHub;
MIGRATOR = migrator;
}
}

View File

@@ -7,6 +7,13 @@ import {LensHub} from 'contracts/LensHub.sol';
import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
struct ProfileMigrationData {
uint256 profileId;
address profileDestination;
string handle;
bytes32 handleHash;
}
contract ProfileMigration is Ownable {
LensHub public immutable lensHub;
LensHandles public immutable lensHandles;
@@ -26,13 +33,6 @@ contract ProfileMigration is Ownable {
tokenHandleRegistry = TokenHandleRegistry(tokenHandleRegistryAddress);
}
struct ProfileMigrationData {
uint256 profileId;
address profileDestination;
string handle;
bytes32 handleHash;
}
// TODO: Assume we pause everything - creating, transfer, etc.
function _migrateProfile(ProfileMigrationData calldata profileMigrationData) internal {
lensHub.migrateProfile(profileMigrationData.profileId, profileMigrationData.handleHash);

View File

@@ -6,7 +6,7 @@ import {ERC721} from '@openzeppelin/contracts/token/ERC721/ERC721.sol';
import {VersionedInitializable} from 'contracts/base/upgradeability/VersionedInitializable.sol';
import {ImmutableOwnable} from 'contracts/misc/migrations/ImmutableOwnable.sol';
library Events {
library HandlesEvents {
event HandleMinted(string handle, string namespace, uint256 handleId, address to);
}
@@ -17,7 +17,11 @@ contract LensHandles is ERC721, VersionedInitializable, ImmutableOwnable {
string constant NAMESPACE = 'lens';
bytes32 constant NAMESPACE_HASH = keccak256(bytes(NAMESPACE));
constructor(address owner, address lensHub) ERC721('', '') ImmutableOwnable(owner, lensHub) {}
constructor(
address owner,
address lensHub,
address migrator
) ERC721('', '') ImmutableOwnable(owner, lensHub, migrator) {}
function name() public pure override returns (string memory) {
return string.concat(symbol(), ' Handles');
@@ -37,12 +41,12 @@ contract LensHandles is ERC721, VersionedInitializable, ImmutableOwnable {
* @param to The address where the handle is being minted to.
* @param localName The local name of the handle.
*/
function mintHandle(address to, string calldata localName) external onlyOwnerOrHub returns (uint256) {
function mintHandle(address to, string calldata localName) external onlyOwnerOrHubOrMigrator returns (uint256) {
bytes32 localNameHash = keccak256(bytes(localName));
bytes32 handleHash = keccak256(abi.encodePacked(localNameHash, NAMESPACE_HASH));
uint256 handleId = uint256(handleHash);
_mint(to, handleId);
emit Events.HandleMinted(localName, NAMESPACE, handleId, to);
emit HandlesEvents.HandleMinted(localName, NAMESPACE, handleId, to);
return handleId;
}

View File

@@ -6,7 +6,7 @@ import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import {VersionedInitializable} from 'contracts/base/upgradeability/VersionedInitializable.sol';
// TODO: Move to Errors file
library Errors {
library RegistryErrors {
error NotHandleOwner();
error NotTokenOwner();
error NotHandleOrTokenOwner();
@@ -26,7 +26,7 @@ struct Handle {
}
// TODO: Move to Events file
library Events {
library RegistryEvents {
event HandleLinked(Handle handle, Token token);
event HandleUnlinked(Handle handle, Token token);
}
@@ -53,14 +53,14 @@ contract TokenHandleRegistry is VersionedInitializable {
modifier onlyHandleOwner(Handle memory handle, address transactionExecutor) {
if (IERC721(handle.collection).ownerOf(handle.id) != transactionExecutor) {
revert Errors.NotHandleOwner();
revert RegistryErrors.NotHandleOwner();
}
_;
}
modifier onlyTokenOwner(Token memory token, address transactionExecutor) {
if (IERC721(token.collection).ownerOf(token.id) != transactionExecutor) {
revert Errors.NotTokenOwner();
revert RegistryErrors.NotTokenOwner();
}
_;
}
@@ -75,7 +75,7 @@ contract TokenHandleRegistry is VersionedInitializable {
!(IERC721(handle.collection).ownerOf(handle.id) == transactionExecutor ||
IERC721(token.collection).ownerOf(token.id) == transactionExecutor)
) {
revert Errors.NotHandleOrTokenOwner();
revert RegistryErrors.NotHandleOrTokenOwner();
}
_;
}
@@ -91,12 +91,12 @@ contract TokenHandleRegistry is VersionedInitializable {
// V1->V2 Migration function
function migrationLinkHandleWithToken(uint256 handleId, uint256 tokenId) external {
require(msg.sender == migrator, 'Only migrator');
require(msg.sender == migrator || msg.sender == LENS_HUB, 'Only migrator or hub');
Handle memory handle = Handle({collection: LENS_HANDLES, id: handleId});
Token memory token = Token({collection: LENS_HUB, id: tokenId});
handleToToken[_handleHash(handle)] = token;
tokenToHandle[_tokenHash(token)] = handle;
emit Events.HandleLinked(handle, token);
emit RegistryEvents.HandleLinked(handle, token);
}
// NOTE: Simplified interfaces for the first version - Namespace and LensHub are constants
@@ -147,7 +147,7 @@ contract TokenHandleRegistry is VersionedInitializable {
) internal onlyTokenOwner(token, msg.sender) onlyHandleOwner(handle, msg.sender) {
handleToToken[_handleHash(handle)] = token;
tokenToHandle[_tokenHash(token)] = handle;
emit Events.HandleLinked(handle, token);
emit RegistryEvents.HandleLinked(handle, token);
}
function _unlinkHandleFromToken(
@@ -156,7 +156,7 @@ contract TokenHandleRegistry is VersionedInitializable {
) internal onlyHandleOrTokenOwner(handle, token, msg.sender) {
delete handleToToken[_handleHash(handle)];
delete tokenToHandle[_tokenHash(token)];
emit Events.HandleUnlinked(handle, token);
emit RegistryEvents.HandleUnlinked(handle, token);
}
// Utility functions for mappings

View File

@@ -8,31 +8,65 @@ import 'contracts/LensHub.sol';
import 'contracts/FollowNFT.sol';
import 'contracts/CollectNFT.sol';
import 'contracts/misc/migrations/ProfileMigration.sol';
import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
/**
* This script will deploy the current repository implementations, using the given environment
* hub proxy address.
*/
contract DeployUpgradeScript is Script {
function run() public {
uint256 deployerKey = vm.envUint('DEPLOYER_KEY');
string memory deployerMnemonic = vm.envString('MNEMONIC');
uint256 deployerKey = vm.deriveKey(deployerMnemonic, 0);
address deployer = vm.addr(deployerKey);
address hubProxyAddr = vm.envAddress('HUB_PROXY_ADDRESS');
address owner = deployer;
LensHub hub = LensHub(hubProxyAddr);
address followNFTAddress = hub.getFollowNFTImpl();
address collectNFTAddress = hub.getCollectNFTImpl();
uint256 deployerNonce = vm.getNonce(deployer);
// Precompute needed addresss.
address lensHandlesAddress = computeCreateAddress(deployer, deployerNonce);
address migratorAddress = computeCreateAddress(deployer, deployerNonce + 1);
address tokenHandleRegistryAddress = computeCreateAddress(deployer, deployerNonce + 2);
// Start deployments.
vm.startBroadcast(deployerKey);
// Precompute needed addresss.
address followNFTAddr = computeCreateAddress(deployer, 1);
address collectNFTAddr = computeCreateAddress(deployer, 2);
LensHandles lensHandles = new LensHandles(owner, address(hub), migratorAddress);
console.log(address(lensHandles), lensHandlesAddress);
// Deploy implementation contracts.
address hubImpl = address(new LensHub(followNFTAddr, collectNFTAddr));
address followNFT = address(new FollowNFT(hubProxyAddr));
address collectNFT = address(new CollectNFT(hubProxyAddr));
ProfileMigration migrator = new ProfileMigration(
owner,
address(hub),
lensHandlesAddress,
tokenHandleRegistryAddress
);
console.log(address(migrator), migratorAddress);
vm.writeFile('addrs', '');
vm.writeLine('addrs', string(abi.encodePacked('hubImpl: ', vm.toString(hubImpl))));
vm.writeLine('addrs', string(abi.encodePacked('followNFT: ', vm.toString(followNFT))));
vm.writeLine('addrs', string(abi.encodePacked('collectNFT: ', vm.toString(collectNFT))));
TokenHandleRegistry tokenHandleRegistry = new TokenHandleRegistry(
address(hub),
lensHandlesAddress,
migratorAddress
);
console.log(address(tokenHandleRegistry), tokenHandleRegistryAddress);
address hubImpl = address(
new LensHub(
followNFTAddress,
collectNFTAddress,
migratorAddress,
lensHandlesAddress,
tokenHandleRegistryAddress
)
);
console.log('New hub impl:', hubImpl);
vm.stopBroadcast();
}
}

View File

@@ -54,7 +54,7 @@ const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: '0.8.15',
version: '0.8.19',
settings: {
optimizer: {
enabled: true,

View File

@@ -64,37 +64,8 @@ contract TestSetup is Test, ForkManagement, ArrayHelpers {
Types.MirrorParams mockMirrorParams;
Types.CollectParams mockCollectParams;
function isEnvSet(string memory key) internal returns (bool) {
try vm.envString(key) {
return true;
} catch {
return false;
}
}
constructor() {
// TODO: Replace with envOr when it's released
forkEnv = isEnvSet('TESTING_FORK') ? vm.envString('TESTING_FORK') : '';
if (bytes(forkEnv).length > 0) {
fork = true;
console.log('\n\n Testing using %s fork', forkEnv);
loadJson();
network = getNetwork();
if (isEnvSet('FORK_BLOCK')) {
forkBlockNumber = vm.envUint('FORK_BLOCK');
vm.createSelectFork(network, forkBlockNumber);
console.log('Fork Block number (FIXED BLOCK):', forkBlockNumber);
} else {
vm.createSelectFork(network);
forkBlockNumber = block.number;
console.log('Fork Block number:', forkBlockNumber);
}
checkNetworkParams();
loadBaseAddresses(forkEnv);
} else {
deployBaseContracts();
@@ -111,12 +82,6 @@ contract TestSetup is Test, ForkManagement, ArrayHelpers {
///////////////////////////////////////// End governance actions.
}
// TODO: Replace with forge-std/StdJson.sol::keyExists(...) when/if this PR is approved:
// https://github.com/foundry-rs/forge-std/pull/226
function keyExists(string memory key) internal returns (bool) {
return json.parseRaw(key).length > 0;
}
function loadBaseAddresses(string memory targetEnv) internal virtual {
bytes32 PROXY_IMPLEMENTATION_STORAGE_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);

View File

@@ -17,6 +17,47 @@ contract ForkManagement is Script {
_;
}
// TODO: Move somewhere else
function isEnvSet(string memory key) internal returns (bool) {
try vm.envString(key) {
return true;
} catch {
return false;
}
}
// TODO: Move somewhere else
// TODO: Replace with forge-std/StdJson.sol::keyExists(...) when/if this PR is approved:
// https://github.com/foundry-rs/forge-std/pull/226
function keyExists(string memory key) internal returns (bool) {
return json.parseRaw(key).length > 0;
}
constructor() {
// TODO: Replace with envOr when it's released
forkEnv = isEnvSet('TESTING_FORK') ? vm.envString('TESTING_FORK') : '';
if (bytes(forkEnv).length > 0) {
fork = true;
console.log('\n\n Testing using %s fork', forkEnv);
loadJson();
network = getNetwork();
if (isEnvSet('FORK_BLOCK')) {
forkBlockNumber = vm.envUint('FORK_BLOCK');
vm.createSelectFork(network, forkBlockNumber);
console.log('Fork Block number (FIXED BLOCK):', forkBlockNumber);
} else {
vm.createSelectFork(network);
forkBlockNumber = block.number;
console.log('Fork Block number:', forkBlockNumber);
}
checkNetworkParams();
}
}
function loadJson() internal {
string memory root = vm.projectRoot();
string memory path = string.concat(root, '/addresses.json');

View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import 'forge-std/Test.sol';
import {ForkManagement} from 'test/foundry/helpers/ForkManagement.sol';
import {CollectNFT} from 'contracts/CollectNFT.sol';
import {LensHub} from 'contracts/LensHub.sol';
import {FollowNFT} from 'contracts/FollowNFT.sol';
import {TransparentUpgradeableProxy} from 'contracts/base/upgradeability/TransparentUpgradeableProxy.sol';
import {ModuleGlobals} from 'contracts/misc/ModuleGlobals.sol';
import 'contracts/misc/migrations/ProfileMigration.sol';
import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
import {Types} from 'contracts/libraries/constants/Types.sol';
contract MigrationsTest is Test, ForkManagement {
using stdJson for string;
uint256 internal constant LENS_PROTOCOL_PROFILE_ID = 1;
bytes32 constant PROXY_IMPLEMENTATION_STORAGE_SLOT =
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
bytes32 constant ADMIN_SLOT = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1);
address owner = address(0x087E4);
address deployer = address(1);
address governance;
address treasury;
address hubProxyAddr;
address proxyAdmin;
ProfileMigration migrator;
LensHandles lensHandles;
TokenHandleRegistry tokenHandleRegistry;
CollectNFT collectNFT;
FollowNFT followNFT;
LensHub hubImpl;
TransparentUpgradeableProxy hubAsProxy;
LensHub hub;
ModuleGlobals moduleGlobals;
function loadBaseAddresses(string memory targetEnv) internal virtual {
console.log('targetEnv:', targetEnv);
hubProxyAddr = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHubProxy')));
console.log('hubProxyAddr:', hubProxyAddr);
hub = LensHub(hubProxyAddr);
console.log('Hub:', address(hub));
address followNFTAddr = hub.getFollowNFTImpl();
address collectNFTAddr = hub.getCollectNFTImpl();
address hubImplAddr = address(uint160(uint256(vm.load(hubProxyAddr, PROXY_IMPLEMENTATION_STORAGE_SLOT))));
console.log('Found hubImplAddr:', hubImplAddr);
proxyAdmin = address(uint160(uint256(vm.load(hubProxyAddr, ADMIN_SLOT))));
followNFT = FollowNFT(followNFTAddr);
collectNFT = CollectNFT(collectNFTAddr);
hubAsProxy = TransparentUpgradeableProxy(payable(address(hub)));
moduleGlobals = ModuleGlobals(json.readAddress(string(abi.encodePacked('.', targetEnv, '.ModuleGlobals'))));
governance = hub.getGovernance();
}
function setUp() public onlyFork {
loadBaseAddresses(forkEnv);
// Precompute needed addresss.
address lensHandlesAddress = computeCreateAddress(deployer, 0);
address migratorAddress = computeCreateAddress(deployer, 1);
address tokenHandleRegistryAddress = computeCreateAddress(deployer, 2);
vm.startPrank(deployer);
lensHandles = new LensHandles(owner, address(hub), migratorAddress);
assertEq(address(lensHandles), lensHandlesAddress);
migrator = new ProfileMigration(owner, address(hub), lensHandlesAddress, tokenHandleRegistryAddress);
assertEq(address(migrator), migratorAddress);
tokenHandleRegistry = new TokenHandleRegistry(address(hub), lensHandlesAddress, migratorAddress);
assertEq(address(tokenHandleRegistry), tokenHandleRegistryAddress);
hubImpl = new LensHub(
address(followNFT),
address(collectNFT),
migratorAddress,
lensHandlesAddress,
tokenHandleRegistryAddress
);
vm.stopPrank();
vm.prank(proxyAdmin);
hubAsProxy.upgradeTo(address(hubImpl));
}
function testMigrationsPublic() public onlyFork {
uint256[] memory profileIds = new uint256[](10);
for (uint256 i = 0; i < 10; i++) {
profileIds[i] = i + 1;
}
hub.batchMigrateProfiles(profileIds);
}
function testMigrationsByOwner() public onlyFork {
ProfileMigrationData[] memory profileMigrationDatas = new ProfileMigrationData[](10);
for (uint256 i = 0; i < 10; i++) {
uint256 profileId = i + 1;
string memory handleWithLens = hub.getProfile(profileId).handleDeprecated;
string memory handle = hub.getProfile(profileId).handleDeprecated;
if (profileId != LENS_PROTOCOL_PROFILE_ID) {
assembly {
let handle_length := mload(handle)
mstore(handle, sub(handle_length, 5)) // Cut 5 chars (.lens) from the end
}
}
profileMigrationDatas[i] = ProfileMigrationData({
profileId: profileId,
profileDestination: hub.ownerOf(profileId),
handle: handle,
handleHash: keccak256(bytes(handleWithLens))
});
}
vm.prank(owner);
migrator.batchMigrateProfiles(profileMigrationDatas);
}
}