Lens для Solidity разработчиков

Привет! Перед тем, как ты продолжишь, я должен предупредить тебя: я не являюсь гуру solidity разработке, у меня нет багажа в несколько лет опыта и я не знаком с Виталиком Бутериным, но мне интересно изучать как устроены различные web3/DeFi протоколы, логику и инфраструктуру их смарт-контрактов.

Я не буду говорить, что я знаю все о смарт-контрактах, но я обещаю, что вы узнаете много полезного и интересного из этого материала.

Цель этой работы - разобраться как работает Lens и поделиться своими знаниями с другими разработчиками, которые не знакомы с этим протоколом.

Я верю в идею "learn by public", поэтому создал этот материал для людей любого уровня в Solidity. Если вы junior или middle, то мои объяснения для каждого контракта будут полезны для вас. Если же вы advanced, то вы сможете двигаться со мной по порядку и углублять свои знания. Но не думайте, что этот материал будет скучным и тягучим, я постараюсь добавить немного юмора и интересных фактов, чтобы вы не заскучали.

Если вы заметили, что я где-то объяснил что-то неправильно или есть моменты, которые можно улучшить в этом материале, пожалуйста, сообщите мне об этом в телеграме. Я всегда открыт к обратной связи и готов улучшать свой материал для вас.

Часть 1: Что такое Lens? 🌿

Я хотел рассказать вам о Lens Protocol и насколько это круто. Но, кажется, ребята из СНГ Lens сообщества меня уже опередили и написали классную статью об этом протоколе!)

Я настоятельно рекомендую тебе ее прочитать. Обрати внимание, что некоторые данные и статистики в статье могут быть актуальны на момент ее написания - 21 февраля.

https://mirror.xyz/lensprotocolru.eth/jgKvqrez2VW8w4fjNjrezSo72XBbRFZEF1BJbqZrLFE

// SPDX-License-Identifier: MIT

pragma solidity 0.8.10;

import {IFollowModule} from '../../../interfaces/IFollowModule.sol';
import {ILensHub} from '../../../interfaces/ILensHub.sol';
import {Errors} from '../../../libraries/Errors.sol';
import {FeeModuleBase} from '../FeeModuleBase.sol';
import {ModuleBase} from '../ModuleBase.sol';
import {FollowValidatorFollowModuleBase} from './FollowValidatorFollowModuleBase.sol';
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';

/**
 * @notice A struct containing the necessary data to execute follow actions on a given profile.
 *
 * @param currency The currency associated with this profile.
 * @param amount The following cost associated with this profile.
 * @param recipient The recipient address associated with this profile.
 */
struct ProfileData {
    address currency;
    uint256 amount;
    address recipient;
}

/**
 * @title FeeFollowModule
 * @author Lens Protocol
 *
 * @notice This is a simple Lens FollowModule implementation, inheriting from the IFollowModule interface, but with additional
 * variables that can be controlled by governance, such as the governance & treasury addresses as well as the treasury fee.
 */
contract FeeFollowModule is FeeModuleBase, FollowValidatorFollowModuleBase {
    using SafeERC20 for IERC20;

    mapping(uint256 => ProfileData) internal _dataByProfile;

    constructor(address hub, address moduleGlobals) FeeModuleBase(moduleGlobals) ModuleBase(hub) {}

    /**
     * @notice This follow module levies a fee on follows.
     *
     * @param profileId The profile ID of the profile to initialize this module for.
     * @param data The arbitrary data parameter, decoded into:
     *      address currency: The currency address, must be internally whitelisted.
     *      uint256 amount: The currency total amount to levy.
     *      address recipient: The custom recipient address to direct earnings to.
     *
     * @return bytes An abi encoded bytes parameter, which is the same as the passed data parameter.
     */
    function initializeFollowModule(uint256 profileId, bytes calldata data)
        external
        override
        onlyHub
        returns (bytes memory)
    {
        (uint256 amount, address currency, address recipient) = abi.decode(
            data,
            (uint256, address, address)
        );
        if (!_currencyWhitelisted(currency) || recipient == address(0) || amount == 0)
            revert Errors.InitParamsInvalid();

        _dataByProfile[profileId].amount = amount;
        _dataByProfile[profileId].currency = currency;
        _dataByProfile[profileId].recipient = recipient;
        return data;
    }

    /**
     * @dev Processes a follow by:
     *  1. Charging a fee
     */
    function processFollow(
        address follower,
        uint256 profileId,
        bytes calldata data
    ) external override onlyHub {
        uint256 amount = _dataByProfile[profileId].amount;
        address currency = _dataByProfile[profileId].currency;
        _validateDataIsExpected(data, currency, amount);

        (address treasury, uint16 treasuryFee) = _treasuryData();
        address recipient = _dataByProfile[profileId].recipient;
        uint256 treasuryAmount = (amount * treasuryFee) / BPS_MAX;
        uint256 adjustedAmount = amount - treasuryAmount;

        IERC20(currency).safeTransferFrom(follower, recipient, adjustedAmount);
        if (treasuryAmount > 0)
            IERC20(currency).safeTransferFrom(follower, treasury, treasuryAmount);
    }

    /**
     * @dev We don't need to execute any additional logic on transfers in this follow module.
     */
    function followModuleTransferHook(
        uint256 profileId,
        address from,
        address to,
        uint256 followNFTTokenId
    ) external override {}

    /**
     * @notice Returns the profile data for a given profile, or an empty struct if that profile was not initialized
     * with this module.
     *
     * @param profileId The token ID of the profile to query.
     *
     * @return ProfileData The ProfileData struct mapped to that profile.
     */
    function getProfileData(uint256 profileId) external view returns (ProfileData memory) {
        return _dataByProfile[profileId];
    }
}