# Lens Protocol 合約跟概念講解
# Overview

## Profile
- 個人檔案,相當於 Lens 當中的身分證,代表你這個人
- 是一個 NFT
- 可選擇要使用的 follow module
- 可指定**dispatcher**,相當於賦予權利給某個 address,讓他代表這個 profile 去執行相關的動作(post setFollowModule....等)
## Publication
### Post
- 等同於社交軟體中的**貼文**
- 包含採用的 reference & collect module
### Comment
- 等同於社交軟體中的**留言**
- 這留言會去對應到相關的 post 跟 profile
- 對應的方法是去記錄一個`Pointer`,因此會動用到 reference module 的功能
### Mirror
- **引用別人的publication** -> post comment mirror(對!你可以mirror他的mirror)
- 不會有內容
- 如果`getContentURI(mirror)`的話,得到的contentURI會是mirror指到的那個publication的contentURI
- 只有reference module
## Follow
- 等同於現在社群中的追隨或關注
- 對應關係是 profile
- 可以去 follow 你想要的 profile
- 可以得到相對應 profile 的 follow NFT,藉此證明你有追隨這個 profile
- 在 follow 的時候會根據你想要 follow 的 profile 當中的 follow module 而有不同的實現邏輯
- 可以有**治理**的功能,讓持有同一種 follow NFT 的人可以形成一個 DAO
## Collect
- 對應關係是 Publication
- 可以 collect 其他 profile 跟 comment
## Module
- module 可以分為以下三種,所有的 module 都可以是自己另外部署的一個合約,並把需要的 init data 跟 address 設定進相對應的物件
- 例如
- follow module 設定進 profile 當中
- reference & collect module 設定 publication 當中
- 可以藉由 module 來設定自己的想要的 logic
## Follow module
- **被設定在 profile 裡面**
- 一旦有人想要 follow 就會去調用 follow module 當中的邏輯
- 例如
- 用戶 A 設定一 follow module 在自己的 profile 中,此 module 的功能為需要打入**通關密碼**才能 follow
- 那麼用戶 B 就需要在 follow 的時候傳送相關的 data 給 follow function,這邊需要的 data 就是**通關密碼**
- 通關密碼正確才算 follow 成功,並得到 follow NFT
## Collect module
- **被設定在 Publication**當中
- 使用方法與 follow module 類似
## Reference module
- **被設定在Publication**當中
- 當有**引用 refrence**的時候會使用到 reference module
-
***
# 核心合約講解
- 使用**typechain**跟**typechain-ethers**來生成 Contract factory
- Lenshub 程式中大量運用**upgradable** **proxy** **create**等等的方式
- 如果是 proxy 還有分 proxy 跟 implementation 兩個部分
-
## LensHub
- 由`LensHub`當成核心合約,去跟其他的 library 以及合約做互動
- `LensHub`採用`TransparentUpgradeableProxy`
- 大多數主要執行動作的都交由`InteractionLogic` `ProfileTokenURILogic` `PublishingLogic` 這三個 library 完成,例如 collect、follow、createProfile 等等
## Follow NFT
- 繼承 LensNFTBase(from ERC721Enumerable)
- 當其他使用者 follow 之後可以 mint 一個 Follow NFT
## Collect NFT
- 跟 follow NFT 結構類似,繼承了相同的 LensNFTBase
- 當 collect publication 後就可以 mint 一個相對應的 NFT
-
## InteractionLogic
功能太多,以下只說明兩個 function
### follow
```solidity
//follow() 的程式片段
address followModule = _profileById[profileIds[i]].followModule;
address followNFT = _profileById[profileIds[i]].followNFT;
//如果被follow的profile的follow NFT address == 0的話就重新
//部署一個新的follow NFT contract
if (followNFT == address(0)) {
followNFT = _deployFollowNFT(profileIds[i]);
_profileById[profileIds[i]].followNFT = followNFT;
}
//部署完畢後用address去調用followNFT的interface去mint
tokenIds[i] = IFollowNFT(followNFT).mint(follower);
//如果profile當中有設定follow module
//那就需要調用follow module當中的processFollow來處理相關的邏輯
if (followModule != address(0)) {
IFollowModule(followModule).processFollow(
follower,
profileIds[i],
followModuleDatas[i]
);
}
```
```solidity
function _deployFollowNFT(uint256 profileId) private returns (address) {
bytes memory functionData = abi.encodeWithSelector(
IFollowNFT.initialize.selector,
profileId
);
//創建一個NFT Proxy後執行initialize()
address followNFT = address(new FollowNFTProxy(functionData));
emit Events.FollowNFTDeployed(profileId, followNFT, block.timestamp);
return followNFT;
}
```
follow 不同的 profile 會產生**邏輯需一樣但儲存狀態不一樣的**合約,所以會需要 proxy 來執行 implementation contract
不重新部署一個全新的合約原因推測是重新部署的成本太大,所以部署一個相對成本較小的 proxy contract 來執行 implementation contract,因為implementation contract 是透過 proxy 啟用,所以implementation的constructor不會動到 ,取而代之的是調用`initialize`來達到初始化的功能
### Collect
結構與follow()類似,
```solidity
//collect 程式片段
//如果collect的是mirror
//那麼必須先找出mirror根源的profile publication collectModule
(uint256 rootProfileId, uint256 rootPubId, address rootCollectModule) = Helpers
.getPointedIfMirror(profileId, pubId, _pubByIdByProfile);
uint256 tokenId;
// Avoids stack too deep
{
address collectNFT = _pubByIdByProfile[rootProfileId][rootPubId].collectNFT;
if (collectNFT == address(0)) {
collectNFT = _deployCollectNFT(
rootProfileId,
rootPubId,
_profileById[rootProfileId].handle,
collectNFTImpl
);
_pubByIdByProfile[rootProfileId][rootPubId].collectNFT = collectNFT;
}
tokenId = ICollectNFT(collectNFT).mint(collector);
}
//如果collect一個mirror的話,那就調用mirror(被collect)指向的publication的collect module
ICollectModule(rootCollectModule).processCollect(
profileId,
collector,
rootProfileId,
rootPubId,
collectModuleData
);
```
```solidity
function _deployCollectNFT(
uint256 profileId,
uint256 pubId,
string memory handle,
address collectNFTImpl
) private returns (address) {
//採用Openzeppelin的Clones以節省成本
address collectNFT = Clones.clone(collectNFTImpl);
bytes4 firstBytes = bytes4(bytes(handle));
string memory collectNFTName = string(
abi.encodePacked(handle, Constants.COLLECT_NFT_NAME_INFIX, pubId.toString())
);
string memory collectNFTSymbol = string(
abi.encodePacked(firstBytes, Constants.COLLECT_NFT_SYMBOL_INFIX, pubId.toString())
);
ICollectNFT(collectNFT).initialize(profileId, pubId, collectNFTName, collectNFTSymbol);
emit Events.CollectNFTDeployed(profileId, pubId, collectNFT, block.timestamp);
return collectNFT;
}
```
***
# 測試流程說明
## 情境測試1 一般使用狀況
假設有三個用戶user userTwo userThree
1. user 跟 userTwo 分別create兩個profile
2. userTwo發了一篇文 -> publicaton ID 1
3. user在userTwo ID為1的publication(post)底下留言(comment),user產生一publication(comment ID 1)
4. userTwo覺得user的留言很中肯,mirror起來,此時userTwo有兩個publication,分別為post(ID 1) 跟mirror(ID 2)
5. 此時userThree想collect userTwo的post(ID 1),但必須先追隨userTwo,因為post的collect module設定為,必須先追隨userTwo才能collect
6. userThree接下來想collect userTwo的mirror(ID 2),因為此mirror指向的是user的comment,所以執行被指向的comment的collect module(也就是必須先追隨user才能collect)
7. 最後檢查userThree有沒有成功獲得collect NFT
## 情境測試2 自定義module
製作一個follow module ->`FollowOtherModule.sol`
功能是假設有一用戶想follow userTwo,那必須先follow user,並將這個follow module放入userTwo的profile中
reference module採用內部預設的合約 ->`FollowerOnlyReferenceModule.sol`
功能是必須先follow才能執行reference相關功能(comment mirror)