mirror of
https://github.com/lens-protocol/core.git
synced 2026-01-09 14:18:04 -05:00
Merge branch 'development' into misc/svgs-v3
This commit is contained in:
121
addresses.json
121
addresses.json
@@ -149,95 +149,144 @@
|
||||
"ProfileTokenURI": "0xCCF77B802160326282F260bb6e275333fEA9E76C",
|
||||
"HandleTokenURI": "0x33b7C0692DD8267f936936C0A0f7079144d78B92",
|
||||
"FollowTokenURI": "0x1a4D3f97770925A14997B351C5cC3Cd47192a5B8"
|
||||
"PermissionlessCreatorImpl": "0xCBC26052907AEC42878870B330cA141264E56205",
|
||||
"PermissionlessCreator": "0x0b5e6100243f793e480DE6088dE6bA70aA9f3872"
|
||||
},
|
||||
"testnet": {
|
||||
"chainId": 80001,
|
||||
"network": "mumbai",
|
||||
"LensProfilesGuardianTimelock": 300,
|
||||
"LensHandlesGuardianTimelock": 300,
|
||||
"LensHubProxy": "0x60Ae865ee4C725cd04353b5AAb364553f56ceF82",
|
||||
"ModuleGlobals": "0x1353aAdfE5FeD85382826757A95DE908bd21C4f9",
|
||||
"LensHubProxy": "0x4fbffF20302F3326B20052ab9C217C44F6480900",
|
||||
"ModuleGlobals": "0x19E75039d17D580b66bD0Eb52cF011B2966A8582",
|
||||
"ProfileCreator": "0x6C1e1bC39b13f9E0Af9424D76De899203F47755F",
|
||||
"LensPeriphery": "0xD5037d72877808cdE7F669563e9389930AF404E8",
|
||||
"LensPeriphery": "0xFf7Ba2334dD8D50067c664561ffFddc7aecEA870",
|
||||
"UIDataProvider": "0x2BcFaaCb0450d63860E3Ee7c62ea12330BdC96F5",
|
||||
"WMATIC": "0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889",
|
||||
"Modules": {
|
||||
"v1": {
|
||||
"collect": [
|
||||
{
|
||||
"name": "FreeCollectModule",
|
||||
"addy": "0x0BE6bD7092ee83D44a6eC1D949626FeE48caB30c"
|
||||
"addy": "0x41564F744244D4221B87e4570A351F507f774245"
|
||||
},
|
||||
{
|
||||
"name": "FeeCollectModule",
|
||||
"addy": "0xeb4f3EC9d01856Cec2413bA5338bF35CeF932D82"
|
||||
"addy": "0x04DdbF98fA7D795A2c522fe501Eff5AdA68e0703"
|
||||
},
|
||||
{
|
||||
"name": "LimitedFeeCollectModule",
|
||||
"addy": "0xFCDA2801a31ba70dfe542793020a934F880D54aB"
|
||||
},
|
||||
{
|
||||
"name": "TimedFeeCollectModule",
|
||||
"addy": "0x36447b496ebc97DDA6d8c8113Fe30A30dC0126Db"
|
||||
},
|
||||
{
|
||||
"name": "LimitedTimedFeeCollectModule",
|
||||
"addy": "0xDa76E44775C441eF53B9c769d175fB2948F15e1C"
|
||||
},
|
||||
{
|
||||
"name": "RevertCollectModule",
|
||||
"addy": "0x5E70fFD2C6D04d65C3abeBa64E93082cfA348dF8"
|
||||
"addy": "0x0206b4123E2fdec8F98BC31132e13928a793D676"
|
||||
},
|
||||
{
|
||||
"name": "SimpleFeeCollectModule",
|
||||
"addy": "0xb4a9874adc790eec88fd086e43d329bbc9520efd"
|
||||
"addy": "0x01C43c11161B7A1960f95595445c8449a9c356D5"
|
||||
},
|
||||
{
|
||||
"name": "MultirecipientFeeCollectModule",
|
||||
"addy": "0x99d6c3eabf05435e851c067d2c3222716f7fcfe5"
|
||||
"addy": "0xBA377571F81D7225A5744911932aB5451b4Ca5d7"
|
||||
},
|
||||
{
|
||||
"name": "StepwiseCollectModule",
|
||||
"addy": "0x7a7b8e7699e0492da1d3c7eab7e2f3bf1065aa40"
|
||||
},
|
||||
{
|
||||
"name": "ERC4626FeeCollectModule",
|
||||
"addy": "0x79697402bd2caa19a53d615fb1a30a98e35b84d5"
|
||||
},
|
||||
{
|
||||
"name": "AaveFeeCollectModule",
|
||||
"addy": "0x912860ed4ed6160c48a52d52fcab5c059d34fe5a"
|
||||
"addy": "0xfdf89C33b36331f7568a1885bcCf2AfaCe44084e"
|
||||
}
|
||||
],
|
||||
"follow": [
|
||||
{
|
||||
"name": "FeeFollowModule",
|
||||
"addy": "0xe7AB9BA11b97EAC820DbCc861869092b52B65C06"
|
||||
"addy": "0x0000000000000000000000000000000000000000"
|
||||
},
|
||||
{
|
||||
"name": "ProfileFollowModule",
|
||||
"addy": "0x8c32203df6b1A04E25145346e2DaAD0B4712C20D"
|
||||
"addy": "0x0000000000000000000000000000000000000000"
|
||||
},
|
||||
{
|
||||
"name": "RevertFollowModule",
|
||||
"addy": "0x8c822Fc029EBdE62Da1Ed1072534c5e112dAE48c"
|
||||
"addy": "0x0000000000000000000000000000000000000000"
|
||||
}
|
||||
],
|
||||
"reference": [
|
||||
{
|
||||
"name": "TokenGatedReferenceModule",
|
||||
"addy": "0xb4ba8dccd35bd3dcc5d58dbb9c7dff9c9268add9"
|
||||
"addy": "0xD7aE30dCD7bc74F98562C2E64B3762058289C884"
|
||||
},
|
||||
{
|
||||
"name": "FollowerOnlyReferenceModule",
|
||||
"addy": "0x7Ea109eC988a0200A1F79Ae9b78590F92D357a16"
|
||||
"addy": "0x50c1E6f739ad1b7ADaCdf848656682e4644aF42F"
|
||||
},
|
||||
{
|
||||
"name": "DegreesOfSeparationReferenceModule",
|
||||
"addy": "0xe20D64D25779D2Ae0d76711e5Aca23EE633f2E1E"
|
||||
"addy": "0xA740cb1f188FfF87b232cc9F5f27c473E1653d6A"
|
||||
}
|
||||
]
|
||||
},
|
||||
"v2": {}
|
||||
"v2": {
|
||||
"follow": [
|
||||
{
|
||||
"name": "FeeFollowModule",
|
||||
"addy": "0xB7612562C63dDcAFE4349beE6Ca35e9170819504"
|
||||
},
|
||||
{
|
||||
"name": "RevertFollowModule",
|
||||
"addy": "0xaaB7E564ECbb2D11bCb9592A18F2E9281BA9f73A"
|
||||
}
|
||||
],
|
||||
"act": [
|
||||
{
|
||||
"name": "CollectPublicationAction",
|
||||
"addy": "0x4FdAae7fC16Ef41eAE8d8f6578d575C9d64722f2"
|
||||
}
|
||||
],
|
||||
"collect": [
|
||||
{
|
||||
"name": "SimpleFeeCollectModule",
|
||||
"addy": "0x345Cc3A3F9127DE2C69819C2E07bB748dE6E45ee"
|
||||
},
|
||||
{
|
||||
"name": "MultirecipientFeeCollectModule",
|
||||
"addy": "0xe9FabdC429640DC35153C9Ba13AE83A0A1996912"
|
||||
}
|
||||
],
|
||||
"reference": [
|
||||
{
|
||||
"name": "DegreesOfSeparationReferenceModule",
|
||||
"addy": "0xFb290857DBA5c24Ef548a8D98CEFba8E56293254"
|
||||
},
|
||||
{
|
||||
"name": "FollowerOnlyReferenceModule",
|
||||
"addy": "0x9Dd0b6e39F8Ea8B8dE9D55acC4652573E2eCB16f"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Treasury": "0x8c6a6488822a5Dad91CBBfFAF7Af3379Bd6d8306",
|
||||
"TreasuryFee": "50",
|
||||
"LegacyCollectNFTImpl": "0x712d529Fe13c075069d97A878243e3E5A826D012",
|
||||
"FollowNFTImpl": "0xC2452BAAeffd6109750401bF83e15E5aaec98606",
|
||||
"LensHandlesImpl": "0x4d2c60431D4Fb2484C82fdD8C05985FfF93851E1",
|
||||
"LensHandles": "0x44e1668150A154f01D53d1f05B8Fa0d8f3f341a9",
|
||||
"TokenHandleRegistryImpl": "0xe3a704FEe3260cbee9B54A300be53f534Ad6755A",
|
||||
"TokenHandleRegistry": "0x66cA05cAc4e8D543028192dD28C0570a0b6e3413",
|
||||
"ModuleRegistryImpl": "0xBa7b1C57e6168b2B910b24F26FEbe41B4552A47e",
|
||||
"ModuleRegistry": "0x4BeB63842BB800A1Da77a62F2c74dE3CA39AF7C0",
|
||||
"LensHubV2Impl": "0xb4A26f55Cc2d1473b8A7649d90d34ba52A480391",
|
||||
"GovernanceContract": "0x6D90D6c3F66784f5b5AFEb68f856592e4ad158c3",
|
||||
"GovernanceContractAdmin": "0x1A1cDf59C94a682a067fA2D288C2167a8506abd7",
|
||||
"ProxyAdminContract": "0xfbAddb4efA038325800846238e928529b7DD4a67",
|
||||
"ProxyAdminContractAdmin": "0xcB6C7b2E340D50701d45d55507f19A5cE5d72330",
|
||||
"LensV2UpgradeContract": "0x0Bd9617289e79751a9e1a0a19232608E1F7C36A4",
|
||||
"ProfileCreationProxy": "0x0554a7163C3aa423429719940FFE179F21cD83f6",
|
||||
"PublicActProxyImpl": "0x8555ad9CaCf2DE9873C15ed8369FC8BE7E2675FA",
|
||||
"PublicActProxy": "0x4ed64Eb32C96Df0eA60BB8934798F4DFd3098Ba1",
|
||||
"CollectNFT": "0x15062A8b16EAe79D68EC6a583212349bdF679C5e",
|
||||
"CollectPublicationActionImpl": "0x2f422811ABBc5AFA6d18f083e70Bc6326fD245E0",
|
||||
"LitAccessControlImpl": "0x6F0512aC88DDEB0A4bE8EeCC716daC0793EeCff2",
|
||||
"LitAccessControl": "0xc4F726a10fDEb0E98e16Fa658b606192d57FC71c",
|
||||
"AnonymousProfileId": "3",
|
||||
"FollowTokenURI": "0xB9136f393861822cF2CAEa7E7062f6c71354d46f",
|
||||
"PermissionlessCreatorImpl": "0xBb841c444655558176aBf97993b7f653A2B24994",
|
||||
"PermissionlessCreator": "0xCb4FB63c3f13CB83cCD6F10E9e5F29eC250329Cc",
|
||||
"FreeCreditsProvider": "0xC3161EeE3A055860125eE0b70412e4e4081852dD",
|
||||
"ProfileTokenURI": "0x19787A2E8b396077B99F381c39B2DB5781FF6D38"
|
||||
},
|
||||
"sandbox": {
|
||||
|
||||
80
broadcast/DeployLensHub.s.sol/80001/9352fad2-latest.json
Normal file
80
broadcast/DeployLensHub.s.sol/80001/9352fad2-latest.json
Normal file
File diff suppressed because one or more lines are too long
80
broadcast/DeployLensHub.s.sol/80001/run-1707226458.json
Normal file
80
broadcast/DeployLensHub.s.sol/80001/run-1707226458.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -129,6 +129,41 @@ abstract contract LensProfiles is LensBaseERC721, ERC2981CollectionRoyalties, IL
|
||||
LensBaseERC721.supportsInterface(interfaceId) || ERC2981CollectionRoyalties.supportsInterface(interfaceId);
|
||||
}
|
||||
|
||||
function transferFromKeepingDelegates(address from, address to, uint256 tokenId) external {
|
||||
//solhint-disable-next-line max-line-length
|
||||
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
|
||||
revert Errors.NotOwnerOrApproved();
|
||||
}
|
||||
|
||||
if (!StorageLib.profileCreatorWhitelisted()[msg.sender]) {
|
||||
// Delegates can be maintained on transfers only when executed by whitelisted profile creators, which are
|
||||
// trusted entities, for the sake of a better onboarding UX.
|
||||
revert Errors.NotAllowed();
|
||||
}
|
||||
|
||||
if (ownerOf(tokenId) != from) {
|
||||
revert Errors.InvalidOwner();
|
||||
}
|
||||
|
||||
if (to == address(0)) {
|
||||
revert Errors.InvalidParameter();
|
||||
}
|
||||
|
||||
_beforeTokenTransferWithoutClearingDelegates(from, to, tokenId);
|
||||
|
||||
// Clear approvals from the previous owner
|
||||
_approve(address(0), tokenId);
|
||||
|
||||
unchecked {
|
||||
--StorageLib.balances()[from];
|
||||
++StorageLib.balances()[to];
|
||||
}
|
||||
|
||||
StorageLib.getTokenData(tokenId).owner = to;
|
||||
|
||||
emit Transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
function _hasTokenGuardianEnabled(address wallet) internal view returns (bool) {
|
||||
return
|
||||
!wallet.isContract() &&
|
||||
@@ -159,4 +194,16 @@ abstract contract LensProfiles is LensBaseERC721, ERC2981CollectionRoyalties, IL
|
||||
}
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
function _beforeTokenTransferWithoutClearingDelegates(
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
) internal whenNotPaused {
|
||||
if (from != address(0) && _hasTokenGuardianEnabled(from)) {
|
||||
// Cannot transfer profile if the guardian is enabled, except at minting time.
|
||||
revert Errors.GuardianEnabled();
|
||||
}
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import {Errors} from 'contracts/libraries/constants/Errors.sol';
|
||||
import {TransparentUpgradeableProxy} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
|
||||
|
||||
contract LensVersion is ILensVersion {
|
||||
string internal constant version = '2.0.3';
|
||||
string internal constant version = '2.0.4';
|
||||
|
||||
bytes20 internal constant gitCommit = hex'3bb1438b28b69f584ab9a290f261e3452fd34ad0';
|
||||
bytes20 internal constant gitCommit = hex'91fb968ff155ed065dad4146f65a1a8c45b8f922';
|
||||
|
||||
event LensUpgradeVersion(address implementation, string version, bytes20 gitCommit, uint256 timestamp);
|
||||
|
||||
|
||||
@@ -29,4 +29,9 @@ interface ILensProfiles is ILensERC721 {
|
||||
* @return uint256 The timestamp at which the Token Guardian will become effectively disabled. Zero if enabled.
|
||||
*/
|
||||
function getTokenGuardianDisablingTimestamp(address wallet) external view returns (uint256);
|
||||
|
||||
/**
|
||||
* @notice allows transferring of profile but keeping the delegate settings
|
||||
*/
|
||||
function transferFromKeepingDelegates(address from, address to, uint256 tokenId) external;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ library StorageLib {
|
||||
// uint256 constant NAME_SLOT = 0;
|
||||
// uint256 constant SYMBOL_SLOT = 1;
|
||||
uint256 constant TOKEN_DATA_MAPPING_SLOT = 2;
|
||||
// uint256 constant BALANCES_SLOT = 3;
|
||||
uint256 constant BALANCES_SLOT = 3;
|
||||
// uint256 constant TOKEN_APPROVAL_MAPPING_SLOT = 4;
|
||||
// uint256 constant OPERATOR_APPROVAL_MAPPING_SLOT = 5;
|
||||
// Slot 6 is deprecated in Lens V2. In V1 it was used for ERC-721 Enumerable's `ownedTokens`.
|
||||
@@ -114,6 +114,12 @@ library StorageLib {
|
||||
}
|
||||
}
|
||||
|
||||
function balances() internal pure returns (mapping(address => uint256) storage _balances) {
|
||||
assembly {
|
||||
_balances.slot := BALANCES_SLOT
|
||||
}
|
||||
}
|
||||
|
||||
function blockedStatus(
|
||||
uint256 blockerProfileId
|
||||
) internal pure returns (mapping(uint256 => bool) storage _blockedStatus) {
|
||||
|
||||
@@ -49,4 +49,6 @@ library Errors {
|
||||
|
||||
// Migration Errors
|
||||
error NotMigrationAdmin();
|
||||
|
||||
error NotAllowed();
|
||||
}
|
||||
|
||||
21
contracts/misc/FreeCreditsProvider.sol
Normal file
21
contracts/misc/FreeCreditsProvider.sol
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.15;
|
||||
|
||||
import {PermissionlessCreator} from 'contracts/misc/PermissionlessCreator.sol';
|
||||
|
||||
contract FreeCreditsProvider {
|
||||
PermissionlessCreator permissionlessCreator;
|
||||
|
||||
constructor(address permissionlessCreator_) {
|
||||
permissionlessCreator = PermissionlessCreator(permissionlessCreator_);
|
||||
}
|
||||
|
||||
function getFreeCredit(address user, uint256 amount) external {
|
||||
return permissionlessCreator.increaseCredits(user, amount);
|
||||
}
|
||||
|
||||
function burnCredits(uint256 amount) external {
|
||||
return permissionlessCreator.decreaseCredits(msg.sender, amount);
|
||||
}
|
||||
}
|
||||
344
contracts/misc/PermissionlessCreator.sol
Normal file
344
contracts/misc/PermissionlessCreator.sol
Normal file
@@ -0,0 +1,344 @@
|
||||
// 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 (bytes(handle).length < _handleLengthMin) {
|
||||
revert HandleLengthNotAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
function _validatePayment(uint256 amount) private view {
|
||||
if (msg.value < amount) {
|
||||
revert InvalidFunds();
|
||||
}
|
||||
}
|
||||
|
||||
function _spendCredit(address account) private {
|
||||
_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 {
|
||||
payable(OWNER).transfer(address(this).balance);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
61
foundry.toml
61
foundry.toml
@@ -13,8 +13,9 @@ optimizer_runs = 10
|
||||
ignored_error_codes = []
|
||||
no_match_path = "script/svg/*"
|
||||
|
||||
# via_ir = true
|
||||
via_ir = true
|
||||
|
||||
# Mainnet libraries:
|
||||
# libraries = [
|
||||
# 'contracts/libraries/ActionLib.sol:ActionLib:0x7990dac84e3241fe314b980bba1466ac08715c4f',
|
||||
# 'contracts/libraries/FollowLib.sol:FollowLib:0xe280cb21fb36b6b2d584428b809a6b822a5c2260',
|
||||
@@ -27,55 +28,17 @@ no_match_path = "script/svg/*"
|
||||
# 'contracts/libraries/ValidationLib.sol:ValidationLib:0x9cafd24d2851d9eb56e5a8fd394ab2ac0ef99849',
|
||||
# ]
|
||||
|
||||
# ProfileSVG
|
||||
# Testnet libraries:
|
||||
# libraries = [
|
||||
# 'contracts/libraries/svgs/Profile/Body/BodyHoodie.sol:BodyHoodie:0x7bc391b78f1ed3a2341faf915fb3edd3b5767e83',
|
||||
# 'contracts/libraries/svgs/Profile/Body/BodyJacket.sol:BodyJacket:0xc6c1142fdd3f538c9435aff13d60e2339ba10160',
|
||||
# 'contracts/libraries/svgs/Profile/Body/BodyShibuya.sol:BodyShibuya:0x9701be89b6ade39ae77ffbf03356eb137a31da7f',
|
||||
# 'contracts/libraries/svgs/Profile/Body/BodyTShirt.sol:BodyTShirt:0xf02231c0319a004fcec98196ee42450a54521d7a',
|
||||
# 'contracts/libraries/svgs/Profile/Body/BodyTanktop.sol:BodyTanktop:0xee94f5418c760f6306f1e8a461df1236074ab811',
|
||||
# 'contracts/libraries/svgs/Profile/Body.sol:Body:0x1fd46ec18608ce20958b8513c305477b60d573d3',
|
||||
# 'contracts/libraries/svgs/Profile/Face.sol:Face:0x0d8e2dbf398638ec60d90e9f120c9a0c2debf6f3',
|
||||
# 'contracts/libraries/svgs/Profile/Face.sol:Face2:0xd418e23eb9333c76113028ccd6c840b4d6f287e7',
|
||||
# 'contracts/libraries/svgs/Profile/Hands.sol:Hands:0xff7e415dd484b80df69ed32da055de9e95185605',
|
||||
# 'contracts/libraries/svgs/Profile/Head.sol:Head:0xd53523fdb369aa75b34452e3f762471527225281',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBeanie.sol:HeadwearBeanie:0xebc5e2516050c0bbd44c83ebbcc074ddb1bab936',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBear.sol:HeadwearBear:0xc3c97e8ae72fecd1f2c1351196cb8124648151d5',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBee.sol:HeadwearBee:0xc6f26ae509e39657e408c8ced9b31f324506a3bb',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBirdie.sol:HeadwearBirdie:0xb3fa351ac9cd4c1d8e5723f8e061f56b61abf59a',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBrains.sol:HeadwearBrains:0x99a2505a8884cac0ab04e1726838bec65cf62ec6',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearBull.sol:HeadwearBull:0x8ef9b3be203c677d0e5bfbc5db11400d3afea821',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearCrown.sol:HeadwearCrown:0x6bbd83ca2ec3dbdcf96bd3d01262fb433f8f477b',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearEarrings.sol:HeadwearEarrings:0x4a92d520f527ead8f6f538ca0cbf9409d6a78f74',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearFloral.sol:HeadwearFloral:0x0c715cbfcc827d3861818f431a5c2299cf26b3eb',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearGlasses.sol:HeadwearGlasses:0xb3475daa34935b309a49741242bb2b4095204557',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearHat.sol:HeadwearHat:0x60f002d200b9fc5061b6c32682dced413113e65e',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearIcecream.sol:HeadwearIcecream:0x57d0e1f5ee84fcbf1d4adfb97e5c9a6b6f90b75a',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearLotus.sol:HeadwearLotus:0xb1b59c3eacedd5a648646825b1aba83f1fa44b7f',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearMajor.sol:HeadwearMajor:0x734e08dcc4dfdbefd1fe39eea94821ca0da1944f',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearMushroom.sol:HeadwearMushroom:0xe0be70c71f3d753119bfa686624c7fa3e4c94d1d',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearNightcap.sol:HeadwearNightcap:0x0e93c0412c55db27d4eb0d6cb9187c8de20bd33b',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearPartyhat.sol:HeadwearPartyhat:0x98a5505d61a2680220a6e9ae22c366a06ff1f2e5',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearPlants.sol:HeadwearPlants:0xa0c7ff9758e2a9518e39c09c19851e5e1fce669c',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearScout.sol:HeadwearScout:0xff286a64d376ba77fbe667fb88395c2818cbca74',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearShaman.sol:HeadwearShaman:0x6252c630824e4c4383f4675233e2fd62f1b224ed',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear/HeadwearSparkles.sol:HeadwearSparkles:0xae96209a41caa3042a979b61fe58c7030b06c826',
|
||||
# 'contracts/libraries/svgs/Profile/Headwear.sol:Headwear:0x6f33a4a06ff39342fbd4a737fd3f53db92ffd038',
|
||||
# 'contracts/libraries/svgs/Profile/Legs.sol:Legs:0x3c6a16c4f5aa3a06e7de06b590d69db5fc1b7a69',
|
||||
# 'contracts/libraries/svgs/Profile/Logo.sol:Logo:0x6099032c8d4b93d25a50a8f0a6e2cc66259103bc',
|
||||
# 'contracts/libraries/svgs/Profile/ProfileSVG.sol:ProfileSVG:0xf32f16af9e44cb92311de854d138547bc1f91dc7',
|
||||
# 'contracts/libraries/svgs/Profile/Shoes.sol:Shoes:0x7289854ff2e55c43afcb895ec8cae798811fcc2d'
|
||||
# ]
|
||||
|
||||
# HandleSVG
|
||||
# libraries = [
|
||||
# 'contracts/libraries/svgs/Handle/GintoNordFontSVG.sol:GintoNordFontSVG:0x68b2751c4113bbacca7c024a030355fe3989bda7',
|
||||
# 'contracts/libraries/svgs/Handle/HandleSVG.sol:HandleSVG:0xa2574d9ddb6a325ad2be838bd854228b80215148'
|
||||
# ]
|
||||
|
||||
# FollowSVG
|
||||
# libraries = [
|
||||
# 'contracts/libraries/svgs/Follow/FollowSVG.sol:FollowSVG:0x37cbd2c689a8eb10459d974a824adef74124dc7e'
|
||||
# 'contracts/libraries/ActionLib.sol:ActionLib:0xf76565aee4bf3641bf10090561ff0143628fde99',
|
||||
# 'contracts/libraries/FollowLib.sol:FollowLib:0xd33423b0ae94717274ef83431950a858b274c3f9',
|
||||
# 'contracts/libraries/GovernanceLib.sol:GovernanceLib:0x06df1ff944ed3f3f140fb0afae5f27429f93eba1',
|
||||
# 'contracts/libraries/LegacyCollectLib.sol:LegacyCollectLib:0xe6b308313eff636b82e7ced21cdc05ea7107504a',
|
||||
# 'contracts/libraries/MetaTxLib.sol:MetaTxLib:0xf4a73d59b5c3eeb02d007e4f82c7952cc659c06f',
|
||||
# 'contracts/libraries/MigrationLib.sol:MigrationLib:0x38fd9d33e9cdd1e99b4f9d983db64ed035ead9d4',
|
||||
# 'contracts/libraries/ProfileLib.sol:ProfileLib:0xdf4bdf058490383c9f12a25c2f28679c8154aa8b',
|
||||
# 'contracts/libraries/PublicationLib.sol:PublicationLib:0x70d2a21726bcc835e56680b173b31c38b6c33c64',
|
||||
# 'contracts/libraries/ValidationLib.sol:ValidationLib:0xef1c3a9c87177217574afd66956d203ba8f0ee6c',
|
||||
# ]
|
||||
|
||||
[rpc_endpoints]
|
||||
|
||||
254
script/DeployLensHub.s.sol
Normal file
254
script/DeployLensHub.s.sol
Normal file
@@ -0,0 +1,254 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {ForkManagement} from 'script/helpers/ForkManagement.sol';
|
||||
import 'forge-std/Script.sol';
|
||||
import {LibString} from 'solady/utils/LibString.sol';
|
||||
import {FollowNFT} from 'contracts/FollowNFT.sol';
|
||||
import {LensHubInitializable} from 'contracts/misc/LensHubInitializable.sol';
|
||||
import {Types} from 'contracts/libraries/constants/Types.sol';
|
||||
import {Governance} from 'contracts/misc/access/Governance.sol';
|
||||
import {LensHandles} from 'contracts/namespaces/LensHandles.sol';
|
||||
|
||||
contract DeployLensHub is Script, ForkManagement {
|
||||
using stdJson for string;
|
||||
|
||||
struct LensAccount {
|
||||
uint256 ownerPk;
|
||||
address owner;
|
||||
uint256 profileId;
|
||||
}
|
||||
|
||||
LensAccount _deployer;
|
||||
|
||||
string mnemonic;
|
||||
|
||||
uint256 internal PROFILE_GUARDIAN_COOLDOWN;
|
||||
uint256 internal HANDLE_GUARDIAN_COOLDOWN;
|
||||
|
||||
address lensHub;
|
||||
address legacyCollectNFTImpl;
|
||||
address followNFTImpl;
|
||||
address moduleRegistry;
|
||||
|
||||
address lensHandlesAddress;
|
||||
address tokenHandleRegistryAddress;
|
||||
address legacyFeeFollowModule;
|
||||
address legacyProfileFollowModule;
|
||||
address newFeeFollowModule;
|
||||
address lensHandlesOwner;
|
||||
address lensHandlesImpl;
|
||||
|
||||
address governanceContract;
|
||||
address governanceAdmin;
|
||||
address lensHubV2Impl;
|
||||
|
||||
string addressesFile = 'addressesV2.txt';
|
||||
|
||||
// TODO: Use from test/ContractAddresses
|
||||
struct Module {
|
||||
address addy;
|
||||
string name;
|
||||
}
|
||||
|
||||
// TODO: Move this somewhere common (also in UpgradeForkTest)
|
||||
function findModuleHelper(
|
||||
Module[] memory modules,
|
||||
string memory moduleNameToFind
|
||||
) internal pure returns (Module memory) {
|
||||
for (uint256 i = 0; i < modules.length; i++) {
|
||||
if (LibString.eq(modules[i].name, moduleNameToFind)) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
revert('Module not found');
|
||||
}
|
||||
|
||||
function saveContractAddress(string memory contractName, address deployedAddress) internal {
|
||||
// console.log('Saving %s (%s) into addresses under %s environment', contractName, deployedAddress, targetEnv);
|
||||
string[] memory inputs = new string[](5);
|
||||
inputs[0] = 'node';
|
||||
inputs[1] = 'script/helpers/saveAddress.js';
|
||||
inputs[2] = targetEnv;
|
||||
inputs[3] = contractName;
|
||||
inputs[4] = vm.toString(deployedAddress);
|
||||
// bytes memory res =
|
||||
vm.ffi(inputs);
|
||||
// string memory output = abi.decode(res, (string));
|
||||
// console.log(output);
|
||||
}
|
||||
|
||||
function loadPrivateKeys() internal {
|
||||
if (isEnvSet('MNEMONIC')) {
|
||||
mnemonic = vm.envString('MNEMONIC');
|
||||
}
|
||||
|
||||
if (bytes(mnemonic).length == 0) {
|
||||
revert('Missing mnemonic');
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
(_deployer.owner, _deployer.ownerPk) = deriveRememberKey(mnemonic, 0);
|
||||
console.logBytes32(bytes32(_deployer.ownerPk));
|
||||
console.log('Deployer address: %s', address(_deployer.owner));
|
||||
|
||||
console.log('\n');
|
||||
|
||||
console.log('Current block:', block.number);
|
||||
}
|
||||
|
||||
function loadBaseAddresses() internal override {
|
||||
lensHub = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHubProxy')));
|
||||
vm.label(lensHub, 'LensHub');
|
||||
console.log('Lens Hub Proxy: %s', lensHub);
|
||||
|
||||
legacyCollectNFTImpl = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LegacyCollectNFTImpl')));
|
||||
vm.label(legacyCollectNFTImpl, 'LegacyCollectNFTImpl');
|
||||
console.log('LegacyCollectNFTImpl: %s', legacyCollectNFTImpl);
|
||||
|
||||
moduleRegistry = json.readAddress(string(abi.encodePacked('.', targetEnv, '.ModuleRegistry')));
|
||||
vm.label(moduleRegistry, 'ModuleRegistry');
|
||||
console.log('ModuleRegistry: %s', moduleRegistry);
|
||||
|
||||
PROFILE_GUARDIAN_COOLDOWN = json.readUint(
|
||||
string(abi.encodePacked('.', targetEnv, '.LensProfilesGuardianTimelock'))
|
||||
);
|
||||
console.log('PROFILE_GUARDIAN_COOLDOWN: %s', PROFILE_GUARDIAN_COOLDOWN);
|
||||
|
||||
HANDLE_GUARDIAN_COOLDOWN = json.readUint(
|
||||
string(abi.encodePacked('.', targetEnv, '.LensHandlesGuardianTimelock'))
|
||||
);
|
||||
console.log('HANDLE_GUARDIAN_COOLDOWN: %s', HANDLE_GUARDIAN_COOLDOWN);
|
||||
|
||||
lensHandlesAddress = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHandles')));
|
||||
|
||||
tokenHandleRegistryAddress = json.readAddress(string(abi.encodePacked('.', targetEnv, '.TokenHandleRegistry')));
|
||||
|
||||
Module[] memory followModules = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Modules.v1.follow'))),
|
||||
(Module[])
|
||||
);
|
||||
|
||||
legacyFeeFollowModule = findModuleHelper(followModules, 'FeeFollowModule').addy;
|
||||
vm.label(legacyFeeFollowModule, 'LegacyFeeFollowModule');
|
||||
console.log('Legacy Fee Follow Module: %s', legacyFeeFollowModule);
|
||||
|
||||
legacyProfileFollowModule = findModuleHelper(followModules, 'ProfileFollowModule').addy;
|
||||
vm.label(legacyProfileFollowModule, 'LegacyProfileFollowModule');
|
||||
console.log('Legacy Profile Follow Module: %s', legacyProfileFollowModule);
|
||||
|
||||
followModules = abi.decode(
|
||||
vm.parseJson(json, string(abi.encodePacked('.', targetEnv, '.Modules.v2.follow'))),
|
||||
(Module[])
|
||||
);
|
||||
|
||||
newFeeFollowModule = findModuleHelper(followModules, 'FeeFollowModule').addy;
|
||||
vm.label(newFeeFollowModule, 'NewFeeFollowModule');
|
||||
console.log('New Fee Follow Module: %s', newFeeFollowModule);
|
||||
|
||||
governanceContract = LensHubInitializable(lensHub).getGovernance();
|
||||
governanceAdmin = Governance(governanceContract).owner();
|
||||
|
||||
lensHandlesOwner = governanceAdmin;
|
||||
vm.label(lensHandlesOwner, 'LensHandlesOwner');
|
||||
console.log('LensHandlesOwner: %s', lensHandlesOwner);
|
||||
|
||||
followNFTImpl = json.readAddress(string(abi.encodePacked('.', targetEnv, '.FollowNFTImpl')));
|
||||
vm.label(followNFTImpl, 'FollowNFTImpl');
|
||||
console.log('FollowNFTImpl: %s', followNFTImpl);
|
||||
|
||||
lensHandlesImpl = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHandlesImpl')));
|
||||
vm.label(lensHandlesImpl, 'LensHandlesImpl');
|
||||
console.log('LensHandlesImpl: %s', lensHandlesImpl);
|
||||
}
|
||||
|
||||
function deploy() internal {
|
||||
if (lensHub == address(0)) {
|
||||
console.log('LensHub not set');
|
||||
revert('LensHub not set');
|
||||
}
|
||||
|
||||
if (lensHandlesOwner == address(0)) {
|
||||
console.log('lensHandlesOwner not set');
|
||||
revert('lensHandlesOwner not set');
|
||||
}
|
||||
|
||||
if (HANDLE_GUARDIAN_COOLDOWN == 0) {
|
||||
console.log('HANDLE_GUARDIAN_COOLDOWN not set');
|
||||
revert('HANDLE_GUARDIAN_COOLDOWN not set');
|
||||
}
|
||||
|
||||
if (legacyCollectNFTImpl == address(0)) {
|
||||
console.log('LegacyCollectNFTImpl not set');
|
||||
revert('LegacyCollectNFTImpl not set');
|
||||
}
|
||||
|
||||
if (moduleRegistry == address(0)) {
|
||||
console.log('ModuleRegistry not set');
|
||||
revert('ModuleRegistry not set');
|
||||
}
|
||||
|
||||
if (PROFILE_GUARDIAN_COOLDOWN == 0) {
|
||||
console.log('PROFILE_GUARDIAN_COOLDOWN not set');
|
||||
revert('PROFILE_GUARDIAN_COOLDOWN not set');
|
||||
}
|
||||
|
||||
console.log('PROFILE_GUARDIAN_COOLDOWN: %s', PROFILE_GUARDIAN_COOLDOWN);
|
||||
|
||||
// Pass all the fucking shit and deploy LensHub V2 Impl with:
|
||||
vm.startBroadcast(_deployer.ownerPk);
|
||||
lensHubV2Impl = address(
|
||||
new LensHubInitializable(
|
||||
followNFTImpl,
|
||||
legacyCollectNFTImpl,
|
||||
moduleRegistry,
|
||||
PROFILE_GUARDIAN_COOLDOWN,
|
||||
Types.MigrationParams({
|
||||
lensHandlesAddress: lensHandlesAddress,
|
||||
tokenHandleRegistryAddress: tokenHandleRegistryAddress,
|
||||
legacyFeeFollowModule: legacyFeeFollowModule,
|
||||
legacyProfileFollowModule: legacyProfileFollowModule,
|
||||
newFeeFollowModule: newFeeFollowModule
|
||||
})
|
||||
)
|
||||
);
|
||||
vm.stopBroadcast();
|
||||
|
||||
console.log('"arguments": [');
|
||||
console.log('\t"%s"', followNFTImpl);
|
||||
console.log('\t"%s"', legacyCollectNFTImpl);
|
||||
console.log('\t"%s"', moduleRegistry);
|
||||
console.log('\t"%s"', PROFILE_GUARDIAN_COOLDOWN);
|
||||
console.log(
|
||||
'\t"%s"',
|
||||
string.concat(
|
||||
'(',
|
||||
vm.toString(lensHandlesAddress),
|
||||
', ',
|
||||
vm.toString(tokenHandleRegistryAddress),
|
||||
', ',
|
||||
vm.toString(legacyFeeFollowModule),
|
||||
', ',
|
||||
vm.toString(legacyProfileFollowModule),
|
||||
', ',
|
||||
vm.toString(newFeeFollowModule),
|
||||
')'
|
||||
)
|
||||
);
|
||||
console.log(']');
|
||||
|
||||
vm.writeLine(addressesFile, string.concat('LensHubV2Impl: ', vm.toString(lensHubV2Impl)));
|
||||
saveContractAddress('LensHubV2Impl', lensHubV2Impl);
|
||||
console.log('LensHubV2Impl: %s', lensHubV2Impl);
|
||||
}
|
||||
|
||||
function run(string memory targetEnv_) external {
|
||||
targetEnv = targetEnv_;
|
||||
loadJson();
|
||||
checkNetworkParams();
|
||||
loadBaseAddresses();
|
||||
loadPrivateKeys();
|
||||
deploy();
|
||||
}
|
||||
}
|
||||
186
script/DeployPermissionlessCreator.s.sol
Normal file
186
script/DeployPermissionlessCreator.s.sol
Normal file
@@ -0,0 +1,186 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {ForkManagement} from 'script/helpers/ForkManagement.sol';
|
||||
import 'forge-std/Script.sol';
|
||||
import {LibString} from 'solady/utils/LibString.sol';
|
||||
import {Types} from 'contracts/libraries/constants/Types.sol';
|
||||
import {Governance} from 'contracts/misc/access/Governance.sol';
|
||||
import {PermissionlessCreator} from 'contracts/misc/PermissionlessCreator.sol';
|
||||
import {TransparentUpgradeableProxy} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
|
||||
import {LensHubInitializable} from 'contracts/misc/LensHubInitializable.sol';
|
||||
|
||||
contract DeployPermissionlessCreator is Script, ForkManagement {
|
||||
using stdJson for string;
|
||||
|
||||
struct LensAccount {
|
||||
uint256 ownerPk;
|
||||
address owner;
|
||||
uint256 profileId;
|
||||
}
|
||||
|
||||
LensAccount _deployer;
|
||||
|
||||
string mnemonic;
|
||||
|
||||
uint256 internal PROFILE_GUARDIAN_COOLDOWN;
|
||||
uint256 internal HANDLE_GUARDIAN_COOLDOWN;
|
||||
|
||||
address lensHub;
|
||||
|
||||
address lensHandlesAddress;
|
||||
address tokenHandleRegistryAddress;
|
||||
|
||||
address governanceContract;
|
||||
address governanceAdmin;
|
||||
|
||||
address proxyAdminContractAdmin;
|
||||
|
||||
address permissionlessCreatorImpl;
|
||||
address permissionlessCreator;
|
||||
|
||||
string addressesFile = 'addressesV2.txt';
|
||||
|
||||
// TODO: Use from test/ContractAddresses
|
||||
struct Module {
|
||||
address addy;
|
||||
string name;
|
||||
}
|
||||
|
||||
// TODO: Move this somewhere common (also in UpgradeForkTest)
|
||||
function findModuleHelper(
|
||||
Module[] memory modules,
|
||||
string memory moduleNameToFind
|
||||
) internal pure returns (Module memory) {
|
||||
for (uint256 i = 0; i < modules.length; i++) {
|
||||
if (LibString.eq(modules[i].name, moduleNameToFind)) {
|
||||
return modules[i];
|
||||
}
|
||||
}
|
||||
revert('Module not found');
|
||||
}
|
||||
|
||||
function saveContractAddress(string memory contractName, address deployedAddress) internal {
|
||||
// console.log('Saving %s (%s) into addresses under %s environment', contractName, deployedAddress, targetEnv);
|
||||
string[] memory inputs = new string[](5);
|
||||
inputs[0] = 'node';
|
||||
inputs[1] = 'script/helpers/saveAddress.js';
|
||||
inputs[2] = targetEnv;
|
||||
inputs[3] = contractName;
|
||||
inputs[4] = vm.toString(deployedAddress);
|
||||
// bytes memory res =
|
||||
vm.ffi(inputs);
|
||||
// string memory output = abi.decode(res, (string));
|
||||
// console.log(output);
|
||||
}
|
||||
|
||||
function loadPrivateKeys() internal {
|
||||
if (isEnvSet('MNEMONIC')) {
|
||||
mnemonic = vm.envString('MNEMONIC');
|
||||
}
|
||||
|
||||
if (bytes(mnemonic).length == 0) {
|
||||
revert('Missing mnemonic');
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
(_deployer.owner, _deployer.ownerPk) = deriveRememberKey(mnemonic, 0);
|
||||
// console.logBytes32(bytes32(_deployer.ownerPk));
|
||||
console.log('Deployer address: %s', address(_deployer.owner));
|
||||
|
||||
console.log('\n');
|
||||
|
||||
console.log('Current block:', block.number);
|
||||
}
|
||||
|
||||
function loadBaseAddresses() internal override {
|
||||
lensHub = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHubProxy')));
|
||||
vm.label(lensHub, 'LensHub');
|
||||
console.log('Lens Hub Proxy: %s', lensHub);
|
||||
|
||||
lensHandlesAddress = json.readAddress(string(abi.encodePacked('.', targetEnv, '.LensHandles')));
|
||||
|
||||
tokenHandleRegistryAddress = json.readAddress(string(abi.encodePacked('.', targetEnv, '.TokenHandleRegistry')));
|
||||
|
||||
governanceContract = LensHubInitializable(lensHub).getGovernance();
|
||||
vm.label(governanceContract, 'Governance');
|
||||
console.log('Governance Contract: %s', governanceContract);
|
||||
|
||||
governanceAdmin = Governance(governanceContract).owner();
|
||||
vm.label(governanceAdmin, 'GovernanceAdmin');
|
||||
console.log('Governance Contract Admin: %s', governanceAdmin);
|
||||
|
||||
proxyAdminContractAdmin = json.readAddress(
|
||||
string(abi.encodePacked('.', targetEnv, '.ProxyAdminContractAdmin'))
|
||||
);
|
||||
vm.label(proxyAdminContractAdmin, 'ProxyAdminContractAdmin');
|
||||
console.log('ProxyAdmin Contract Admin: %s', proxyAdminContractAdmin);
|
||||
}
|
||||
|
||||
function deploy() internal {
|
||||
if (lensHub == address(0)) {
|
||||
console.log('LensHub not set');
|
||||
revert('LensHub not set');
|
||||
}
|
||||
|
||||
if (proxyAdminContractAdmin == address(0)) {
|
||||
console.log('ProxyAdminContractAdmin not set');
|
||||
revert('ProxyAdminContractAdmin not set');
|
||||
}
|
||||
|
||||
if (lensHandlesAddress == address(0)) {
|
||||
console.log('lensHandlesAddress not set');
|
||||
revert('lensHandlesAddress not set');
|
||||
}
|
||||
|
||||
if (tokenHandleRegistryAddress == address(0)) {
|
||||
console.log('tokenHandleRegistryAddress not set');
|
||||
revert('tokenHandleRegistryAddress not set');
|
||||
}
|
||||
|
||||
vm.startBroadcast(_deployer.ownerPk);
|
||||
|
||||
permissionlessCreatorImpl = address(
|
||||
new PermissionlessCreator(governanceAdmin, lensHub, lensHandlesAddress, tokenHandleRegistryAddress)
|
||||
);
|
||||
|
||||
// Make PermissionlessCreator a transparentUpgradeableProxy
|
||||
// permissionlessCreator = address(
|
||||
// new TransparentUpgradeableProxy(permissionlessCreatorImpl, proxyAdminContractAdmin, '')
|
||||
// );
|
||||
vm.stopBroadcast();
|
||||
|
||||
console.log('\n\n---------------------------------------------------');
|
||||
console.log('PermissionlessCreator params:');
|
||||
console.log('\towner: %s', governanceAdmin);
|
||||
console.log('\tlensHub: %s', lensHub);
|
||||
console.log('\tlensHandlesAddress: %s', lensHandlesAddress);
|
||||
console.log('\ttokenHandleRegistryAddress: %s', tokenHandleRegistryAddress);
|
||||
|
||||
console.log('\n\n---------------------------------------------------');
|
||||
console.log('PermissionlessCreator TransparentProxy params:');
|
||||
console.log('\timplementation: %s', permissionlessCreatorImpl);
|
||||
console.log('\tadmin: %s', proxyAdminContractAdmin);
|
||||
|
||||
vm.writeLine(
|
||||
addressesFile,
|
||||
string.concat('PermissionlessCreatorImpl: ', vm.toString(permissionlessCreatorImpl))
|
||||
);
|
||||
saveContractAddress('PermissionlessCreatorImpl', permissionlessCreatorImpl);
|
||||
console.log('\n\nPermissionlessCreatorImpl: %s', permissionlessCreatorImpl);
|
||||
|
||||
vm.writeLine(addressesFile, string.concat('PermissionlessCreator: ', vm.toString(permissionlessCreator)));
|
||||
saveContractAddress('PermissionlessCreator', permissionlessCreator);
|
||||
console.log('PermissionlessCreator: %s', permissionlessCreator);
|
||||
}
|
||||
|
||||
function run(string memory targetEnv_) external {
|
||||
targetEnv = targetEnv_;
|
||||
loadJson();
|
||||
checkNetworkParams();
|
||||
loadBaseAddresses();
|
||||
loadPrivateKeys();
|
||||
deploy();
|
||||
}
|
||||
}
|
||||
27
script/forkUpgradeLensHub.sh
Normal file
27
script/forkUpgradeLensHub.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
source .env
|
||||
|
||||
set -e
|
||||
|
||||
TARGET=$1
|
||||
|
||||
if [[ "$TARGET" == "" ]]
|
||||
then
|
||||
echo "No TARGET specified. Terminating"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using target: $TARGET"
|
||||
|
||||
|
||||
IMPLEMENTATION_SLOT="0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
|
||||
LENSHUB=$(node script/helpers/readAddress.js $TARGET LensHubProxy)
|
||||
PROXY_CONTRACT=$(node script/helpers/readAddress.js $TARGET ProxyAdminContract)
|
||||
PROXY_CONTRACT_OWNER=$(cast call $PROXY_CONTRACT "owner()(address)")
|
||||
|
||||
cast rpc anvil_impersonateAccount $PROXY_CONTRACT_OWNER
|
||||
cast send $PROXY_CONTRACT "proxy_upgrade(address)" "0xb4A26f55Cc2d1473b8A7649d90d34ba52A480391" --unlocked --from $PROXY_CONTRACT_OWNER
|
||||
|
||||
NEW_IMPLEMENTATION=$(cast parse-bytes32-address $(cast storage $LENSHUB $IMPLEMENTATION_SLOT))
|
||||
|
||||
echo "Successfully upgraded LensHub to $NEW_IMPLEMENTATION"
|
||||
|
||||
cast rpc anvil_stopImpersonatingAccount $PROXY_CONTRACT_OWNER
|
||||
@@ -39,7 +39,7 @@ CALLDATA=$(cast calldata "run(string)" $TARGET)
|
||||
echo "Interactions calldata:"
|
||||
echo "$CALLDATA"
|
||||
|
||||
forge script script/$SCRIPT_NAME.s.sol:$SCRIPT_NAME -s $CALLDATA --rpc-url $NETWORK -vv --legacy --skip test --ffi
|
||||
forge script script/$SCRIPT_NAME.s.sol:$SCRIPT_NAME --sig $CALLDATA --rpc-url $NETWORK -vv --legacy --skip test --ffi
|
||||
|
||||
# If the confirmation override is set to s or S - then we skip the rest of the script and exit with success
|
||||
if [[ "$CONFIRMATION_OVERRIDE" == "s" || "$CONFIRMATION_OVERRIDE" == "S" ]]
|
||||
@@ -83,10 +83,10 @@ if [[ "$CONFIRMATION" == "y" || "$CONFIRMATION" == "Y" ]]
|
||||
NETWORK="matic"
|
||||
fi
|
||||
|
||||
catapulta script script/$SCRIPT_NAME.s.sol --chain $NETWORK -s $CALLDATA --legacy --skip test --ffi --slow --skip-git
|
||||
catapulta script script/$SCRIPT_NAME.s.sol --chain $NETWORK --sig $CALLDATA --legacy --skip test --ffi --slow --skip-git
|
||||
exit 0
|
||||
else
|
||||
forge script script/$SCRIPT_NAME.s.sol:$SCRIPT_NAME -s $CALLDATA --rpc-url $NETWORK -vv --legacy --skip test --ffi --slow --broadcast
|
||||
forge script script/$SCRIPT_NAME.s.sol:$SCRIPT_NAME --sig $CALLDATA --rpc-url $NETWORK -vv --legacy --skip test --ffi --slow --broadcast
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
# (It is normal for the numbers in end of type names to be different)
|
||||
source .env
|
||||
|
||||
set -e
|
||||
|
||||
if [[ $(colordiff --help 2>/dev/null) ]]
|
||||
then
|
||||
shopt -s expand_aliases
|
||||
@@ -65,10 +67,10 @@ if [[ $NETWORK == "" ]]
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $NETWORK == "mumbai" ]]
|
||||
then
|
||||
NETWORK="polygon-mumbai"
|
||||
fi
|
||||
# if [[ $NETWORK == "mumbai" ]]
|
||||
# then
|
||||
# NETWORK="polygon-mumbai"
|
||||
# fi
|
||||
|
||||
PROXY_ADDRESS=$(node script/helpers/readAddress.js $TARGET ${PROXY_NAME})
|
||||
NEW_IMPLEMENTATION_ADDRESS=$(node script/helpers/readAddress.js $TARGET ${NEW_IMPL_NAME})
|
||||
|
||||
499
test/TransferKeepingDelegatesTest.t.sol
Normal file
499
test/TransferKeepingDelegatesTest.t.sol
Normal file
@@ -0,0 +1,499 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import 'test/base/BaseTest.t.sol';
|
||||
import 'test/LensBaseERC721Test.t.sol';
|
||||
import {Base64} from 'solady/utils/Base64.sol';
|
||||
import {LibString} from 'solady/utils/LibString.sol';
|
||||
import {ProfileTokenURI} from 'contracts/misc/token-uris/ProfileTokenURI.sol';
|
||||
import {IProfileTokenURI} from 'contracts/interfaces/IProfileTokenURI.sol';
|
||||
import {ILensProfiles} from 'contracts/interfaces/ILensProfiles.sol';
|
||||
import {MockTokenHolderContract} from 'test/mocks/MockTokenHolderContract.sol';
|
||||
import {Address} from '@openzeppelin/contracts/utils/Address.sol';
|
||||
|
||||
interface IGuardedToken is IERC721 {
|
||||
function DANGER__disableTokenGuardian() external;
|
||||
|
||||
function enableTokenGuardian() external;
|
||||
|
||||
function getTokenGuardianDisablingTimestamp(address wallet) external view returns (uint256);
|
||||
|
||||
function transferFromKeepingDelegates(address from, address to, uint256 tokenId) external;
|
||||
|
||||
function burn(uint256 tokenId) external;
|
||||
}
|
||||
|
||||
contract TransferKeepingDelegatesTest is BaseTest {
|
||||
using Address for address;
|
||||
|
||||
function _getERC721TokenAddress() internal view virtual returns (address) {
|
||||
return address(hub);
|
||||
}
|
||||
|
||||
function _LensProfiles() private view returns (ILensProfiles) {
|
||||
return ILensProfiles(_getERC721TokenAddress());
|
||||
}
|
||||
|
||||
function _mintERC721(address to) internal virtual returns (uint256) {
|
||||
vm.assume(!_isLensHubProxyAdmin(to));
|
||||
return _createProfile(to);
|
||||
}
|
||||
|
||||
function _burnERC721(uint256 tokenId) internal virtual {
|
||||
return hub.burn(tokenId);
|
||||
}
|
||||
|
||||
function _disableGuardian(address wallet) internal {
|
||||
_effectivelyDisableProfileGuardian(wallet);
|
||||
}
|
||||
|
||||
function _assumeNotProxyAdmin(address account) internal view virtual {
|
||||
vm.assume(!_isLensHubProxyAdmin(account));
|
||||
}
|
||||
|
||||
function _TOKEN_GUARDIAN_COOLDOWN() internal view returns (uint256) {
|
||||
return fork ? hub.TOKEN_GUARDIAN_COOLDOWN() : PROFILE_GUARDIAN_COOLDOWN;
|
||||
}
|
||||
|
||||
function _guardedToken() private view returns (IGuardedToken) {
|
||||
return IGuardedToken(_getERC721TokenAddress());
|
||||
}
|
||||
|
||||
MockTokenHolderContract tokenHolderContract;
|
||||
uint256 tokenIdHeldByEOA;
|
||||
uint256 tokenIdHeldByNonEOA;
|
||||
|
||||
function setUp() public virtual override {
|
||||
super.setUp();
|
||||
tokenHolderContract = new MockTokenHolderContract();
|
||||
tokenHolderContract.setCollection(address(_guardedToken()));
|
||||
tokenIdHeldByEOA = _mintERC721(defaultAccount.owner);
|
||||
tokenIdHeldByNonEOA = _mintERC721(address(this));
|
||||
_guardedToken().safeTransferFrom(address(this), address(tokenHolderContract), tokenIdHeldByNonEOA);
|
||||
}
|
||||
|
||||
// TokenGuardian tests
|
||||
|
||||
function testCannot_transferFrom_ifEOA_andTokenGuardianEnabled(address to) public {
|
||||
vm.assume(to != address(0));
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
vm.expectRevert(Errors.GuardianEnabled.selector);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, to, tokenIdHeldByEOA);
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), defaultAccount.owner);
|
||||
}
|
||||
|
||||
function testCannot_transferFrom_ifEOA_andTokenGuardianDisabled_butNotTakenEffectYet(
|
||||
uint256 elapsedTimeAfterDisabling,
|
||||
address to
|
||||
) public {
|
||||
vm.assume(to != address(0));
|
||||
elapsedTimeAfterDisabling = bound(elapsedTimeAfterDisabling, 0, _TOKEN_GUARDIAN_COOLDOWN() - 1);
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().DANGER__disableTokenGuardian();
|
||||
|
||||
vm.warp(block.timestamp + elapsedTimeAfterDisabling);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
vm.expectRevert(Errors.GuardianEnabled.selector);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, to, tokenIdHeldByEOA);
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), defaultAccount.owner);
|
||||
}
|
||||
|
||||
function testTransferFrom_ifEOA_onlyAfterTokenGuardianIsEffectivelyDisabled(address to) public {
|
||||
vm.assume(to != address(0));
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), defaultAccount.owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, to, tokenIdHeldByEOA);
|
||||
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), to);
|
||||
}
|
||||
|
||||
function testApprovalStateDoesNotChange_afterProtectionStateChanges(address anotherAddress) public {
|
||||
vm.assume(anotherAddress != address(0));
|
||||
vm.assume(anotherAddress != defaultAccount.owner);
|
||||
|
||||
// Disable protection
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), defaultAccount.owner);
|
||||
|
||||
// Approve
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().approve(anotherAddress, tokenIdHeldByEOA);
|
||||
|
||||
// Approve state has changed
|
||||
assertEq(_guardedToken().getApproved(tokenIdHeldByEOA), anotherAddress);
|
||||
|
||||
// Enable protection
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().enableTokenGuardian();
|
||||
|
||||
// Approve state remains the same after enabling protection
|
||||
assertEq(_guardedToken().getApproved(tokenIdHeldByEOA), anotherAddress);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
// But, you cannot transfer even if approved, because the protection is enabled
|
||||
vm.prank(defaultAccount.owner);
|
||||
vm.expectRevert(Errors.GuardianEnabled.selector);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, anotherAddress, tokenIdHeldByEOA);
|
||||
}
|
||||
|
||||
function testApproveForAllState_DoesNotChange_AfterGuardianStateChanges(address anotherAddress) public {
|
||||
vm.assume(anotherAddress != address(0));
|
||||
vm.assume(anotherAddress != defaultAccount.owner);
|
||||
|
||||
// Disable protection
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), defaultAccount.owner);
|
||||
|
||||
// ApproveForAll
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().setApprovalForAll(anotherAddress, true);
|
||||
|
||||
// ApproveForAll state has changed
|
||||
assertTrue(_guardedToken().isApprovedForAll(defaultAccount.owner, anotherAddress));
|
||||
|
||||
// Enable protection
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().enableTokenGuardian();
|
||||
|
||||
// ApproveForAll state remains the same after enabling protection
|
||||
assertTrue(_guardedToken().isApprovedForAll(defaultAccount.owner, anotherAddress));
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
// But, you cannot transfer even if ApprovedForAll, because the protection is enabled
|
||||
vm.prank(defaultAccount.owner);
|
||||
vm.expectRevert(Errors.GuardianEnabled.selector);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, anotherAddress, tokenIdHeldByEOA);
|
||||
}
|
||||
|
||||
function testTransfersDoesNotAffectProtectionState_InboundTransfer(address anotherAddress) public {
|
||||
vm.assume(anotherAddress != address(0));
|
||||
vm.assume(anotherAddress != defaultAccount.owner);
|
||||
vm.assume(anotherAddress.code.length == 0);
|
||||
|
||||
// UserTwo does not have any profile
|
||||
vm.assume(_guardedToken().balanceOf(anotherAddress) == 0);
|
||||
|
||||
// User disables protection, so it can perform a transfer later
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), defaultAccount.owner);
|
||||
|
||||
// UserTwo disables protection
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), anotherAddress);
|
||||
|
||||
// UserTwo ApproveForAll User
|
||||
vm.prank(anotherAddress);
|
||||
_guardedToken().setApprovalForAll(defaultAccount.owner, true);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
// UserTwo receives a profile from User
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, anotherAddress, tokenIdHeldByEOA);
|
||||
|
||||
// UserTwo now holds the profile
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), anotherAddress);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(anotherAddress, true);
|
||||
|
||||
// The profile is unprotected, and User is ApproveForAll by UserTwo, so can transfer the profile back
|
||||
vm.prank(anotherAddress);
|
||||
_guardedToken().transferFromKeepingDelegates(anotherAddress, defaultAccount.owner, tokenIdHeldByEOA);
|
||||
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), defaultAccount.owner);
|
||||
}
|
||||
|
||||
function testTransfersDoNotAffectProtectionState_OutboundTransfer(address anotherAddress) public {
|
||||
vm.assume(anotherAddress != address(0));
|
||||
vm.assume(anotherAddress != defaultAccount.owner);
|
||||
|
||||
// Disables protection
|
||||
_effectivelyDisableGuardian(address(_guardedToken()), defaultAccount.owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(defaultAccount.owner, true);
|
||||
|
||||
// Transfers the profile to UserTwo
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().transferFromKeepingDelegates(defaultAccount.owner, anotherAddress, tokenIdHeldByEOA);
|
||||
|
||||
// User does not have the profile anymore
|
||||
assertEq(_guardedToken().ownerOf(tokenIdHeldByEOA), anotherAddress);
|
||||
|
||||
// Transfers does not affect protection state, so User can execute ApproveForAll even after transfer
|
||||
vm.prank(defaultAccount.owner);
|
||||
_guardedToken().setApprovalForAll(anotherAddress, true);
|
||||
assertTrue(_guardedToken().isApprovedForAll(defaultAccount.owner, anotherAddress));
|
||||
}
|
||||
|
||||
// General ERC721.TransferFrom tests
|
||||
|
||||
function testTransferFromKeepingDelegates_SenderIsApproved(address owner, address approvedTo, address to) public {
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(approvedTo != address(0));
|
||||
vm.assume(owner != approvedTo);
|
||||
vm.assume(to != address(0));
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
_disableGuardian(owner);
|
||||
|
||||
vm.prank(owner);
|
||||
_LensProfiles().approve(approvedTo, tokenId);
|
||||
|
||||
uint256 ownerBalanceBefore = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceBefore = _LensProfiles().balanceOf(to);
|
||||
|
||||
_assumeNotProxyAdmin(approvedTo);
|
||||
|
||||
_disableGuardian(approvedTo);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(approvedTo, true);
|
||||
|
||||
vm.prank(approvedTo);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
|
||||
uint256 ownerBalanceAfter = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceAfter = _LensProfiles().balanceOf(to);
|
||||
|
||||
assertEq(_LensProfiles().getApproved(tokenId), address(0));
|
||||
assertEq(_LensProfiles().ownerOf(tokenId), to);
|
||||
|
||||
if (owner != to) {
|
||||
assertEq(ownerBalanceAfter, ownerBalanceBefore - 1);
|
||||
assertEq(toBalanceAfter, toBalanceBefore + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function testTransferFromKeepingDelegates_SenderIsTheOwner(address owner, address to) public {
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(to != address(0));
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
uint256 ownerBalanceBefore = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceBefore = _LensProfiles().balanceOf(to);
|
||||
|
||||
_disableGuardian(owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(owner, true);
|
||||
|
||||
vm.prank(owner);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
|
||||
uint256 ownerBalanceAfter = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceAfter = _LensProfiles().balanceOf(to);
|
||||
|
||||
assertEq(_LensProfiles().getApproved(tokenId), address(0));
|
||||
assertEq(_LensProfiles().ownerOf(tokenId), to);
|
||||
|
||||
if (owner != to) {
|
||||
assertEq(ownerBalanceAfter, ownerBalanceBefore - 1);
|
||||
assertEq(toBalanceAfter, toBalanceBefore + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function testTransferFromKeepingDelegates_SenderIsApprovedForAll(
|
||||
address owner,
|
||||
address approvedTo,
|
||||
address to
|
||||
) public {
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(approvedTo != address(0));
|
||||
vm.assume(owner != approvedTo);
|
||||
vm.assume(to != address(0));
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
_disableGuardian(owner);
|
||||
|
||||
vm.prank(owner);
|
||||
_LensProfiles().setApprovalForAll(approvedTo, true);
|
||||
|
||||
uint256 ownerBalanceBefore = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceBefore = _LensProfiles().balanceOf(to);
|
||||
|
||||
_assumeNotProxyAdmin(approvedTo);
|
||||
|
||||
_disableGuardian(approvedTo);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(approvedTo, true);
|
||||
|
||||
vm.prank(approvedTo);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
|
||||
uint256 ownerBalanceAfter = _LensProfiles().balanceOf(owner);
|
||||
uint256 toBalanceAfter = _LensProfiles().balanceOf(to);
|
||||
|
||||
assertEq(_LensProfiles().ownerOf(tokenId), to);
|
||||
|
||||
if (owner != to) {
|
||||
assertEq(ownerBalanceAfter, ownerBalanceBefore - 1);
|
||||
assertEq(toBalanceAfter, toBalanceBefore + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function testCannot_TransferFromKeepingDelegates_NotOwner(address owner, address to, address otherAddress) public {
|
||||
vm.assume(owner != to);
|
||||
vm.assume(owner != otherAddress);
|
||||
vm.assume(to != address(0));
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(otherAddress != address(0));
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
_assumeNotProxyAdmin(otherAddress);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(otherAddress, true);
|
||||
|
||||
vm.expectRevert(Errors.NotOwnerOrApproved.selector);
|
||||
vm.prank(otherAddress);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
}
|
||||
|
||||
function testCannotTransferFromKeepingDelegates_WrongFromParameter_SenderOwner(
|
||||
address owner,
|
||||
address from,
|
||||
address to
|
||||
) public {
|
||||
_assumeNotProxyAdmin(owner);
|
||||
vm.assume(owner != to);
|
||||
vm.assume(owner != from);
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(to != address(0));
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(owner, true);
|
||||
|
||||
vm.expectRevert(Errors.InvalidOwner.selector);
|
||||
|
||||
vm.prank(owner);
|
||||
_LensProfiles().transferFromKeepingDelegates(from, to, tokenId);
|
||||
}
|
||||
|
||||
function testCannot_TransferFromKeepingDelegates_NonexistingToken(
|
||||
uint256 tokenId,
|
||||
address from,
|
||||
address to
|
||||
) public {
|
||||
vm.assume(from != address(0));
|
||||
vm.assume(to != address(0));
|
||||
|
||||
vm.assume(_LensProfiles().exists(tokenId) == false);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(address(this), true);
|
||||
|
||||
vm.expectRevert(Errors.TokenDoesNotExist.selector);
|
||||
_LensProfiles().transferFromKeepingDelegates(from, to, tokenId);
|
||||
}
|
||||
|
||||
function testCannot_TransferFromKeepingDelegates_ToZero(address owner) public {
|
||||
vm.assume(owner != address(0));
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(owner, true);
|
||||
|
||||
vm.expectRevert(Errors.InvalidParameter.selector);
|
||||
|
||||
vm.prank(owner);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, address(0), tokenId);
|
||||
}
|
||||
|
||||
// Tests list for TransferFromKeepingDelegates function:
|
||||
|
||||
// Negatives
|
||||
|
||||
function testCannot_TransferFromKeepingDelegates_IfNotWhitelistedProfileCreator(
|
||||
address owner,
|
||||
address to,
|
||||
address approvedTo
|
||||
) public {
|
||||
vm.assume(owner != to);
|
||||
vm.assume(to != address(0));
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(approvedTo != address(0));
|
||||
vm.assume(hub.isProfileCreatorWhitelisted(approvedTo) == false);
|
||||
_assumeNotProxyAdmin(approvedTo);
|
||||
_assumeNotProxyAdmin(owner);
|
||||
|
||||
_disableGuardian(owner);
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
if (owner != approvedTo) {
|
||||
vm.prank(owner);
|
||||
_LensProfiles().approve(approvedTo, tokenId);
|
||||
}
|
||||
|
||||
vm.expectRevert(Errors.NotAllowed.selector);
|
||||
vm.prank(approvedTo);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
}
|
||||
|
||||
// Scenarios
|
||||
|
||||
function testTransferFromKeepingDelegates(address owner, address to, address approvedTo) public {
|
||||
vm.assume(owner != to);
|
||||
vm.assume(to != address(0));
|
||||
vm.assume(owner != address(0));
|
||||
vm.assume(approvedTo != address(0));
|
||||
_assumeNotProxyAdmin(approvedTo);
|
||||
|
||||
_disableGuardian(owner);
|
||||
|
||||
uint256 tokenId = _mintERC721(owner);
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(approvedTo, true);
|
||||
|
||||
if (owner != approvedTo) {
|
||||
vm.prank(owner);
|
||||
_LensProfiles().approve(approvedTo, tokenId);
|
||||
}
|
||||
|
||||
address[] memory delegatedExecutors = new address[](3);
|
||||
delegatedExecutors[0] = makeAddr('DE0');
|
||||
delegatedExecutors[1] = makeAddr('DE1');
|
||||
delegatedExecutors[2] = makeAddr('DE2');
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
vm.prank(owner);
|
||||
hub.changeDelegatedExecutorsConfig(tokenId, delegatedExecutors, executorEnabled);
|
||||
|
||||
vm.prank(approvedTo);
|
||||
_LensProfiles().transferFromKeepingDelegates(owner, to, tokenId);
|
||||
|
||||
for (uint256 i = 0; i < delegatedExecutors.length; i++) {
|
||||
assertTrue(hub.isDelegatedExecutorApproved(tokenId, delegatedExecutors[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
101
test/misc/FreeCreditsProviderTest.t.sol
Normal file
101
test/misc/FreeCreditsProviderTest.t.sol
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import 'test/base/BaseTest.t.sol';
|
||||
import {PermissionlessCreator} from 'contracts/misc/PermissionlessCreator.sol';
|
||||
import {FreeCreditsProvider} from 'contracts/misc/FreeCreditsProvider.sol';
|
||||
import {Types} from 'contracts/libraries/constants/Types.sol';
|
||||
|
||||
contract FreeCreditsProviderTest is BaseTest {
|
||||
error OnlyOwner();
|
||||
|
||||
using stdJson for string;
|
||||
|
||||
PermissionlessCreator permissionlessCreator;
|
||||
address permissionlessCreatorOwner = makeAddr('PERMISSIONLESS_CREATOR_OWNER');
|
||||
|
||||
FreeCreditsProvider freeCreditsProvider;
|
||||
|
||||
function setUp() public virtual override {
|
||||
super.setUp();
|
||||
|
||||
if (fork) {
|
||||
if (keyExists(json, string(abi.encodePacked('.', forkEnv, '.PermissionlessCreator')))) {
|
||||
permissionlessCreator = PermissionlessCreator(
|
||||
json.readAddress(string(abi.encodePacked('.', forkEnv, '.PermissionlessCreator')))
|
||||
);
|
||||
permissionlessCreatorOwner = permissionlessCreator.OWNER();
|
||||
} else {
|
||||
console.log('PermissionlessCreator key does not exist');
|
||||
if (forkVersion == 1) {
|
||||
console.log('No PermissionlessCreator address found - deploying new one');
|
||||
permissionlessCreator = new PermissionlessCreator(
|
||||
permissionlessCreatorOwner,
|
||||
address(hub),
|
||||
address(lensHandles),
|
||||
address(tokenHandleRegistry)
|
||||
);
|
||||
} else {
|
||||
console.log('No PermissionlessCreator address found in addressBook, which is required for V2');
|
||||
revert('No PermissionlessCreator address found in addressBook, which is required for V2');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
permissionlessCreator = new PermissionlessCreator(
|
||||
permissionlessCreatorOwner,
|
||||
address(hub),
|
||||
address(lensHandles),
|
||||
address(tokenHandleRegistry)
|
||||
);
|
||||
vm.startPrank(permissionlessCreatorOwner);
|
||||
permissionlessCreator.setHandleCreationPrice(5 ether);
|
||||
permissionlessCreator.setProfileCreationPrice(5 ether);
|
||||
permissionlessCreator.setHandleLengthMin(5);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
vm.prank(governance);
|
||||
hub.whitelistProfileCreator(address(permissionlessCreator), true);
|
||||
|
||||
freeCreditsProvider = new FreeCreditsProvider(address(permissionlessCreator));
|
||||
|
||||
vm.prank(permissionlessCreatorOwner);
|
||||
permissionlessCreator.addCreditProvider(address(freeCreditsProvider));
|
||||
}
|
||||
|
||||
// Scenarios
|
||||
function testIncreaseCredit(address profileCreator, address txSender) public {
|
||||
vm.assume(profileCreator != address(0));
|
||||
vm.assume(profileCreator != address(permissionlessCreator));
|
||||
vm.assume(profileCreator != address(freeCreditsProvider));
|
||||
vm.assume(txSender != address(0));
|
||||
vm.assume(txSender != address(permissionlessCreator));
|
||||
vm.assume(txSender != address(freeCreditsProvider));
|
||||
|
||||
uint256 creditsBefore = permissionlessCreator.getCreditBalance(profileCreator);
|
||||
|
||||
vm.prank(txSender);
|
||||
freeCreditsProvider.getFreeCredit(profileCreator, 123);
|
||||
|
||||
uint256 creditsAfter = permissionlessCreator.getCreditBalance(profileCreator);
|
||||
|
||||
assertEq(creditsBefore + 123, creditsAfter);
|
||||
}
|
||||
|
||||
function testDecreaseCredit(address profileCreator) public {
|
||||
vm.assume(profileCreator != address(0));
|
||||
vm.assume(profileCreator != address(permissionlessCreator));
|
||||
vm.assume(profileCreator != address(freeCreditsProvider));
|
||||
|
||||
freeCreditsProvider.getFreeCredit(profileCreator, 123456);
|
||||
|
||||
uint256 creditsBefore = permissionlessCreator.getCreditBalance(profileCreator);
|
||||
|
||||
vm.prank(profileCreator);
|
||||
freeCreditsProvider.burnCredits(123);
|
||||
|
||||
uint256 creditsAfter = permissionlessCreator.getCreditBalance(profileCreator);
|
||||
|
||||
assertEq(creditsBefore - 123, creditsAfter);
|
||||
}
|
||||
}
|
||||
1013
test/misc/PermissionlessCreatorTest.t.sol
Normal file
1013
test/misc/PermissionlessCreatorTest.t.sol
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user