// SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.10; import {IFollowNFT} from '../interfaces/IFollowNFT.sol'; import {IFollowModule} from '../interfaces/IFollowModule.sol'; import {ILensHub} from '../interfaces/ILensHub.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 {IERC721Metadata} from '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.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; } address public immutable HUB; bytes32 internal constant DELEGATE_BY_SIG_TYPEHASH = 0xb8f190a57772800093f4e2b186099eb4f1df0ed7f5e2791e89a4a07678e0aeff; // keccak256( // 'DelegateBySig(address delegator,address delegatee,uint256 nonce,uint256 deadline)' // ); 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) { HUB = hub; } /// @inheritdoc IFollowNFT function initialize( uint256 profileId, string calldata name, string calldata symbol ) external override { if (_initialized) revert Errors.Initialized(); _initialized = true; _profileId = profileId; super._initialize(name, symbol); emit Events.FollowNFTInitialized(profileId, block.timestamp); } /// @inheritdoc IFollowNFT function mint(address to) external override { if (msg.sender != HUB) revert Errors.NotHub(); uint256 tokenId = ++_tokenIdCounter; _mint(to, 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 { _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 } uint256 lower = 0; uint256 upper = snapshotCount - 1; // First check most recent balance if (_snapshots[user][upper].blockNumber <= blockNumber) { return _snapshots[user][upper].value; } // Next check implicit zero balance if (_snapshots[user][lower].blockNumber > blockNumber) { return 0; } while (upper > lower) { uint256 center = upper - (upper - lower) / 2; Snapshot memory snapshot = _snapshots[user][center]; if (snapshot.blockNumber == blockNumber) { return snapshot.value; } else if (snapshot.blockNumber < blockNumber) { lower = center; } else { upper = center - 1; } } return _snapshots[user][lower].value; } /// @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 } uint256 lower = 0; uint256 upper = snapshotCount - 1; // First check most recent delegated supply if (_delSupplySnapshots[upper].blockNumber <= blockNumber) { return _delSupplySnapshots[upper].value; } // Next check implicit zero balance if (_delSupplySnapshots[lower].blockNumber > blockNumber) { return 0; } while (upper > lower) { uint256 center = upper - (upper - lower) / 2; Snapshot memory snapshot = _delSupplySnapshots[center]; if (snapshot.blockNumber == blockNumber) { return snapshot.value; } else if (snapshot.blockNumber < blockNumber) { lower = center; } else { upper = center - 1; } } return _delSupplySnapshots[lower].value; } /** * @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 _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 { if (from != address(0)) { 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 (from == address(0)) { // 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 (from != address(0)) { // 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; } } } }