mirror of
https://github.com/lens-protocol/core.git
synced 2026-04-22 03:02:03 -04:00
feat: Legacy FollowNFT removed from the repo
This commit is contained in:
@@ -1,298 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.15;
|
||||
|
||||
import {IFollowNFT} from '../interfaces/IFollowNFT.sol';
|
||||
import {IFollowModule} from '../interfaces/IFollowModule.sol';
|
||||
import {ILensHub} from '../interfaces/ILensHub.sol';
|
||||
import {MetaTxHelpers} from '../libraries/helpers/MetaTxHelpers.sol';
|
||||
import {Errors} from '../libraries/Errors.sol';
|
||||
import {Events} from '../libraries/Events.sol';
|
||||
import {DataTypes} from '../libraries/DataTypes.sol';
|
||||
import {LensNFTBase} from './base/LensNFTBase.sol';
|
||||
import '../libraries/Constants.sol';
|
||||
|
||||
/**
|
||||
* @title FollowNFT
|
||||
* @author Lens Protocol
|
||||
*
|
||||
* @notice This contract is the NFT that is minted upon following a given profile. It is cloned upon first follow for a
|
||||
* given profile, and includes built-in governance power and delegation mechanisms.
|
||||
*
|
||||
* NOTE: This contract assumes total NFT supply for this follow NFT will never exceed 2^128 - 1
|
||||
*/
|
||||
contract FollowNFT is LensNFTBase, IFollowNFT {
|
||||
struct Snapshot {
|
||||
uint128 blockNumber;
|
||||
uint128 value;
|
||||
}
|
||||
|
||||
bytes32 internal constant DELEGATE_BY_SIG_TYPEHASH =
|
||||
keccak256(
|
||||
'DelegateBySig(address delegator,address delegatee,uint256 nonce,uint256 deadline)'
|
||||
);
|
||||
|
||||
address public immutable HUB;
|
||||
|
||||
mapping(address => mapping(uint256 => Snapshot)) internal _snapshots;
|
||||
mapping(address => address) internal _delegates;
|
||||
mapping(address => uint256) internal _snapshotCount;
|
||||
mapping(uint256 => Snapshot) internal _delSupplySnapshots;
|
||||
uint256 internal _delSupplySnapshotCount;
|
||||
uint256 internal _profileId;
|
||||
uint256 internal _tokenIdCounter;
|
||||
|
||||
bool private _initialized;
|
||||
|
||||
// We create the FollowNFT with the pre-computed HUB address before deploying the hub.
|
||||
constructor(address hub) {
|
||||
if (hub == address(0)) revert Errors.InitParamsInvalid();
|
||||
HUB = hub;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function initialize(uint256 profileId) external override {
|
||||
if (_initialized) revert Errors.Initialized();
|
||||
_initialized = true;
|
||||
_profileId = profileId;
|
||||
emit Events.FollowNFTInitialized(profileId, block.timestamp);
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function mint(address to) external override returns (uint256) {
|
||||
if (msg.sender != HUB) revert Errors.NotHub();
|
||||
unchecked {
|
||||
uint256 tokenId = ++_tokenIdCounter;
|
||||
_mint(to, tokenId);
|
||||
return tokenId;
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function delegate(address delegatee) external override {
|
||||
_delegate(msg.sender, delegatee);
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function delegateBySig(
|
||||
address delegator,
|
||||
address delegatee,
|
||||
DataTypes.EIP712Signature calldata sig
|
||||
) external override {
|
||||
unchecked {
|
||||
MetaTxHelpers._validateRecoveredAddress(
|
||||
_calculateDigest(
|
||||
keccak256(
|
||||
abi.encode(
|
||||
DELEGATE_BY_SIG_TYPEHASH,
|
||||
delegator,
|
||||
delegatee,
|
||||
sigNonces[delegator]++,
|
||||
sig.deadline
|
||||
)
|
||||
)
|
||||
),
|
||||
delegator,
|
||||
sig
|
||||
);
|
||||
}
|
||||
_delegate(delegator, delegatee);
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function getPowerByBlockNumber(address user, uint256 blockNumber)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (uint256)
|
||||
{
|
||||
if (blockNumber > block.number) revert Errors.BlockNumberInvalid();
|
||||
uint256 snapshotCount = _snapshotCount[user];
|
||||
if (snapshotCount == 0) return 0; // Returning zero since this means the user never delegated and has no power
|
||||
return _getSnapshotValueByBlockNumber(_snapshots[user], blockNumber, snapshotCount);
|
||||
}
|
||||
|
||||
/// @inheritdoc IFollowNFT
|
||||
function getDelegatedSupplyByBlockNumber(uint256 blockNumber)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (uint256)
|
||||
{
|
||||
if (blockNumber > block.number) revert Errors.BlockNumberInvalid();
|
||||
uint256 snapshotCount = _delSupplySnapshotCount;
|
||||
if (snapshotCount == 0) return 0; // Returning zero since this means a delegation has never occurred
|
||||
return _getSnapshotValueByBlockNumber(_delSupplySnapshots, blockNumber, snapshotCount);
|
||||
}
|
||||
|
||||
function name() public view override returns (string memory) {
|
||||
string memory handle = ILensHub(HUB).getHandle(_profileId);
|
||||
return string(abi.encodePacked(handle, FOLLOW_NFT_NAME_SUFFIX));
|
||||
}
|
||||
|
||||
function symbol() public view override returns (string memory) {
|
||||
string memory handle = ILensHub(HUB).getHandle(_profileId);
|
||||
bytes4 firstBytes = bytes4(bytes(handle));
|
||||
return string(abi.encodePacked(firstBytes, FOLLOW_NFT_SYMBOL_SUFFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev This returns the follow NFT URI fetched from the hub.
|
||||
*/
|
||||
function tokenURI(uint256 tokenId) public view override returns (string memory) {
|
||||
if (!_exists(tokenId)) revert Errors.TokenDoesNotExist();
|
||||
return ILensHub(HUB).getFollowNFTURI(_profileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Upon transfers, we move the appropriate delegations, and emit the transfer event in the hub.
|
||||
*/
|
||||
function _beforeTokenTransfer(
|
||||
address from,
|
||||
address to,
|
||||
uint256 tokenId
|
||||
) internal override {
|
||||
address fromDelegatee = _delegates[from];
|
||||
address toDelegatee = _delegates[to];
|
||||
address followModule = ILensHub(HUB).getFollowModule(_profileId);
|
||||
|
||||
_moveDelegate(fromDelegatee, toDelegatee, 1);
|
||||
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
ILensHub(HUB).emitFollowNFTTransferEvent(_profileId, tokenId, from, to);
|
||||
if (followModule != address(0)) {
|
||||
IFollowModule(followModule).followModuleTransferHook(_profileId, from, to, tokenId);
|
||||
}
|
||||
}
|
||||
|
||||
function _getSnapshotValueByBlockNumber(
|
||||
mapping(uint256 => Snapshot) storage _shots,
|
||||
uint256 blockNumber,
|
||||
uint256 snapshotCount
|
||||
) internal view returns (uint256) {
|
||||
unchecked {
|
||||
uint256 lower = 0;
|
||||
uint256 upper = snapshotCount - 1;
|
||||
|
||||
// First check most recent snapshot
|
||||
if (_shots[upper].blockNumber <= blockNumber) return _shots[upper].value;
|
||||
|
||||
// Next check implicit zero balance
|
||||
if (_shots[lower].blockNumber > blockNumber) return 0;
|
||||
|
||||
while (upper > lower) {
|
||||
uint256 center = upper - (upper - lower) / 2;
|
||||
Snapshot memory snapshot = _shots[center];
|
||||
if (snapshot.blockNumber == blockNumber) {
|
||||
return snapshot.value;
|
||||
} else if (snapshot.blockNumber < blockNumber) {
|
||||
lower = center;
|
||||
} else {
|
||||
upper = center - 1;
|
||||
}
|
||||
}
|
||||
return _shots[lower].value;
|
||||
}
|
||||
}
|
||||
|
||||
function _delegate(address delegator, address delegatee) internal {
|
||||
uint256 delegatorBalance = balanceOf(delegator);
|
||||
address previousDelegate = _delegates[delegator];
|
||||
_delegates[delegator] = delegatee;
|
||||
_moveDelegate(previousDelegate, delegatee, delegatorBalance);
|
||||
}
|
||||
|
||||
function _moveDelegate(
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount
|
||||
) internal {
|
||||
unchecked {
|
||||
bool fromZero = from == address(0);
|
||||
if (!fromZero) {
|
||||
uint256 fromSnapshotCount = _snapshotCount[from];
|
||||
|
||||
// Underflow is impossible since, if from != address(0), then a delegation must have occurred (at least 1 snapshot)
|
||||
uint256 previous = _snapshots[from][fromSnapshotCount - 1].value;
|
||||
uint128 newValue = uint128(previous - amount);
|
||||
|
||||
_writeSnapshot(from, newValue, fromSnapshotCount);
|
||||
emit Events.FollowNFTDelegatedPowerChanged(from, newValue, block.timestamp);
|
||||
}
|
||||
|
||||
if (to != address(0)) {
|
||||
// if from == address(0) then this is an initial delegation (add amount to supply)
|
||||
if (fromZero) {
|
||||
// It is expected behavior that the `previousDelSupply` underflows upon the first delegation,
|
||||
// returning the expected value of zero
|
||||
uint256 delSupplySnapshotCount = _delSupplySnapshotCount;
|
||||
uint128 previousDelSupply = _delSupplySnapshots[delSupplySnapshotCount - 1]
|
||||
.value;
|
||||
uint128 newDelSupply = uint128(previousDelSupply + amount);
|
||||
_writeSupplySnapshot(newDelSupply, delSupplySnapshotCount);
|
||||
}
|
||||
|
||||
// It is expected behavior that `previous` underflows upon the first delegation to an address,
|
||||
// returning the expected value of zero
|
||||
uint256 toSnapshotCount = _snapshotCount[to];
|
||||
uint128 previous = _snapshots[to][toSnapshotCount - 1].value;
|
||||
uint128 newValue = uint128(previous + amount);
|
||||
_writeSnapshot(to, newValue, toSnapshotCount);
|
||||
emit Events.FollowNFTDelegatedPowerChanged(to, newValue, block.timestamp);
|
||||
} else {
|
||||
// If from != address(0) then this is removing a delegation, otherwise we're dealing with a
|
||||
// non-delegated burn of tokens and don't need to take any action
|
||||
if (!fromZero) {
|
||||
// Upon removing delegation (from != address(0) && to == address(0)), supply calculations cannot
|
||||
// underflow because if from != address(0), then a delegation must have previously occurred, so
|
||||
// the snapshot count must be >= 1 and the previous delegated supply must be >= amount
|
||||
uint256 delSupplySnapshotCount = _delSupplySnapshotCount;
|
||||
uint128 previousDelSupply = _delSupplySnapshots[delSupplySnapshotCount - 1]
|
||||
.value;
|
||||
uint128 newDelSupply = uint128(previousDelSupply - amount);
|
||||
_writeSupplySnapshot(newDelSupply, delSupplySnapshotCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _writeSnapshot(
|
||||
address owner,
|
||||
uint128 newValue,
|
||||
uint256 ownerSnapshotCount
|
||||
) internal {
|
||||
unchecked {
|
||||
uint128 currentBlock = uint128(block.number);
|
||||
mapping(uint256 => Snapshot) storage ownerSnapshots = _snapshots[owner];
|
||||
|
||||
// Doing multiple operations in the same block
|
||||
if (
|
||||
ownerSnapshotCount != 0 &&
|
||||
ownerSnapshots[ownerSnapshotCount - 1].blockNumber == currentBlock
|
||||
) {
|
||||
ownerSnapshots[ownerSnapshotCount - 1].value = newValue;
|
||||
} else {
|
||||
ownerSnapshots[ownerSnapshotCount] = Snapshot(currentBlock, newValue);
|
||||
_snapshotCount[owner] = ownerSnapshotCount + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _writeSupplySnapshot(uint128 newValue, uint256 supplySnapshotCount) internal {
|
||||
unchecked {
|
||||
uint128 currentBlock = uint128(block.number);
|
||||
|
||||
// Doing multiple operations in the same block
|
||||
if (
|
||||
supplySnapshotCount != 0 &&
|
||||
_delSupplySnapshots[supplySnapshotCount - 1].blockNumber == currentBlock
|
||||
) {
|
||||
_delSupplySnapshots[supplySnapshotCount - 1].value = newValue;
|
||||
} else {
|
||||
_delSupplySnapshots[supplySnapshotCount] = Snapshot(currentBlock, newValue);
|
||||
_delSupplySnapshotCount = supplySnapshotCount + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user