├── .env.example
├── .eslintrc.json
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── .prettierignore
├── .prettierrc
├── .solcover.js
├── LICENSE
├── README.md
├── addresses.json
├── audit
├── V1
│ └── PeckShield-Audit-Report-LensProtocol-v1.0.pdf
└── V2
│ └── PeckShield-Audit-Report-LensV2-v1.0.pdf
├── contracts
├── FollowNFT.sol
├── LensHub.sol
├── base
│ ├── ERC2981CollectionRoyalties.sol
│ ├── HubRestricted.sol
│ ├── LensBaseERC721.sol
│ ├── LensGovernable.sol
│ ├── LensHubEventHooks.sol
│ ├── LensHubStorage.sol
│ ├── LensImplGetters.sol
│ ├── LensProfiles.sol
│ └── upgradeability
│ │ ├── FollowNFTProxy.sol
│ │ └── VersionedInitializable.sol
├── interfaces
│ ├── ICollectModule.sol
│ ├── ICollectNFT.sol
│ ├── IERC721Burnable.sol
│ ├── IERC721MetaTx.sol
│ ├── IERC721Timestamped.sol
│ ├── IFollowModule.sol
│ ├── IFollowNFT.sol
│ ├── ILegacyCollectModule.sol
│ ├── ILegacyCollectNFT.sol
│ ├── ILegacyFollowModule.sol
│ ├── ILegacyReferenceModule.sol
│ ├── ILensERC721.sol
│ ├── ILensGovernable.sol
│ ├── ILensHandles.sol
│ ├── ILensHub.sol
│ ├── ILensHubEventHooks.sol
│ ├── ILensHubInitializable.sol
│ ├── ILensImplGetters.sol
│ ├── ILensProfiles.sol
│ ├── ILensProtocol.sol
│ ├── IModuleGlobals.sol
│ ├── IPublicationActionModule.sol
│ ├── IReferenceModule.sol
│ └── ITokenHandleRegistry.sol
├── libraries
│ ├── ActionLib.sol
│ ├── FollowLib.sol
│ ├── GovernanceLib.sol
│ ├── LegacyCollectLib.sol
│ ├── MetaTxLib.sol
│ ├── MigrationLib.sol
│ ├── ProfileLib.sol
│ ├── PublicationLib.sol
│ ├── StorageLib.sol
│ ├── ValidationLib.sol
│ ├── constants
│ │ ├── Errors.sol
│ │ ├── Events.sol
│ │ ├── Typehash.sol
│ │ └── Types.sol
│ └── token-uris
│ │ ├── FollowTokenURILib.sol
│ │ ├── HandleTokenURILib.sol
│ │ ├── ProfileTokenURILib.sol
│ │ ├── TokenURIMainFontLib.sol
│ │ └── TokenURISecondaryFontLib.sol
├── misc
│ ├── ImmutableOwnable.sol
│ ├── LegacyCollectNFT.sol
│ ├── LensHubInitializable.sol
│ ├── LensV2Migration.sol
│ ├── LensV2UpgradeContract.sol
│ ├── ModuleGlobals.sol
│ ├── ProfileCreationProxy.sol
│ ├── UIDataProvider.sol
│ └── access
│ │ ├── ControllableByContract.sol
│ │ ├── Governance.sol
│ │ └── ProxyAdmin.sol
├── modules
│ ├── ActionRestricted.sol
│ ├── FeeModuleBase.sol
│ ├── README.md
│ ├── act
│ │ ├── collect
│ │ │ ├── CollectNFT.sol
│ │ │ ├── CollectPublicationAction.sol
│ │ │ ├── MultirecipientFeeCollectModule.sol
│ │ │ ├── SimpleFeeCollectModule.sol
│ │ │ └── base
│ │ │ │ └── BaseFeeCollectModule.sol
│ │ └── seadrop
│ │ │ ├── LensSeaDropCollection.sol
│ │ │ └── SeaDropMintPublicationAction.sol
│ ├── constants
│ │ └── Errors.sol
│ ├── follow
│ │ ├── FeeFollowModule.sol
│ │ └── RevertFollowModule.sol
│ ├── interfaces
│ │ ├── IBaseFeeCollectModule.sol
│ │ └── IWMATIC.sol
│ ├── libraries
│ │ └── FollowValidationLib.sol
│ └── reference
│ │ ├── DegreesOfSeparationReferenceModule.sol
│ │ ├── FollowerOnlyReferenceModule.sol
│ │ └── TokenGatedReferenceModule.sol
└── namespaces
│ ├── LensHandles.sol
│ ├── TokenHandleRegistry.sol
│ └── constants
│ ├── Errors.sol
│ ├── Events.sol
│ └── Types.sol
├── diagrams
├── migration-scheme.png
├── pub-layer-architecture.png
├── referral-system.png
├── social-layer-architecture.png
└── upgrade-scheme.png
├── discord-export
├── Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html
├── Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files
│ ├── 07783feeadd7fbf9645b8f2cff9bbdc6-79919.png
│ ├── 08a051c6ef73c89fd5967637cbbb4acd-9A9A6.png
│ ├── 1-B2132.png
│ ├── 1110202897947426866-A5105.png
│ ├── 188be8c99e89942dad132d5f7b774545-F428F.png
│ ├── 1f349-79F56.svg
│ ├── 1f374-505CF.svg
│ ├── 1f389-5C738.svg
│ ├── 1f3b9-C10EF.svg
│ ├── 1f3c1-445DC.svg
│ ├── 1f3c6-621A1.svg
│ ├── 1f43a-EB486.svg
│ ├── 1f440-6C64D.svg
│ ├── 1f44b-8A059.svg
│ ├── 1f44d-1f3fb-ED2AA.svg
│ ├── 1f44d-27259.svg
│ ├── 1f44f-3D381.svg
│ ├── 1f4aa-2FD27.svg
│ ├── 1f4af-4CFF5.svg
│ ├── 1f4b8-E3468.svg
│ ├── 1f4c4-413F0.svg
│ ├── 1f4c6-44E30.svg
│ ├── 1f4dc-AC641.svg
│ ├── 1f50d-195C0.svg
│ ├── 1f525-8FE4F.svg
│ ├── 1f602-168C5.svg
│ ├── 1f604-BF863.svg
│ ├── 1f605-42B43.svg
│ ├── 1f642-83E8A.svg
│ ├── 1f64c-7C820.svg
│ ├── 1f64f-22B8D.svg
│ ├── 1f680-A35CE.svg
│ ├── 1f6a8-A8AB3.svg
│ ├── 1f911-F346C.svg
│ ├── 1f916-AD810.svg
│ ├── 1f91d-5A0F2.svg
│ ├── 1f970-85DC7.svg
│ ├── 1f972-F415D.svg
│ ├── 1f973-88B39.svg
│ ├── 1f979-BE2CD.svg
│ ├── 1f9d1-5BC80.svg
│ ├── 1fae1-B19DE.svg
│ ├── 2-ADBB4.png
│ ├── 2696-15F4A.svg
│ ├── 2705-0589F.svg
│ ├── 2764-A3D25.svg
│ ├── 2a9faff195fe333526cfe6ae6fce1420-49B98.png
│ ├── 2bc9256840cf583765cbbee210b7c33a-A77F9.png
│ ├── 2c1e18acbcb9cdaeabfd7fc6f5090edf-CA387.png
│ ├── 3270d963d3ddad601eced253ef8486c0-41F1F.png
│ ├── 3e66ba0e363a1e54abc42a137953597a-23D5F.png
│ ├── 4-4551A.png
│ ├── 458772ba1638eeef45aa2b27fdd74c50-322DF.png
│ ├── 4933-8E3F2
│ ├── 5-E9BDB.png
│ ├── 5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png
│ ├── 61a267bef614e2ed06bd3dc8008556c6-48B7F.png
│ ├── 67594ee4b4d1fc03bca468327a0d145b-BD76A.png
│ ├── 6ad4ef2f519afddce2d78021aa7dd099-B55F0.png
│ ├── 6b084fd00f226a77abcf31000a85623e-5C817.png
│ ├── 7c6243b3f5117a8160472efef723ba9f-F0002.png
│ ├── 851893827089727568-5FD38.png
│ ├── 8beb125bbaa4085dbee1d02a58895784-398A3.png
│ ├── 9010cff7dc5115048ddc82561051356b-12341.png
│ ├── 92723d55220e7121bd5dc9694136a3d2-3B2C3.png
│ ├── 9542a3c6a8ce011c16ce81d61dc908f9-F344E.png
│ ├── 977133670429261884-CA8EA.png
│ ├── a4c5f4ed4ac3c1bfaae3fc3aa73bcdc5-9A986.png
│ ├── ac4bbcb3c0b85e2a4e614559bf696e56-6C05D.png
│ ├── ad1bdf970e39199a645d59618f8426cc-F4141.png
│ ├── b4fead77f737aa7840a25e1cd39eb062-CEDEC.png
│ ├── b9d093be224ecf13dd2aa3004eefdaef-1107F.png
│ ├── c0d82faff46e636c3e88f1d883784d5f-86322.png
│ ├── c4-og-banner-AF181.png
│ ├── c6b85a4a6ca07ab15a30a24f570be5b8-3D1A6.png
│ ├── c6c31419353d7af9cac1bec0835a1f21-F268C.png
│ ├── d352b5d64239648acbeb7b6f310df0b8-E875A.png
│ ├── doctor-sung-sung-B522E.mp4
│ ├── e780672df5ac1f1a85dc2e2ef26354f3-7EF05.png
│ ├── e9472a11f41d425badda92e53fc91864-F560D.png
│ ├── ec283548018391e9a25a61215bd46007-2D6CA.png
│ ├── f4422c70568695907d5aa7f8632b4d51-4FBE2.png
│ ├── ggsans-italic-400-E988B.woff2
│ ├── ggsans-italic-500-0777F.woff2
│ ├── ggsans-italic-600-CB411.woff2
│ ├── ggsans-italic-700-891AC.woff2
│ ├── ggsans-italic-800-D36B0.woff2
│ ├── ggsans-normal-400-1456D.woff2
│ ├── ggsans-normal-500-89CE5.woff2
│ ├── ggsans-normal-600-C1EA8.woff2
│ ├── ggsans-normal-700-1949A.woff2
│ ├── ggsans-normal-800-58487.woff2
│ ├── highlight.min-D8D27.js
│ ├── lottie.min-99657.js
│ ├── solarized-dark.min-BA98F.css
│ └── thinking-cat-6ABDD.mp4
├── Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt
└── Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files
│ ├── 4933-8E3F2
│ ├── c4-og-banner-AF181.png
│ ├── doctor-sung-sung-341B0.png
│ └── thinking-cat-DFA30.png
├── foundry-scripts
└── DeployUpgrade.s.sol
├── foundry.toml
├── hardhat.config.ts
├── helper-hardhat-config.ts
├── helpers
├── hardhat-constants.ts
├── test-wallets.ts
├── types.ts
└── wallet-helpers.ts
├── package-lock.json
├── package.json
├── remappings.txt
├── scripts
├── getProxyAdmin.sh
└── mergeCoverage.sh
├── tasks
├── full-deploy-verify.ts
├── full-deploy.ts
├── helpers
│ └── utils.ts
├── list-storage.ts
├── testnet-full-deploy-verify.ts
└── whitelist-currency.ts
├── test
├── ActTest.t.sol
├── ChangeDelegatedExecutorsConfigTest.t.sol
├── CollectNFTTest.t.sol
├── Constants.sol
├── ERC721.t.sol
├── FollowNFTTest.t.sol
├── FollowTest.t.sol
├── GovernanceFunctions.t.sol
├── LegacyCollectNFTTest.t.sol
├── LegacyCollectTest.t.sol
├── LensBaseERC721Test.t.sol
├── LensHubEventHooksTest.t.sol
├── LensHubTest.t.sol
├── MetaTxNegatives.t.sol
├── ModuleGlobals.t.sol
├── ProfileMetadataURI.t.sol
├── ProfileNFTTest.t.sol
├── ReferralSystem.t.sol
├── SetBlockStatusTest.t.sol
├── SetFollowModule.t.sol
├── SetProfileImageURI.t.sol
├── TokenGuardian.t.sol
├── UnfollowTest.t.sol
├── base
│ ├── BaseTest.t.sol
│ └── TestSetup.t.sol
├── fork
│ └── UpgradeForkTest.t.sol
├── helpers
│ ├── ArrayHelpers.sol
│ ├── Events.sol
│ └── ForkManagement.sol
├── migrations
│ └── Migrations.t.sol
├── misc
│ ├── ControllableByContractTest.t.sol
│ ├── GovernanceTest.t.sol
│ ├── ProfileCreationProxyTest.t.sol
│ ├── ProxyAdminTest.t.sol
│ └── UIDataProviderTest.t.sol
├── mocks
│ ├── MockActionModule.sol
│ ├── MockCollectModule.sol
│ ├── MockCurrency.sol
│ ├── MockDeprecatedCollectModule.sol
│ ├── MockDeprecatedFollowModule.sol
│ ├── MockDeprecatedReferenceModule.sol
│ ├── MockERC721RecipientWithRevertFlag.sol
│ ├── MockFollowModule.sol
│ ├── MockFollowModuleWithRevertFlag.sol
│ ├── MockModule.sol
│ ├── MockNFT.sol
│ ├── MockNonERC721Recipient.sol
│ ├── MockProfileCreationProxy.sol
│ ├── MockReferenceModule.sol
│ ├── MockTokenHolderContract.sol
│ └── MockWrongReturnDataERC721Recipient.sol
├── modules
│ ├── act
│ │ ├── CollectPublicationAction.t.sol
│ │ └── collect
│ │ │ ├── BaseFeeCollectModule.base.sol
│ │ │ ├── BaseFeeCollectModule.t.sol
│ │ │ ├── MultirecipientCollectModule.base.sol
│ │ │ └── MultirecipientCollectModule.t.sol
│ ├── follow
│ │ ├── FeeFollowModule.t.sol
│ │ └── RevertFollowModule.t.sol
│ └── reference
│ │ ├── DegreesOfSeparationReferenceModule.t.sol
│ │ ├── FollowerOnlyReferenceModule.t.sol
│ │ └── TokenGatedReferenceModule.t.sol
├── namespaces
│ ├── LensHandlesTest.t.sol
│ └── TokenHandleRegistryTest.t.sol
└── publications
│ ├── CommentTest.t.sol
│ ├── MirrorTest.t.sol
│ ├── PostTest.t.sol
│ ├── PublicationTest.t.sol
│ ├── PublicationType.t.sol
│ └── QuoteTest.t.sol
├── tsconfig.json
└── verifyStorageSlots.sh
/.env.example:
--------------------------------------------------------------------------------
1 | MNEMONIC=
2 | POLYGON_RPC_URL=
3 | MUMBAI_RPC_URL=
4 | KOVAN_RPC_URL=
5 | ROPSTEN_RPC_URL=
6 | MAINNET_RPC_URL=
7 | BLOCK_EXPLORER_KEY=
8 | TENDERLY_PROJECT=
9 | TENDERLY_USERNAME=
10 | TENDERLY_FORK_ID=
11 | TENDERLY_HEAD_ID=
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": { "node": true },
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint"],
5 | "parserOptions": {
6 | "ecmaVersion": 2020,
7 | "sourceType": "module"
8 | },
9 | "extends": [
10 | "eslint:recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "prettier",
13 | "plugin:prettier/recommended"
14 | ],
15 | "rules": {}
16 | }
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | concurrency:
4 | group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches: [main]
10 | pull_request:
11 |
12 | jobs:
13 | foundry:
14 | strategy:
15 | fail-fast: true
16 |
17 | name: Lens Protocol CI
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v3
21 | with:
22 | submodules: recursive # This replaces `forge install` for dependencies
23 | - name: Install Foundry
24 | uses: foundry-rs/foundry-toolchain@v1
25 | with:
26 | version: nightly
27 | - name: Compile Project
28 | run: |
29 | forge --version
30 | FOUNDRY_PROFILE=cibuild forge build --deny-warnings --skip test
31 | - name: Run Tests
32 | run: |
33 | cp .env.example .env
34 | source .env
35 | FOUNDRY_PROFILE=citest forge test -vvv
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | #Hardhat files
3 | cache
4 | artifacts
5 | node_modules
6 | dist/
7 | build/
8 | .vscode
9 | .idea
10 | /.VSCodeCounter/
11 | /types
12 | /typechain-types
13 | /tasks/unpause.ts
14 | /tasks/create-profile.ts
15 | /tasks/post.ts
16 | /tasks/follow.ts
17 | /tasks/collect.ts
18 | /tasks/test-module.ts
19 |
20 | /coverage*
21 | coverage.json
22 | .coverage_artifacts
23 | .coverage_cache
24 | .coverage_contracts
25 | lcov*
26 |
27 | # Compiler files
28 | forge-cache/
29 | out/
30 |
31 | docs/
32 |
33 | # Ignores development broadcast logs
34 | !/broadcast
35 | /broadcast/*/31337/
36 | /broadcast/**/dry-run/
37 |
38 | contracts/core/modules/follow/SecretCodeFollowModule.sol
39 |
40 | .DS_Store
41 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/seadrop"]
2 | path = lib/seadrop
3 | url = https://github.com/lens-protocol/seadrop.git
4 | [submodule "lib/forge-std"]
5 | path = lib/forge-std
6 | url = https://github.com/foundry-rs/forge-std
7 | [submodule "lib/solady"]
8 | path = lib/solady
9 | url = https://github.com/Vectorized/solady
10 | [submodule "lib/openzeppelin-contracts"]
11 | path = lib/openzeppelin-contracts
12 | url = https://github.com/OpenZeppelin/openzeppelin-contracts
13 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | artifacts
2 | cache
3 | node_modules
4 | coverage
5 | typechain-types
6 | lib
7 | out
8 | contracts/libraries/constants/Typehash.sol
9 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "trailingComma": "es5",
4 | "semi": true,
5 | "singleQuote": true,
6 | "tabWidth": 2,
7 | "overrides": [
8 | {
9 | "files": "*.sol",
10 | "options": {
11 | "semi": true,
12 | "printWidth": 120,
13 | "tabWidth": 4
14 | }
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/.solcover.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | skipFiles: [
3 | '/core/base/ERC721Time.sol',
4 | '/core/base/ERC721Enumerable.sol',
5 | '/core/modules/follow/SecretCodeFollowModule.sol',
6 | '/upgradeability',
7 | '/interfaces',
8 | '/mocks',
9 | ],
10 | };
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Avara Labs Ltd
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/addresses.json:
--------------------------------------------------------------------------------
1 | {
2 | "mainnet": {
3 | "chainId": 137,
4 | "network": "polygon",
5 | "LensHubProxy": "0xDb46d1Dc155634FbC732f92E853b10B288AD5a1d",
6 | "LensHubImplementation": "0x96f1ba24294ffe0dfcd832d8376da4a4645a4cd6",
7 | "ModuleGlobals": "0x3Df697FF746a60CBe9ee8D47555c88CB66f03BB9",
8 | "FreeCollectModule": "0x23b9467334bEb345aAa6fd1545538F3d54436e96",
9 | "PoolAddressesProvider": "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb",
10 | "MultirecipientFeeCollectModule": "0xfa9da21d0a18c7b7de4566481c1e8952371f880a",
11 | "StepwiseCollectModule": "0x210c94877dcfceda8238acd382372278844a54d7",
12 | "ERC4626FeeCollectModule": "0x394fd100b13197787023ef4cf318cde456545db7",
13 | "AaveFeeCollectModule": "0xa94713d0688c8a483c3352635cec4e0ce88d6a29",
14 | "TokenGatedReferenceModule": "0x3d7f4f71a90fe5a9d13fab2716080f2917cf88f3"
15 | },
16 | "testnet": {
17 | "chainId": 80001,
18 | "network": "mumbai",
19 | "LensHubProxy": "0x60Ae865ee4C725cd04353b5AAb364553f56ceF82",
20 | "LensHubImplementation": "0x45cf9Ba12b43F6c8B7148E06A6f84c5B9ad3Dd44",
21 | "ModuleGlobals": "0x1353aAdfE5FeD85382826757A95DE908bd21C4f9",
22 | "FreeCollectModule": "0x0BE6bD7092ee83D44a6eC1D949626FeE48caB30c",
23 | "PoolAddressesProvider": "0x5343b5bA672Ae99d627A1C87866b8E53F47Db2E6",
24 | "MultirecipientFeeCollectModule": "0x99d6c3eabf05435e851c067d2c3222716f7fcfe5",
25 | "StepwiseCollectModule": "0x7a7b8e7699e0492da1d3c7eab7e2f3bf1065aa40",
26 | "ERC4626FeeCollectModule": "0x79697402bd2caa19a53d615fb1a30a98e35b84d5",
27 | "AaveFeeCollectModule": "0x912860ed4ed6160c48a52d52fcab5c059d34fe5a",
28 | "TokenGatedReferenceModule": "0xb4ba8dccd35bd3dcc5d58dbb9c7dff9c9268add9",
29 | "InteractionLogic": "0x845242e2Cd249af8D4f0D7085DefEAc3381815E3"
30 | },
31 | "sandbox": {
32 | "chainId": 80001,
33 | "network": "mumbai",
34 | "LensHubProxy": "0x7582177F9E536aB0b6c721e11f383C326F2Ad1D5",
35 | "LensHubImplementation": "0x1b30F214c192EF4B7F8c9926c47C4161016955DA",
36 | "ModuleGlobals": "0xcbCC5b9611d22d11403373432642Df9Ef7Dd81AD",
37 | "FreeCollectModule": "0x11C45Cbc6fDa2dbe435C0079a2ccF9c4c7051595",
38 | "PoolAddressesProvider": "0x5343b5bA672Ae99d627A1C87866b8E53F47Db2E6",
39 | "MultirecipientFeeCollectModule": "0x1cff6c45b0de2fff70670ef4dc67a92a1ccfe0bb",
40 | "StepwiseCollectModule": "0x6928d6127dfa0da401737e6ff421fcf62d5617a3",
41 | "ERC4626FeeCollectModule": "0x31126c602cf88193825a99dcd1d17bf1124b1b4f",
42 | "AaveFeeCollectModule": "0x666e06215747879ee68b3e5a317dcd8411de1897",
43 | "TokenGatedReferenceModule": "0x86d35562ceb9f10d7c2c23c098dfeacb02f53853",
44 | "MockSandboxGovernance": "0x1677d9cC4861f1C85ac7009d5F06f49c928CA2AD"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/audit/V1/PeckShield-Audit-Report-LensProtocol-v1.0.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/audit/V1/PeckShield-Audit-Report-LensProtocol-v1.0.pdf
--------------------------------------------------------------------------------
/audit/V2/PeckShield-Audit-Report-LensV2-v1.0.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/audit/V2/PeckShield-Audit-Report-LensV2-v1.0.pdf
--------------------------------------------------------------------------------
/contracts/base/HubRestricted.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {Errors} from 'contracts/libraries/constants/Errors.sol';
6 |
7 | /**
8 | * @title HubRestricted
9 | * @author Lens Protocol
10 | *
11 | * @notice This abstract contract adds a public `HUB` immutable field, as well as an `onlyHub` modifier,
12 | * to inherit from contracts that have functions restricted to be only called by the Lens hub.
13 | */
14 | abstract contract HubRestricted {
15 | address public immutable HUB;
16 |
17 | modifier onlyHub() {
18 | if (msg.sender != HUB) {
19 | revert Errors.NotHub();
20 | }
21 | _;
22 | }
23 |
24 | constructor(address hub) {
25 | HUB = hub;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/base/LensHubEventHooks.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensHubEventHooks} from 'contracts/interfaces/ILensHubEventHooks.sol';
6 | import {Errors} from 'contracts/libraries/constants/Errors.sol';
7 | import {StorageLib} from 'contracts/libraries/StorageLib.sol';
8 | import {Events} from 'contracts/libraries/constants/Events.sol';
9 |
10 | abstract contract LensHubEventHooks is ILensHubEventHooks {
11 | /// @inheritdoc ILensHubEventHooks
12 | function emitUnfollowedEvent(uint256 unfollowerProfileId, uint256 idOfProfileUnfollowed) external override {
13 | address expectedFollowNFT = StorageLib.getProfile(idOfProfileUnfollowed).followNFT;
14 | if (msg.sender != expectedFollowNFT) {
15 | revert Errors.CallerNotFollowNFT();
16 | }
17 | emit Events.Unfollowed(unfollowerProfileId, idOfProfileUnfollowed, block.timestamp);
18 | }
19 |
20 | //////////////////////////////////////
21 | /// DEPRECATED FUNCTIONS ///
22 | //////////////////////////////////////
23 |
24 | // Deprecated in V2. Kept here just for backwards compatibility with Lens V1 Collect NFTs.
25 | function emitCollectNFTTransferEvent(
26 | uint256 profileId,
27 | uint256 pubId,
28 | uint256 collectNFTId,
29 | address from,
30 | address to
31 | ) external {
32 | address expectedCollectNFT = StorageLib.getPublication(profileId, pubId).__DEPRECATED__collectNFT;
33 | if (msg.sender != expectedCollectNFT) {
34 | revert Errors.CallerNotCollectNFT();
35 | }
36 | emit Events.CollectNFTTransferred(profileId, pubId, collectNFTId, from, to, block.timestamp);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/contracts/base/LensImplGetters.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensImplGetters} from 'contracts/interfaces/ILensImplGetters.sol';
6 |
7 | contract LensImplGetters is ILensImplGetters {
8 | address internal immutable FOLLOW_NFT_IMPL;
9 | address internal immutable __LEGACY__COLLECT_NFT_IMPL;
10 |
11 | constructor(address followNFTImpl, address collectNFTImpl) {
12 | FOLLOW_NFT_IMPL = followNFTImpl;
13 | __LEGACY__COLLECT_NFT_IMPL = collectNFTImpl;
14 | }
15 |
16 | /// @inheritdoc ILensImplGetters
17 | function getFollowNFTImpl() external view override returns (address) {
18 | return FOLLOW_NFT_IMPL;
19 | }
20 |
21 | /// @inheritdoc ILensImplGetters
22 | function getCollectNFTImpl() external view override returns (address) {
23 | return __LEGACY__COLLECT_NFT_IMPL; // LEGACY support: Used only for compatibility with V1 collectible posts.
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/contracts/base/upgradeability/FollowNFTProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
6 | import {Proxy} from '@openzeppelin/contracts/proxy/Proxy.sol';
7 | import {Address} from '@openzeppelin/contracts/utils/Address.sol';
8 |
9 | contract FollowNFTProxy is Proxy {
10 | using Address for address;
11 | address immutable HUB;
12 |
13 | constructor(bytes memory data) {
14 | HUB = msg.sender;
15 | ILensHub(msg.sender).getFollowNFTImpl().functionDelegateCall(data);
16 | }
17 |
18 | function _implementation() internal view override returns (address) {
19 | return ILensHub(HUB).getFollowNFTImpl();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/base/upgradeability/VersionedInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.15;
3 |
4 | import {Errors} from 'contracts/libraries/constants/Errors.sol';
5 | import {StorageLib} from 'contracts/libraries/StorageLib.sol';
6 |
7 | /**
8 | * @title VersionedInitializable
9 | *
10 | * @dev Helper contract to implement initializer functions. To use it, replace
11 | * the constructor with a function that has the `initializer` modifier.
12 | * WARNING: Unlike constructors, initializer functions must be manually
13 | * invoked. This applies both to deploying an Initializable contract, as well
14 | * as extending an Initializable contract via inheritance.
15 | * WARNING: When used with inheritance, manual care must be taken to not invoke
16 | * a parent initializer twice, or ensure that all initializers are idempotent,
17 | * because this is not dealt with automatically as with constructors.
18 | *
19 | * This is slightly modified from [Aave's version.](https://github.com/aave/protocol-v2/blob/6a503eb0a897124d8b9d126c915ffdf3e88343a9/contracts/protocol/libraries/aave-upgradeability/VersionedInitializable.sol)
20 | *
21 | * @author Lens Protocol, inspired by Aave's implementation, which is in turn inspired by OpenZeppelin's
22 | * Initializable contract
23 | */
24 | abstract contract VersionedInitializable {
25 | address private immutable originalImpl;
26 |
27 | /**
28 | * @dev Modifier to use in the initializer function of a contract.
29 | */
30 | modifier initializer() {
31 | if (address(this) == originalImpl) {
32 | revert Errors.CannotInitImplementation();
33 | }
34 | if (getRevision() <= StorageLib.getLastInitializedRevision()) {
35 | revert Errors.Initialized();
36 | }
37 | StorageLib.setLastInitializedRevision(getRevision());
38 | _;
39 | }
40 |
41 | constructor() {
42 | originalImpl = address(this);
43 | }
44 |
45 | /**
46 | * @dev returns the revision number of the contract
47 | * Needs to be defined in the inherited class as a constant.
48 | **/
49 | function getRevision() internal pure virtual returns (uint256);
50 | }
51 |
--------------------------------------------------------------------------------
/contracts/interfaces/ICollectModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {Types} from 'contracts/libraries/constants/Types.sol';
6 |
7 | /**
8 | * @title ICollectModule
9 | * @author Lens Protocol
10 | *
11 | * @notice This is the standard interface for all Lens-compatible CollectModules.
12 | * Collect modules allow users to execute custom logic upon a collect action over a publication, like:
13 | * - Only allow the collect if the collector is following the publication author.
14 | * - Only allow the collect if the collector has made a payment to
15 | * - Allow any collect but only during the first 24 hours.
16 | * - Etc.
17 | */
18 | interface ICollectModule {
19 | /**
20 | * @notice Initializes data for a given publication being published.
21 | * @custom:permissions LensHub.
22 | *
23 | * @param profileId The token ID of the profile publishing the publication.
24 | * @param pubId The associated publication's LensHub publication ID.
25 | * @param transactionExecutor The owner or an approved delegated executor.
26 | * @param data Arbitrary data __passed from the user!__ to be decoded.
27 | *
28 | * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by
29 | * indexers or UIs.
30 | */
31 | function initializePublicationCollectModule(
32 | uint256 profileId,
33 | uint256 pubId,
34 | address transactionExecutor,
35 | bytes calldata data
36 | ) external returns (bytes memory);
37 |
38 | /**
39 | * @notice Processes a collect action for a given publication.
40 | * @custom:permissions LensHub.
41 | *
42 | * @param processCollectParams The parameters for the collect action.
43 | *
44 | * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by
45 | * indexers or UIs.
46 | */
47 | function processCollect(Types.ProcessCollectParams calldata processCollectParams) external returns (bytes memory);
48 | }
49 |
--------------------------------------------------------------------------------
/contracts/interfaces/ICollectNFT.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ICollectNFT
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the CollectNFT contract. Which is cloned upon the first collect for any given
10 | * publication.
11 | */
12 | interface ICollectNFT {
13 | /**
14 | * @notice Initializes the collect NFT, setting the feed as the privileged minter, storing the collected publication pointer
15 | * and initializing the name and symbol in the LensNFTBase contract.
16 | * @custom:permissions CollectPublicationAction.
17 | *
18 | * @param profileId The token ID of the profile in the hub that this Collect NFT points to.
19 | * @param pubId The profile publication ID in the hub that this Collect NFT points to.
20 | */
21 | function initialize(uint256 profileId, uint256 pubId) external;
22 |
23 | /**
24 | * @notice Mints a collect NFT to the specified address. This can only be called by the hub and is called
25 | * upon collection.
26 | * @custom:permissions CollectPublicationAction.
27 | *
28 | * @param to The address to mint the NFT to.
29 | *
30 | * @return uint256 An integer representing the minted token ID.
31 | */
32 | function mint(address to) external returns (uint256);
33 |
34 | /**
35 | * @notice Returns the source publication of this collect NFT.
36 | *
37 | * @return tuple First is the profile ID, and second is the publication ID.
38 | */
39 | function getSourcePublicationPointer() external view returns (uint256, uint256);
40 | }
41 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721Burnable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title IERC721Burnable
7 | * @author Lens Protocol
8 | *
9 | * @notice Extension of ERC-721 including a function that allows the token to be burned.
10 | */
11 | interface IERC721Burnable {
12 | /**
13 | * @notice Burns an NFT, removing it from circulation and essentially destroying it.
14 | * @custom:permission Owner of the NFT.
15 | *
16 | * @param tokenId The token ID of the token to burn.
17 | */
18 | function burn(uint256 tokenId) external;
19 | }
20 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721MetaTx.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title IERC721MetaTx
7 | * @author Lens Protocol
8 | *
9 | * @notice Extension of ERC-721 including meta-tx signatures related functions.
10 | */
11 | interface IERC721MetaTx {
12 | /**
13 | * @notice Returns the current signature nonce of the given signer.
14 | *
15 | * @param signer The address for which to query the nonce.
16 | *
17 | * @return uint256 The current nonce of the given signer.
18 | */
19 | function nonces(address signer) external view returns (uint256);
20 |
21 | /**
22 | * @notice Returns the EIP-712 domain separator for this contract.
23 | *
24 | * @return bytes32 The domain separator.
25 | */
26 | function getDomainSeparator() external view returns (bytes32);
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/interfaces/IERC721Timestamped.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {Types} from 'contracts/libraries/constants/Types.sol';
6 |
7 | /**
8 | * @title IERC721Timestamped
9 | * @author Lens Protocol
10 | *
11 | * @notice Extension of ERC-721 including a struct for token data, which contains the owner and the mint timestamp, as
12 | * well as their associated getters.
13 | */
14 | interface IERC721Timestamped {
15 | /**
16 | * @notice Returns the mint timestamp associated with a given NFT.
17 | *
18 | * @param tokenId The token ID of the NFT to query the mint timestamp for.
19 | *
20 | * @return uint256 Mint timestamp, this is stored as a uint96 but returned as a uint256 to reduce unnecessary
21 | * padding.
22 | */
23 | function mintTimestampOf(uint256 tokenId) external view returns (uint256);
24 |
25 | /**
26 | * @notice Returns the token data associated with a given NFT. This allows fetching the token owner and
27 | * mint timestamp in a single call.
28 | *
29 | * @param tokenId The token ID of the NFT to query the token data for.
30 | *
31 | * @return TokenData A struct containing both the owner address and the mint timestamp.
32 | */
33 | function tokenDataOf(uint256 tokenId) external view returns (Types.TokenData memory);
34 |
35 | /**
36 | * @notice Returns whether a token with the given token ID exists.
37 | *
38 | * @param tokenId The token ID of the NFT to check existence for.
39 | *
40 | * @return bool True if the token exists.
41 | */
42 | function exists(uint256 tokenId) external view returns (bool);
43 |
44 | /**
45 | * @notice Returns the amount of tokens in circulation.
46 | *
47 | * @return uint256 The current total supply of tokens.
48 | */
49 | function totalSupply() external view returns (uint256);
50 | }
51 |
--------------------------------------------------------------------------------
/contracts/interfaces/IFollowModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title IFollowModule
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the standard interface for all Lens-compatible Follow Modules.
10 | * These are responsible for processing the follow actions and can be used to implement any kind of follow logic.
11 | * For example:
12 | * - Token-gated follows (e.g. a user must hold a certain amount of a token to follow a profile).
13 | * - Paid follows (e.g. a user must pay a certain amount of a token to follow a profile).
14 | * - Rewarding users for following a profile.
15 | * - Etc.
16 | */
17 | interface IFollowModule {
18 | /**
19 | * @notice Initializes a follow module for a given Lens profile.
20 | * @custom:permissions LensHub.
21 | *
22 | * @param profileId The Profile ID to initialize this follow module for.
23 | * @param transactionExecutor The address of the transaction executor (e.g. for any funds to transferFrom).
24 | * @param data Arbitrary data passed from the user to be decoded by the Follow Module during initialization.
25 | *
26 | * @return bytes The encoded data to be emitted from the hub.
27 | */
28 | function initializeFollowModule(
29 | uint256 profileId,
30 | address transactionExecutor,
31 | bytes calldata data
32 | ) external returns (bytes memory);
33 |
34 | /**
35 | * @notice Processes a given follow.
36 | * @custom:permissions LensHub.
37 | *
38 | * @param followerProfileId The Profile ID of the follower's profile.
39 | * @param followTokenId The Follow Token ID that is being used to follow. Zero if we are processing a new fresh
40 | * follow, in this case, the follow ID assigned can be queried from the Follow NFT collection if needed.
41 | * @param transactionExecutor The address of the transaction executor (e.g. for any funds to transferFrom).
42 | * @param targetProfileId The token ID of the profile being followed.
43 | * @param data Arbitrary data passed by the follower.
44 | *
45 | * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by
46 | * indexers or UIs.
47 | */
48 | function processFollow(
49 | uint256 followerProfileId,
50 | uint256 followTokenId,
51 | address transactionExecutor,
52 | uint256 targetProfileId,
53 | bytes calldata data
54 | ) external returns (bytes memory);
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILegacyCollectModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ICollectModule
7 | * @author Lens Protocol
8 | * @custom:pending-deprecation
9 | *
10 | * @notice This is the deprecated interface for previously Lens-compatible CollectModules.
11 | */
12 | interface ILegacyCollectModule {
13 | /**
14 | * @notice Initializes data for a given publication being published. This can only be called by the hub.
15 | *
16 | * @param profileId The token ID of the profile publishing the publication.
17 | * @param pubId The associated publication's LensHub publication ID.
18 | * @param data Arbitrary data __passed from the user!__ to be decoded.
19 | *
20 | * @return bytes An ABI-encoded encapsulating the execution's state changes. This will be emitted by the
21 | * hub alongside the collect module's address and should be consumed by front ends.
22 | */
23 | function initializePublicationCollectModule(
24 | uint256 profileId,
25 | uint256 pubId,
26 | bytes calldata data
27 | ) external returns (bytes memory);
28 |
29 | /**
30 | * @notice Processes a collect action for a given publication, this can only be called by the hub.
31 | *
32 | * @param referrerProfileId The LensHub profile token ID of the referrer's profile (only different in case of mirrors).
33 | * @param collector The collector address.
34 | * @param profileId The token ID of the profile associated with the publication being collected.
35 | * @param pubId The LensHub publication ID associated with the publication being collected.
36 | * @param data Arbitrary data __passed from the collector!__ to be decoded.
37 | */
38 | function processCollect(
39 | uint256 referrerProfileId,
40 | address collector,
41 | uint256 profileId,
42 | uint256 pubId,
43 | bytes calldata data
44 | ) external;
45 | }
46 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILegacyCollectNFT.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ILegacyCollectNFT
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the Lens V1 CollectNFT contract. Which is cloned upon the first collect for any
10 | * given publication.
11 | */
12 | interface ILegacyCollectNFT {
13 | /**
14 | * @notice Initializes the collect NFT, setting the feed as the privileged minter, storing the collected publication
15 | * pointer and initializing the name and symbol in the LensNFTBase contract.
16 | * @custom:permissions LensHub.
17 | *
18 | * @param profileId The token ID of the profile in the hub that this Collect NFT points to.
19 | * @param pubId The profile publication ID in the hub that this Collect NFT points to.
20 | * @param name The name to set for this NFT.
21 | * @param symbol The symbol to set for this NFT.
22 | */
23 | function initialize(
24 | uint256 profileId,
25 | uint256 pubId,
26 | string calldata name,
27 | string calldata symbol
28 | ) external;
29 |
30 | /**
31 | * @notice Mints a collect NFT to the specified address.
32 | * @custom:permissions LensHub.
33 | *
34 | * @param to The address to mint the NFT to.
35 | *
36 | * @return uint256 An integer representing the minted token ID.
37 | */
38 | function mint(address to) external returns (uint256);
39 |
40 | /**
41 | * @notice Returns the source publication of this collect NFT.
42 | *
43 | * @return tuple First is the profile ID, and second is the publication ID.
44 | */
45 | function getSourcePublicationPointer() external view returns (uint256, uint256);
46 | }
47 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILegacyReferenceModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title IReferenceModule
7 | * @author Lens Protocol
8 | * @custom:pending-deprecation
9 | *
10 | * @notice This is the deprecated interface for previously Lens-compatible ReferenceModules.
11 | */
12 | interface ILegacyReferenceModule {
13 | /**
14 | * @notice Initializes data for a given publication being published. This can only be called by the hub.
15 | *
16 | * @param profileId The token ID of the profile publishing the publication.
17 | * @param pubId The associated publication's LensHub publication ID.
18 | * @param data Arbitrary data passed from the user to be decoded.
19 | *
20 | * @return bytes An ABI-encoded data encapsulating the execution's state changes. This will be emitted by the
21 | * hub alongside the collect module's address and should be consumed by front ends.
22 | */
23 | function initializeReferenceModule(
24 | uint256 profileId,
25 | uint256 pubId,
26 | bytes calldata data
27 | ) external returns (bytes memory);
28 |
29 | /**
30 | * @notice Processes a comment action referencing a given publication. This can only be called by the hub.
31 | *
32 | * @param profileId The token ID of the profile associated with the publication being published.
33 | * @param pointedProfileId The profile ID of the profile associated with the publication being referenced.
34 | * @param pointedPubId The publication ID of the publication being referenced.
35 | * @param data Arbitrary data __passed from the commenter!__ to be decoded.
36 | */
37 | function processComment(
38 | uint256 profileId,
39 | uint256 pointedProfileId,
40 | uint256 pointedPubId,
41 | bytes calldata data
42 | ) external;
43 |
44 | /**
45 | * @notice Processes a mirror action referencing a given publication. This can only be called by the hub.
46 | *
47 | * @param profileId The token ID of the profile associated with the publication being published.
48 | * @param pointedProfileId The profile ID of the profile associated with the publication being referenced.
49 | * @param pointedPubId The publication ID of the publication being referenced.
50 | * @param data Arbitrary data __passed from the mirrorer!__ to be decoded.
51 | */
52 | function processMirror(
53 | uint256 profileId,
54 | uint256 pointedProfileId,
55 | uint256 pointedPubId,
56 | bytes calldata data
57 | ) external;
58 | }
59 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensERC721.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
6 | import {IERC721Timestamped} from 'contracts/interfaces/IERC721Timestamped.sol';
7 | import {IERC721Burnable} from 'contracts/interfaces/IERC721Burnable.sol';
8 | import {IERC721MetaTx} from 'contracts/interfaces/IERC721MetaTx.sol';
9 | import {IERC721Metadata} from '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
10 |
11 | interface ILensERC721 is IERC721, IERC721Timestamped, IERC721Burnable, IERC721MetaTx, IERC721Metadata {}
12 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensHandles.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
6 |
7 | /**
8 | * @title ILensHandles
9 | * @author Lens Protocol
10 | *
11 | * @notice This is the interface for the LensHandles contract that is responsible for minting and burning handle NFTs.
12 | * A handle is composed of a local name and a namespace, separated by a dot.
13 | * Example: `satoshi.lens` is a handle composed of the local name `satoshi` and the namespace `lens`.
14 | */
15 | interface ILensHandles is IERC721 {
16 | /**
17 | * @notice Mints a handle NFT in the given namespace.
18 | * @custom:permissions Only LensHandles contract's owner or LensHub.
19 | *
20 | * @param to The address to mint the handle to.
21 | * @param localName The local name of the handle (the part before ".lens").
22 | *
23 | * @return uint256 The ID of the handle NFT minted.
24 | */
25 | function mintHandle(address to, string calldata localName) external returns (uint256);
26 |
27 | /**
28 | * @notice Burns a handle NFT.
29 | * @custom:permissions Owner of Handle NFT.
30 | *
31 | * @param tokenId The ID of the handle NFT to burn.
32 | */
33 | function burn(uint256 tokenId) external;
34 |
35 | /**
36 | * @notice Gets the namespace of the contract. It's 'lens' for the LensHandles contract.
37 | *
38 | * @return string The namespace of the contract.
39 | */
40 | function getNamespace() external pure returns (string memory);
41 |
42 | /**
43 | * @notice Gets the hash of the namespace of the contract. It's keccak256('lens') for the LensHandles contract.
44 | *
45 | * @return bytes32 The hash of the namespace of the contract.
46 | */
47 | function getNamespaceHash() external pure returns (bytes32);
48 |
49 | /**
50 | * @notice Returns whether `tokenId` exists.
51 | *
52 | * Tokens start existing when they are minted (`_mint`),
53 | * and stop existing when they are burned (`_burn`).
54 | *
55 | * @return bool Whether the token exists.
56 | */
57 | function exists(uint256 tokenId) external view returns (bool);
58 | }
59 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensHub.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {ILensProtocol} from 'contracts/interfaces/ILensProtocol.sol';
6 | import {ILensGovernable} from 'contracts/interfaces/ILensGovernable.sol';
7 | import {ILensHubEventHooks} from 'contracts/interfaces/ILensHubEventHooks.sol';
8 | import {ILensImplGetters} from 'contracts/interfaces/ILensImplGetters.sol';
9 | import {ILensProfiles} from 'contracts/interfaces/ILensProfiles.sol';
10 |
11 | interface ILensHub is ILensProfiles, ILensProtocol, ILensGovernable, ILensHubEventHooks, ILensImplGetters {}
12 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensHubEventHooks.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ILensHubEventHooks
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the LensHub contract's event hooks. As we want most of the core events to be
10 | * emitted by the LensHub contract, event hooks are needed for core events generated by pheripheral contracts.
11 | */
12 | interface ILensHubEventHooks {
13 | /**
14 | * @dev Helper function to emit an `Unfollowed` event from the hub, to be consumed by indexers to track unfollows.
15 | * @custom:permissions FollowNFT of the Profile unfollowed.
16 | *
17 | * @param unfollowerProfileId The ID of the profile that executed the unfollow.
18 | * @param idOfProfileUnfollowed The ID of the profile that was unfollowed.
19 | */
20 | function emitUnfollowedEvent(uint256 unfollowerProfileId, uint256 idOfProfileUnfollowed) external;
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensHubInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ILensHub
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the LensHub contract, the main entry point for the Lens Protocol.
10 | * You'll find all the events and external functions, as well as the reasoning behind them here.
11 | */
12 | interface ILensHubInitializable {
13 | /**
14 | * @notice Initializes the LensHub, setting the initial governance address, the name and symbol of the profiles
15 | * in the LensNFTBase contract, and Protocol State (Paused).
16 | * @dev This is assuming a proxy pattern is implemented.
17 | * @custom:permissions Callable once.
18 | *
19 | * @param name The name of the Profile NFT.
20 | * @param symbol The symbol of the Profile NFT.
21 | * @param newGovernance The governance address to set.
22 | */
23 | function initialize(
24 | string calldata name,
25 | string calldata symbol,
26 | address newGovernance
27 | ) external;
28 | }
29 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensImplGetters.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title ILensImplGetters
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the LensHub contract's implementation getters. These implementations will be used
10 | * for deploying each respective contract for each profile.
11 | */
12 | interface ILensImplGetters {
13 | /**
14 | * @notice Returns the Follow NFT implementation address that is used for all deployed Follow NFTs.
15 | *
16 | * @return address The Follow NFT implementation address.
17 | */
18 | function getFollowNFTImpl() external view returns (address);
19 |
20 | /**
21 | * @notice Returns the Collect NFT implementation address that is used for each new deployed Collect NFT.
22 | * @custom:pending-deprecation
23 | *
24 | * @return address The Collect NFT implementation address.
25 | */
26 | function getCollectNFTImpl() external view returns (address);
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/interfaces/ILensProfiles.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {ILensERC721} from 'contracts/interfaces/ILensERC721.sol';
6 |
7 | interface ILensProfiles is ILensERC721 {
8 | /**
9 | * @notice DANGER: Triggers disabling the profile protection mechanism for the msg.sender, which will allow
10 | * transfers or approvals over profiles held by it.
11 | * Disabling the mechanism will have a 7-day timelock before it becomes effective, allowing the owner to re-enable
12 | * the protection back in case of being under attack.
13 | * The protection layer only applies to EOA wallets.
14 | */
15 | function DANGER__disableTokenGuardian() external;
16 |
17 | /**
18 | * @notice Enables back the profile protection mechanism for the msg.sender, preventing profile transfers or
19 | * approvals (except when revoking them).
20 | * The protection layer only applies to EOA wallets.
21 | */
22 | function enableTokenGuardian() external;
23 |
24 | /**
25 | * @notice Returns the timestamp at which the Token Guardian will become effectively disabled.
26 | *
27 | * @param wallet The address to check the timestamp for.
28 | *
29 | * @return uint256 The timestamp at which the Token Guardian will become effectively disabled. Zero if enabled.
30 | */
31 | function getTokenGuardianDisablingTimestamp(address wallet) external view returns (uint256);
32 | }
33 |
--------------------------------------------------------------------------------
/contracts/interfaces/IModuleGlobals.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title IModuleGlobals
7 | * @author Lens Protocol
8 | *
9 | * @notice This is the interface for the ModuleGlobals contract, a data-providing contract to be queried by modules
10 | * for the most up-to-date parameters.
11 | * ModuleGlobals contract handles the following:
12 | * - Governance address for modules
13 | * - Lens treasury address
14 | * - Lens treasury fee
15 | * - Whitelist of currencies allowed for use in modules
16 | */
17 | interface IModuleGlobals {
18 | /**
19 | * @notice Sets the modules governance address.
20 | * @custom:permissions Modules Governance
21 | *
22 | * @param newGovernance The new governance address to set.
23 | */
24 | function setGovernance(address newGovernance) external;
25 |
26 | /**
27 | * @notice Sets the treasury address.
28 | * @custom:permissions Modules Governance
29 | *
30 | * @param newTreasury The new treasury address to set.
31 | */
32 | function setTreasury(address newTreasury) external;
33 |
34 | /**
35 | * @notice Sets the treasury fee.
36 | * @custom:permissions Modules Governance
37 | *
38 | * @param newTreasuryFee The new treasury fee to set.
39 | */
40 | function setTreasuryFee(uint16 newTreasuryFee) external;
41 |
42 | /**
43 | * @notice Adds or removes a currency from the whitelist.
44 | * @custom:permissions Modules Governance
45 | *
46 | * @param currency The currency to add or remove from the whitelist.
47 | * @param toWhitelist Whether to add (true) or remove (false) the currency from the whitelist.
48 | */
49 | function whitelistCurrency(address currency, bool toWhitelist) external;
50 |
51 | /////////////////////////////////
52 | /// VIEW FUNCTIONS ///
53 | /////////////////////////////////
54 |
55 | /**
56 | * @notice Returns whether a currency is whitelisted.
57 | *
58 | * @param currency The currency to query the whitelist for.
59 | *
60 | * @return bool True if the queried currency is whitelisted, false otherwise.
61 | */
62 | function isCurrencyWhitelisted(address currency) external view returns (bool);
63 |
64 | /**
65 | * @notice Returns the governance address.
66 | *
67 | * @return address The governance address.
68 | */
69 | function getGovernance() external view returns (address);
70 |
71 | /**
72 | * @notice Returns the treasury address.
73 | *
74 | * @return address The treasury address.
75 | */
76 | function getTreasury() external view returns (address);
77 |
78 | /**
79 | * @notice Returns the treasury fee.
80 | *
81 | * @return uint16 The treasury fee.
82 | */
83 | function getTreasuryFee() external view returns (uint16);
84 |
85 | /**
86 | * @notice Returns the treasury address and treasury fee in a single call.
87 | *
88 | * @return tuple First, the treasury address, second, the treasury fee.
89 | */
90 | function getTreasuryData() external view returns (address, uint16);
91 | }
92 |
--------------------------------------------------------------------------------
/contracts/interfaces/IPublicationActionModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {Types} from 'contracts/libraries/constants/Types.sol';
6 |
7 | /**
8 | * @title IPublicationAction
9 | * @author Lens Protocol
10 | *
11 | * @notice This is the standard interface for all Lens-compatible Publication Actions.
12 | * Publication action modules allow users to execute actions directly from a publication, like:
13 | * - Minting NFTs.
14 | * - Collecting a publication.
15 | * - Sending funds to the publication author (e.g. tipping).
16 | * - Etc.
17 | * Referrers are supported, so any publication or profile that references the publication can receive a share from the
18 | * publication's action if the action module supports it.
19 | */
20 | interface IPublicationActionModule {
21 | /**
22 | * @notice Initializes the action module for the given publication being published with this Action module.
23 | * @custom:permissions LensHub.
24 | *
25 | * @param profileId The profile ID of the author publishing the content with this Publication Action.
26 | * @param pubId The publication ID being published.
27 | * @param transactionExecutor The address of the transaction executor (e.g. for any funds to transferFrom).
28 | * @param data Arbitrary data passed from the user to be decoded by the Action Module during initialization.
29 | *
30 | * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by
31 | * indexers or UIs.
32 | */
33 | function initializePublicationAction(
34 | uint256 profileId,
35 | uint256 pubId,
36 | address transactionExecutor,
37 | bytes calldata data
38 | ) external returns (bytes memory);
39 |
40 | /**
41 | * @notice Processes the action for a given publication. This includes the action's logic and any monetary/token
42 | * operations.
43 | * @custom:permissions LensHub.
44 | *
45 | * @param processActionParams The parameters needed to execute the publication action.
46 | *
47 | * @return bytes Any custom ABI-encoded data. This will be a LensHub event params that can be used by
48 | * indexers or UIs.
49 | */
50 | function processPublicationAction(Types.ProcessActionParams calldata processActionParams)
51 | external
52 | returns (bytes memory);
53 | }
54 |
--------------------------------------------------------------------------------
/contracts/libraries/constants/Errors.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | library Errors {
6 | error CannotInitImplementation();
7 | error Initialized();
8 | error SignatureExpired();
9 | error SignatureInvalid();
10 | error InvalidOwner();
11 | error NotOwnerOrApproved();
12 | error NotHub();
13 | error TokenDoesNotExist();
14 | error NotGovernance();
15 | error NotGovernanceOrEmergencyAdmin();
16 | error EmergencyAdminCanOnlyPauseFurther();
17 | error NotProfileOwner();
18 | error PublicationDoesNotExist();
19 | error ProfileImageURILengthInvalid();
20 | error CallerNotFollowNFT();
21 | error CallerNotCollectNFT(); // Legacy
22 | error ArrayMismatch();
23 | error NotWhitelisted();
24 | error InvalidParameter();
25 | error ExecutorInvalid();
26 | error Blocked();
27 | error SelfBlock();
28 | error NotFollowing();
29 | error SelfFollow();
30 | error InvalidReferrer();
31 | error InvalidPointedPub();
32 | error NonERC721ReceiverImplementer();
33 | error AlreadyEnabled();
34 |
35 | // Internal Errors
36 | error MaxActionModuleIdReached(); // This means we need an upgrade
37 |
38 | // Module Errors
39 | error InitParamsInvalid();
40 | error ActionNotAllowed();
41 |
42 | error CollectNotAllowed(); // Used in LegacyCollectLib (pending deprecation)
43 |
44 | // MultiState Errors
45 | error Paused();
46 | error PublishingPaused();
47 |
48 | // Profile Guardian Errors
49 | error GuardianEnabled();
50 | error NotEOA();
51 | error DisablingAlreadyTriggered();
52 | }
53 |
--------------------------------------------------------------------------------
/contracts/misc/ImmutableOwnable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | contract ImmutableOwnable {
6 | address public immutable OWNER;
7 | address public immutable LENS_HUB;
8 |
9 | error OnlyOwner();
10 | error OnlyOwnerOrHub();
11 |
12 | modifier onlyOwner() {
13 | if (msg.sender != OWNER) {
14 | revert OnlyOwner();
15 | }
16 | _;
17 | }
18 |
19 | modifier onlyOwnerOrHub() {
20 | if (msg.sender != OWNER && msg.sender != LENS_HUB) {
21 | revert OnlyOwnerOrHub();
22 | }
23 | _;
24 | }
25 |
26 | constructor(address owner, address lensHub) {
27 | OWNER = owner;
28 | LENS_HUB = lensHub;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/contracts/misc/LensHubInitializable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {LensHub} from 'contracts/LensHub.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {GovernanceLib} from 'contracts/libraries/GovernanceLib.sol';
8 | import {ILensHubInitializable} from 'contracts/interfaces/ILensHubInitializable.sol';
9 | import {VersionedInitializable} from 'contracts/base/upgradeability/VersionedInitializable.sol';
10 |
11 | /**
12 | * @title LensHubInitializable
13 | * @author Lens Protocol
14 | *
15 | * @notice Extension of LensHub contract that includes initialization for fresh deployments.
16 | */
17 | contract LensHubInitializable is LensHub, VersionedInitializable, ILensHubInitializable {
18 | // Constant for upgradeability purposes, see VersionedInitializable.
19 | // Do not confuse it with the EIP-712 version number.
20 | uint256 internal constant REVISION = 1;
21 |
22 | constructor(
23 | address moduleGlobals,
24 | address followNFTImpl,
25 | address collectNFTImpl,
26 | address lensHandlesAddress,
27 | address tokenHandleRegistryAddress,
28 | address legacyFeeFollowModule,
29 | address legacyProfileFollowModule,
30 | address newFeeFollowModule,
31 | uint256 tokenGuardianCooldown
32 | )
33 | LensHub(
34 | moduleGlobals,
35 | followNFTImpl,
36 | collectNFTImpl,
37 | lensHandlesAddress,
38 | tokenHandleRegistryAddress,
39 | legacyFeeFollowModule,
40 | legacyProfileFollowModule,
41 | newFeeFollowModule,
42 | tokenGuardianCooldown
43 | )
44 | {}
45 |
46 | /**
47 | * @inheritdoc ILensHubInitializable
48 | * @custom:permissions Callable once. This is expected to be atomically called during the deployment by the Proxy.
49 | */
50 | function initialize(
51 | string calldata name,
52 | string calldata symbol,
53 | address newGovernance
54 | ) external override initializer {
55 | super._initialize(name, symbol);
56 | GovernanceLib.initState(Types.ProtocolState.Paused);
57 | GovernanceLib.setGovernance(newGovernance);
58 | }
59 |
60 | function getRevision() internal pure virtual override returns (uint256) {
61 | return REVISION;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/contracts/misc/LensV2Migration.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {MigrationLib} from 'contracts/libraries/MigrationLib.sol';
6 |
7 | // Handles
8 | import {LensHandles} from 'contracts/namespaces/LensHandles.sol';
9 | import {TokenHandleRegistry} from 'contracts/namespaces/TokenHandleRegistry.sol';
10 |
11 | contract LensV2Migration {
12 | address internal immutable FEE_FOLLOW_MODULE;
13 | address internal immutable PROFILE_FOLLOW_MODULE;
14 | address internal immutable NEW_FEE_FOLLOW_MODULE;
15 |
16 | LensHandles internal immutable lensHandles;
17 | TokenHandleRegistry internal immutable tokenHandleRegistry;
18 |
19 | constructor(
20 | address legacyFeeFollowModule,
21 | address legacyProfileFollowModule,
22 | address newFeeFollowModule,
23 | address lensHandlesAddress,
24 | address tokenHandleRegistryAddress
25 | ) {
26 | FEE_FOLLOW_MODULE = legacyFeeFollowModule;
27 | PROFILE_FOLLOW_MODULE = legacyProfileFollowModule;
28 | NEW_FEE_FOLLOW_MODULE = newFeeFollowModule;
29 | lensHandles = LensHandles(lensHandlesAddress);
30 | tokenHandleRegistry = TokenHandleRegistry(tokenHandleRegistryAddress);
31 | }
32 |
33 | function batchMigrateProfiles(uint256[] calldata profileIds) external {
34 | MigrationLib.batchMigrateProfiles(profileIds, lensHandles, tokenHandleRegistry);
35 | }
36 |
37 | function batchMigrateFollows(
38 | uint256[] calldata followerProfileIds,
39 | uint256[] calldata idsOfProfileFollowed,
40 | uint256[] calldata followTokenIds
41 | ) external {
42 | MigrationLib.batchMigrateFollows(followerProfileIds, idsOfProfileFollowed, followTokenIds);
43 | }
44 |
45 | function batchMigrateFollowModules(uint256[] calldata profileIds) external {
46 | MigrationLib.batchMigrateFollowModules(
47 | profileIds,
48 | FEE_FOLLOW_MODULE,
49 | PROFILE_FOLLOW_MODULE,
50 | NEW_FEE_FOLLOW_MODULE
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/contracts/misc/ProfileCreationProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {ImmutableOwnable} from 'contracts/misc/ImmutableOwnable.sol';
8 |
9 | import {ILensHandles} from 'contracts/interfaces/ILensHandles.sol';
10 | import {ITokenHandleRegistry} from 'contracts/interfaces/ITokenHandleRegistry.sol';
11 |
12 | /**
13 | * @title ProfileCreationProxy
14 | * @author Lens Protocol
15 | *
16 | * @notice This is an ownable proxy contract that enforces ".lens" handle suffixes at profile creation.
17 | * Only the owner can create profiles.
18 | */
19 | contract ProfileCreationProxy is ImmutableOwnable {
20 | ILensHandles immutable LENS_HANDLES;
21 | ITokenHandleRegistry immutable TOKEN_HANDLE_REGISTRY;
22 |
23 | constructor(
24 | address owner,
25 | address hub,
26 | address lensHandles,
27 | address tokenHandleRegistry
28 | ) ImmutableOwnable(owner, hub) {
29 | LENS_HANDLES = ILensHandles(lensHandles);
30 | TOKEN_HANDLE_REGISTRY = ITokenHandleRegistry(tokenHandleRegistry);
31 | }
32 |
33 | function proxyCreateProfile(Types.CreateProfileParams calldata createProfileParams)
34 | external
35 | onlyOwner
36 | returns (uint256)
37 | {
38 | return ILensHub(LENS_HUB).createProfile(createProfileParams);
39 | }
40 |
41 | function proxyCreateProfileWithHandle(Types.CreateProfileParams memory createProfileParams, string calldata handle)
42 | external
43 | onlyOwner
44 | returns (uint256, uint256)
45 | {
46 | // We mint the handle & profile to this contract first, then link it to the profile
47 | // This will not allow to initialize follow modules that require funds from the msg.sender,
48 | // but we assume only simple follow modules should be set during profile creation.
49 | // Complex ones can be set after the profile is created.
50 | address destination = createProfileParams.to;
51 | createProfileParams.to = address(this);
52 | uint256 profileId = ILensHub(LENS_HUB).createProfile(createProfileParams);
53 | uint256 handleId = LENS_HANDLES.mintHandle(address(this), handle);
54 |
55 | TOKEN_HANDLE_REGISTRY.link({handleId: handleId, tokenId: profileId});
56 |
57 | // Transfer the handle & profile to the destination
58 | LENS_HANDLES.transferFrom(address(this), destination, handleId);
59 | ILensHub(LENS_HUB).transferFrom(address(this), destination, profileId);
60 |
61 | return (profileId, handleId);
62 | }
63 |
64 | function proxyCreateHandle(address to, string calldata handle) external onlyOwner returns (uint256) {
65 | return LENS_HANDLES.mintHandle(to, handle);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/contracts/misc/UIDataProvider.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 |
8 | /**
9 | * @dev This struct contains both a `Profile` and a `Publication`.
10 | *
11 | * @param profile A standard profile struct.
12 | * @param publication A standard Publication.
13 | */
14 | struct LatestData {
15 | Types.Profile profile;
16 | Types.Publication publication;
17 | }
18 |
19 | /**
20 | * @title UIDataProvider
21 | * @author Lens Protocol
22 | *
23 | * @dev This is a helper contract to fetch a profile and its latest publication in a single call.
24 | */
25 | contract UIDataProvider {
26 | ILensHub immutable HUB;
27 |
28 | constructor(ILensHub hub) {
29 | HUB = hub;
30 | }
31 |
32 | /**
33 | * @notice Returns the profile struct and latest publication struct associated with the passed
34 | * profile ID.
35 | *
36 | * @param profileId The profile ID to query.
37 | *
38 | * @return LensData A struct containing the `Profile` and the `Publication` queried.
39 | */
40 | function getLatestDataByProfile(uint256 profileId) external view returns (LatestData memory) {
41 | Types.Profile memory profile = HUB.getProfile(profileId);
42 | uint256 pubCount = profile.pubCount;
43 | return LatestData(profile, HUB.getPublication(profileId, pubCount));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/contracts/misc/access/ControllableByContract.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
6 |
7 | contract ControllableByContract is Ownable {
8 | event ControllerContractUpdated(address previousControllerContract, address newControllerContract);
9 |
10 | error Unauthorized();
11 |
12 | address public controllerContract;
13 |
14 | modifier onlyOwnerOrControllerContract() {
15 | if (msg.sender != owner() && msg.sender != controllerContract) {
16 | revert Unauthorized();
17 | }
18 | _;
19 | }
20 |
21 | constructor(address owner) Ownable() {
22 | _transferOwnership(owner);
23 | }
24 |
25 | function clearControllerContract() external onlyOwnerOrControllerContract {
26 | emit ControllerContractUpdated(controllerContract, address(0));
27 | delete controllerContract;
28 | }
29 |
30 | function setControllerContract(address newControllerContract) external onlyOwner {
31 | emit ControllerContractUpdated(controllerContract, newControllerContract);
32 | controllerContract = newControllerContract;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/contracts/misc/access/ProxyAdmin.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {TransparentUpgradeableProxy} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol';
6 | import {ControllableByContract} from 'contracts/misc/access/ControllableByContract.sol';
7 |
8 | contract ProxyAdmin is ControllableByContract {
9 | TransparentUpgradeableProxy public immutable LENS_HUB_PROXY;
10 | address public previousImplementation;
11 |
12 | constructor(
13 | address lensHubAddress_,
14 | address previousImplementation_,
15 | address proxyAdminOwner_
16 | ) ControllableByContract(proxyAdminOwner_) {
17 | LENS_HUB_PROXY = TransparentUpgradeableProxy(payable(lensHubAddress_));
18 | previousImplementation = previousImplementation_;
19 | }
20 |
21 | function currentImplementation() external returns (address) {
22 | return LENS_HUB_PROXY.implementation();
23 | }
24 |
25 | /////////////////////////////////////////////////////////
26 | /// ONLY PROXY ADMIN OWNER ///
27 | /////////////////////////////////////////////////////////
28 |
29 | function rollbackLastUpgrade() external onlyOwner {
30 | LENS_HUB_PROXY.upgradeTo(previousImplementation);
31 | }
32 |
33 | function proxy_changeAdmin(address newAdmin) external onlyOwner {
34 | LENS_HUB_PROXY.changeAdmin(newAdmin);
35 | }
36 |
37 | /////////////////////////////////////////////////////////
38 | /// ONLY PROXY ADMIN OWNER OR CONTROLLER CONTRACT ///
39 | /////////////////////////////////////////////////////////
40 |
41 | function proxy_upgrade(address newImplementation) external onlyOwnerOrControllerContract {
42 | previousImplementation = LENS_HUB_PROXY.implementation();
43 | LENS_HUB_PROXY.upgradeTo(newImplementation);
44 | delete controllerContract;
45 | }
46 |
47 | function proxy_upgradeAndCall(address newImplementation, bytes calldata data)
48 | external
49 | onlyOwnerOrControllerContract
50 | {
51 | previousImplementation = LENS_HUB_PROXY.implementation();
52 | LENS_HUB_PROXY.upgradeToAndCall(newImplementation, data);
53 | delete controllerContract;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/modules/ActionRestricted.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {Errors} from 'contracts/modules/constants/Errors.sol';
6 |
7 | /**
8 | * @title ActionRestricted
9 | * @author Lens Protocol
10 | *
11 | * @notice This abstract contract adds a public `ACTION_MODULE` immutable field, and `onlyActionModule` modifier,
12 | * to inherit from contracts that have functions restricted to be only called by the Action Modules.
13 | */
14 | abstract contract ActionRestricted {
15 | address public immutable ACTION_MODULE;
16 |
17 | modifier onlyActionModule() {
18 | if (msg.sender != ACTION_MODULE) {
19 | revert Errors.NotActionModule();
20 | }
21 | _;
22 | }
23 |
24 | constructor(address actionModule) {
25 | ACTION_MODULE = actionModule;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/modules/FeeModuleBase.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.10;
4 |
5 | import {Errors} from 'contracts/modules/constants/Errors.sol';
6 | import {IModuleGlobals} from 'contracts/interfaces/IModuleGlobals.sol';
7 |
8 | /**
9 | * @title FeeModuleBase
10 | * @author Lens Protocol
11 | *
12 | * @notice This is an abstract contract to be inherited from by modules that require basic fee functionality.
13 | * It contains getters for module globals parameters as well as a validation function to check expected data.
14 | */
15 | abstract contract FeeModuleBase {
16 | uint16 internal constant BPS_MAX = 10000;
17 |
18 | IModuleGlobals public immutable MODULE_GLOBALS;
19 |
20 | constructor(address moduleGlobals) {
21 | MODULE_GLOBALS = IModuleGlobals(moduleGlobals);
22 | }
23 |
24 | function _currencyWhitelisted(address currency) internal view returns (bool) {
25 | return MODULE_GLOBALS.isCurrencyWhitelisted(currency);
26 | }
27 |
28 | function _treasuryData() internal view returns (address, uint16) {
29 | return MODULE_GLOBALS.getTreasuryData();
30 | }
31 |
32 | function _validateDataIsExpected(
33 | bytes calldata data,
34 | address currency,
35 | uint256 amount
36 | ) internal pure {
37 | (address decodedCurrency, uint256 decodedAmount) = abi.decode(data, (address, uint256));
38 | if (decodedAmount != amount || decodedCurrency != currency) {
39 | revert Errors.ModuleDataMismatch();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/contracts/modules/README.md:
--------------------------------------------------------------------------------
1 | This will be migrated to the [modules repository](https://github.com/lens-protocol/modules) after audit.
2 |
--------------------------------------------------------------------------------
/contracts/modules/constants/Errors.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | library Errors {
6 | error FollowInvalid();
7 | error ModuleDataMismatch();
8 | error NotHub();
9 | error InitParamsInvalid();
10 | error InvalidParams();
11 | error MintLimitExceeded();
12 | error CollectExpired();
13 | error NotActionModule();
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/modules/follow/RevertFollowModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.10;
4 |
5 | import {Errors} from 'contracts/modules/constants/Errors.sol';
6 | import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
7 |
8 | /**
9 | * @title RevertFollowModule
10 | * @author Lens Protocol
11 | *
12 | * @notice This follow module rejects all follow attempts.
13 | */
14 | contract RevertFollowModule is IFollowModule {
15 | /// @inheritdoc IFollowModule
16 | function initializeFollowModule(
17 | uint256, /* profileId */
18 | address, /* transactionExecutor */
19 | bytes calldata /* data */
20 | ) external pure override returns (bytes memory) {
21 | return '';
22 | }
23 |
24 | /**
25 | * @inheritdoc IFollowModule
26 | * @notice Processes a follow by rejecting it, reverting the transaction. Parameters are ignored.
27 | */
28 | function processFollow(
29 | uint256, /* followerProfileId */
30 | uint256, /* followTokenId */
31 | address, /* transactionExecutor */
32 | uint256, /* profileId */
33 | bytes calldata /* data */
34 | ) external pure override returns (bytes memory) {
35 | revert Errors.FollowInvalid();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/contracts/modules/interfaces/IBaseFeeCollectModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity >=0.6.0;
3 |
4 | import {ICollectModule} from 'contracts/interfaces/ICollectModule.sol';
5 | import {Types} from 'contracts/libraries/constants/Types.sol';
6 |
7 | /**
8 | * @notice A struct containing the necessary data to execute collect actions on a publication.
9 | *
10 | * @param amount The collecting cost associated with this publication. 0 for free collect.
11 | * @param collectLimit The maximum number of collects for this publication. 0 for no limit.
12 | * @param currency The currency associated with this publication.
13 | * @param currentCollects The current number of collects for this publication.
14 | * @param referralFee The referral fee associated with this publication.
15 | * @param followerOnly True if only followers of publisher may collect the post.
16 | * @param endTimestamp The end timestamp after which collecting is impossible. 0 for no expiry.
17 | * @param recipient Recipient of collect fees.
18 | */
19 | struct BaseProfilePublicationData {
20 | uint160 amount;
21 | uint96 collectLimit;
22 | address currency;
23 | uint96 currentCollects;
24 | address recipient;
25 | uint16 referralFee;
26 | bool followerOnly;
27 | uint72 endTimestamp;
28 | }
29 |
30 | /**
31 | * @notice A struct containing the necessary data to initialize this Base Collect Module.
32 | *
33 | * @param amount The collecting cost associated with this publication. 0 for free collect.
34 | * @param collectLimit The maximum number of collects for this publication. 0 for no limit.
35 | * @param currency The currency associated with this publication.
36 | * @param referralFee The referral fee associated with this publication.
37 | * @param followerOnly True if only followers of publisher may collect the post.
38 | * @param endTimestamp The end timestamp after which collecting is impossible. 0 for no expiry.
39 | * @param recipient Recipient of collect fees.
40 | */
41 | struct BaseFeeCollectModuleInitData {
42 | uint160 amount;
43 | uint96 collectLimit;
44 | address currency;
45 | uint16 referralFee;
46 | bool followerOnly;
47 | uint72 endTimestamp;
48 | address recipient;
49 | }
50 |
51 | interface IBaseFeeCollectModule is ICollectModule {
52 | /**
53 | * @notice Returns the Base publication data for a given publication, or an empty struct if that publication was not
54 | * initialized with this module.
55 | *
56 | * @param profileId The token ID of the profile mapped to the publication to query.
57 | * @param pubId The publication ID of the publication to query.
58 | *
59 | * @return The BaseProfilePublicationData struct mapped to that publication.
60 | */
61 | function getBasePublicationData(uint256 profileId, uint256 pubId)
62 | external
63 | view
64 | returns (BaseProfilePublicationData memory);
65 |
66 | /**
67 | * @notice Calculates and returns the collect fee of a publication.
68 | * @dev Override this function to use a different formula for the fee.
69 | *
70 | * @return The collect fee of the specified publication.
71 | */
72 | function calculateFee(Types.ProcessCollectParams calldata processCollectParams) external view returns (uint160);
73 | }
74 |
--------------------------------------------------------------------------------
/contracts/modules/interfaces/IWMATIC.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
6 |
7 | interface IWMATIC is IERC20 {
8 | function withdraw(uint256 amountToUnwrap) external;
9 |
10 | function deposit() external payable;
11 | }
12 |
--------------------------------------------------------------------------------
/contracts/modules/libraries/FollowValidationLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.19;
4 |
5 | import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
6 | import {Errors} from 'contracts/libraries/constants/Errors.sol';
7 |
8 | /**
9 | * @title FollowValidationLib
10 | * @author Lens Protocol
11 | *
12 | * @notice A library contract that verifies that a user is following another user and reverts if not.
13 | */
14 | library FollowValidationLib {
15 | function validateIsFollowing(
16 | address hub,
17 | uint256 followerProfileId,
18 | uint256 followedProfileId
19 | ) internal view {
20 | if (!ILensHub(hub).isFollowing(followerProfileId, followedProfileId)) {
21 | revert Errors.NotFollowing();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/contracts/modules/reference/FollowerOnlyReferenceModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.10;
4 |
5 | import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';
6 | import {HubRestricted} from 'contracts/base/HubRestricted.sol';
7 | import {Types} from 'contracts/libraries/constants/Types.sol';
8 | import {FollowValidationLib} from 'contracts/modules/libraries/FollowValidationLib.sol';
9 |
10 | /**
11 | * @title FollowerOnlyReferenceModule
12 | * @author Lens Protocol
13 | *
14 | * @notice A simple reference module that validates that comments, quotes or mirrors originate from a profile that
15 | * follows the profile of the original publication.
16 | */
17 | contract FollowerOnlyReferenceModule is HubRestricted, IReferenceModule {
18 | constructor(address hub) HubRestricted(hub) {}
19 |
20 | /**
21 | * @inheritdoc IReferenceModule
22 | *
23 | * @dev There is nothing needed at initialization.
24 | */
25 | function initializeReferenceModule(
26 | uint256, /* profileId */
27 | uint256, /* pubId */
28 | address, /* transactionExecutor */
29 | bytes calldata /* data */
30 | ) external pure returns (bytes memory) {
31 | return '';
32 | }
33 |
34 | /**
35 | * @inheritdoc IReferenceModule
36 | *
37 | * @dev Validates that the commenting profile's owner is a follower.
38 | */
39 | function processComment(Types.ProcessCommentParams calldata processCommentParams)
40 | external
41 | view
42 | override
43 | returns (bytes memory)
44 | {
45 | FollowValidationLib.validateIsFollowing({
46 | hub: HUB,
47 | followerProfileId: processCommentParams.profileId,
48 | followedProfileId: processCommentParams.pointedProfileId
49 | });
50 | return '';
51 | }
52 |
53 | /**
54 | * @inheritdoc IReferenceModule
55 | *
56 | * @dev Validates that the quoting profile's owner is a follower.
57 | */
58 | function processQuote(Types.ProcessQuoteParams calldata processQuoteParams)
59 | external
60 | view
61 | override
62 | returns (bytes memory)
63 | {
64 | FollowValidationLib.validateIsFollowing({
65 | hub: HUB,
66 | followerProfileId: processQuoteParams.profileId,
67 | followedProfileId: processQuoteParams.pointedProfileId
68 | });
69 | return '';
70 | }
71 |
72 | /**
73 | * @inheritdoc IReferenceModule
74 | *
75 | * @dev Validates that the mirroring profile's owner is a follower.
76 | */
77 | function processMirror(Types.ProcessMirrorParams calldata processMirrorParams)
78 | external
79 | view
80 | override
81 | returns (bytes memory)
82 | {
83 | FollowValidationLib.validateIsFollowing({
84 | hub: HUB,
85 | followerProfileId: processMirrorParams.profileId,
86 | followedProfileId: processMirrorParams.pointedProfileId
87 | });
88 | return '';
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/contracts/namespaces/constants/Errors.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | library RegistryErrors {
6 | error NotHandleOwner();
7 | error NotTokenOwner();
8 | error NotHandleNorTokenOwner();
9 | error OnlyLensHub();
10 | error NotLinked();
11 | error DoesNotExist();
12 | }
13 |
14 | library HandlesErrors {
15 | error HandleLengthInvalid();
16 | error HandleContainsInvalidCharacters();
17 | error HandleFirstCharInvalid();
18 | error NotOwnerNorWhitelisted();
19 | error NotOwner();
20 | error NotHub();
21 | error DoesNotExist();
22 | error NotEOA();
23 | error DisablingAlreadyTriggered();
24 | error GuardianEnabled();
25 | error AlreadyEnabled();
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/namespaces/constants/Events.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | import {RegistryTypes} from 'contracts/namespaces/constants/Types.sol';
6 |
7 | library HandlesEvents {
8 | event HandleMinted(string handle, string namespace, uint256 handleId, address to, uint256 timestamp);
9 |
10 | /**
11 | * @dev Emitted when an address' Token Guardian state change is triggered.
12 | *
13 | * @param wallet The address whose Token Guardian state change is being triggered.
14 | * @param enabled True if the Token Guardian is being enabled, false if it is being disabled.
15 | * @param tokenGuardianDisablingTimestamp The UNIX timestamp when disabling the Token Guardian will take effect,
16 | * if disabling it. Zero if the protection is being enabled.
17 | * @param timestamp The UNIX timestamp of the change being triggered.
18 | */
19 | event TokenGuardianStateChanged(
20 | address indexed wallet,
21 | bool indexed enabled,
22 | uint256 tokenGuardianDisablingTimestamp,
23 | uint256 timestamp
24 | );
25 | }
26 |
27 | library RegistryEvents {
28 | event HandleLinked(RegistryTypes.Handle handle, RegistryTypes.Token token, uint256 timestamp);
29 |
30 | /**
31 | * WARNING: If a linked handle or token is burnt, this event will not be emitted.
32 | * Indexers should also take into account token burns through ERC-721 Transfer events to track all unlink actions.
33 | * The `resolveHandle` and `resolveToken` functions will properly reflect the unlink in any case.
34 | */
35 | event HandleUnlinked(RegistryTypes.Handle handle, RegistryTypes.Token token, uint256 timestamp);
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/namespaces/constants/Types.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | /**
6 | * @title Namespaces Types
7 | * @author Lens Protocol
8 | */
9 | library RegistryTypes {
10 | struct Token {
11 | uint256 id; // SLOT 0
12 | address collection; // SLOT 1 - end
13 | // uint96 _gap; // SLOT 1 - start
14 | }
15 |
16 | struct Handle {
17 | uint256 id; // SLOT 0
18 | address collection; // SLOT 1 - end
19 | // uint96 _gap; // SLOT 1 - start
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/diagrams/migration-scheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/diagrams/migration-scheme.png
--------------------------------------------------------------------------------
/diagrams/pub-layer-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/diagrams/pub-layer-architecture.png
--------------------------------------------------------------------------------
/diagrams/referral-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/diagrams/referral-system.png
--------------------------------------------------------------------------------
/diagrams/social-layer-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/diagrams/social-layer-architecture.png
--------------------------------------------------------------------------------
/diagrams/upgrade-scheme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/diagrams/upgrade-scheme.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/07783feeadd7fbf9645b8f2cff9bbdc6-79919.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/07783feeadd7fbf9645b8f2cff9bbdc6-79919.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/08a051c6ef73c89fd5967637cbbb4acd-9A9A6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/08a051c6ef73c89fd5967637cbbb4acd-9A9A6.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1-B2132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1-B2132.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1110202897947426866-A5105.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1110202897947426866-A5105.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/188be8c99e89942dad132d5f7b774545-F428F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/188be8c99e89942dad132d5f7b774545-F428F.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f349-79F56.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f374-505CF.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f389-5C738.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f3b9-C10EF.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f3c1-445DC.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f3c6-621A1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f43a-EB486.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f440-6C64D.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f44b-8A059.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f44d-1f3fb-ED2AA.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f44d-27259.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f44f-3D381.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f4aa-2FD27.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f4af-4CFF5.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f4c4-413F0.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f4c6-44E30.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f4dc-AC641.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f50d-195C0.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f525-8FE4F.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f602-168C5.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f604-BF863.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f605-42B43.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f642-83E8A.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f64c-7C820.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f64f-22B8D.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f680-A35CE.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f6a8-A8AB3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f911-F346C.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f916-AD810.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f91d-5A0F2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f970-85DC7.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f972-F415D.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f973-88B39.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f979-BE2CD.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1f9d1-5BC80.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/1fae1-B19DE.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2-ADBB4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2-ADBB4.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2696-15F4A.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2705-0589F.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2764-A3D25.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2a9faff195fe333526cfe6ae6fce1420-49B98.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2a9faff195fe333526cfe6ae6fce1420-49B98.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2bc9256840cf583765cbbee210b7c33a-A77F9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2bc9256840cf583765cbbee210b7c33a-A77F9.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2c1e18acbcb9cdaeabfd7fc6f5090edf-CA387.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/2c1e18acbcb9cdaeabfd7fc6f5090edf-CA387.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/3270d963d3ddad601eced253ef8486c0-41F1F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/3270d963d3ddad601eced253ef8486c0-41F1F.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/3e66ba0e363a1e54abc42a137953597a-23D5F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/3e66ba0e363a1e54abc42a137953597a-23D5F.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/4-4551A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/4-4551A.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/458772ba1638eeef45aa2b27fdd74c50-322DF.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/458772ba1638eeef45aa2b27fdd74c50-322DF.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/4933-8E3F2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/4933-8E3F2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/5-E9BDB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/5-E9BDB.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/61a267bef614e2ed06bd3dc8008556c6-48B7F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/61a267bef614e2ed06bd3dc8008556c6-48B7F.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/67594ee4b4d1fc03bca468327a0d145b-BD76A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/67594ee4b4d1fc03bca468327a0d145b-BD76A.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/6ad4ef2f519afddce2d78021aa7dd099-B55F0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/6ad4ef2f519afddce2d78021aa7dd099-B55F0.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/6b084fd00f226a77abcf31000a85623e-5C817.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/6b084fd00f226a77abcf31000a85623e-5C817.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/7c6243b3f5117a8160472efef723ba9f-F0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/7c6243b3f5117a8160472efef723ba9f-F0002.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/851893827089727568-5FD38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/851893827089727568-5FD38.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/8beb125bbaa4085dbee1d02a58895784-398A3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/8beb125bbaa4085dbee1d02a58895784-398A3.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/9010cff7dc5115048ddc82561051356b-12341.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/9010cff7dc5115048ddc82561051356b-12341.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/92723d55220e7121bd5dc9694136a3d2-3B2C3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/92723d55220e7121bd5dc9694136a3d2-3B2C3.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/9542a3c6a8ce011c16ce81d61dc908f9-F344E.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/9542a3c6a8ce011c16ce81d61dc908f9-F344E.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/977133670429261884-CA8EA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/977133670429261884-CA8EA.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/a4c5f4ed4ac3c1bfaae3fc3aa73bcdc5-9A986.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/a4c5f4ed4ac3c1bfaae3fc3aa73bcdc5-9A986.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ac4bbcb3c0b85e2a4e614559bf696e56-6C05D.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ac4bbcb3c0b85e2a4e614559bf696e56-6C05D.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ad1bdf970e39199a645d59618f8426cc-F4141.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ad1bdf970e39199a645d59618f8426cc-F4141.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/b4fead77f737aa7840a25e1cd39eb062-CEDEC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/b4fead77f737aa7840a25e1cd39eb062-CEDEC.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/b9d093be224ecf13dd2aa3004eefdaef-1107F.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/b9d093be224ecf13dd2aa3004eefdaef-1107F.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c0d82faff46e636c3e88f1d883784d5f-86322.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c0d82faff46e636c3e88f1d883784d5f-86322.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c4-og-banner-AF181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c4-og-banner-AF181.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c6b85a4a6ca07ab15a30a24f570be5b8-3D1A6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c6b85a4a6ca07ab15a30a24f570be5b8-3D1A6.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c6c31419353d7af9cac1bec0835a1f21-F268C.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/c6c31419353d7af9cac1bec0835a1f21-F268C.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/d352b5d64239648acbeb7b6f310df0b8-E875A.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/d352b5d64239648acbeb7b6f310df0b8-E875A.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/doctor-sung-sung-B522E.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/doctor-sung-sung-B522E.mp4
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/e780672df5ac1f1a85dc2e2ef26354f3-7EF05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/e780672df5ac1f1a85dc2e2ef26354f3-7EF05.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/e9472a11f41d425badda92e53fc91864-F560D.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/e9472a11f41d425badda92e53fc91864-F560D.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ec283548018391e9a25a61215bd46007-2D6CA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ec283548018391e9a25a61215bd46007-2D6CA.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/f4422c70568695907d5aa7f8632b4d51-4FBE2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/f4422c70568695907d5aa7f8632b4d51-4FBE2.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-400-E988B.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-400-E988B.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-500-0777F.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-500-0777F.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-600-CB411.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-600-CB411.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-700-891AC.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-700-891AC.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-800-D36B0.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-italic-800-D36B0.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-400-1456D.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-400-1456D.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-500-89CE5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-500-89CE5.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-600-C1EA8.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-600-C1EA8.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-700-1949A.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-700-1949A.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-800-58487.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/ggsans-normal-800-58487.woff2
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/solarized-dark.min-BA98F.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/thinking-cat-6ABDD.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].html_Files/thinking-cat-6ABDD.mp4
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/4933-8E3F2:
--------------------------------------------------------------------------------
1 | error code: 502
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/c4-og-banner-AF181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/c4-og-banner-AF181.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/doctor-sung-sung-341B0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/doctor-sung-sung-341B0.png
--------------------------------------------------------------------------------
/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/thinking-cat-DFA30.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/code-423n4/2023-07-lens/ca1c693f56cf6ac1f67bbccd524b80f3341835af/discord-export/Code4rena - ARCHIVE-Q3-2023 - lens-jul17 [1129378367813189652].txt_Files/thinking-cat-DFA30.png
--------------------------------------------------------------------------------
/foundry-scripts/DeployUpgrade.s.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.8.13;
2 |
3 | import 'forge-std/Script.sol';
4 | import 'forge-std/console2.sol';
5 |
6 | import 'contracts/LensHub.sol';
7 | import 'contracts/FollowNFT.sol';
8 | import 'contracts/modules/act/collect/CollectNFT.sol';
9 |
10 | import 'contracts/misc/migrations/ProfileMigration.sol';
11 | import {LensHandles} from 'contracts/misc/namespaces/LensHandles.sol';
12 | import {TokenHandleRegistry} from 'contracts/misc/namespaces/TokenHandleRegistry.sol';
13 |
14 | /**
15 | * This script will deploy the current repository implementations, using the given environment
16 | * hub proxy address.
17 | */
18 | contract DeployUpgradeScript is Script {
19 | function run() public {
20 | string memory deployerMnemonic = vm.envString('MNEMONIC');
21 | uint256 deployerKey = vm.deriveKey(deployerMnemonic, 0);
22 | address deployer = vm.addr(deployerKey);
23 | address hubProxyAddr = vm.envAddress('HUB_PROXY_ADDRESS');
24 |
25 | address owner = deployer;
26 |
27 | LensHub hub = LensHub(hubProxyAddr);
28 | address followNFTAddress = hub.getFollowNFTImpl();
29 | address collectNFTAddress = hub.getCollectNFTImpl();
30 |
31 | uint256 deployerNonce = vm.getNonce(deployer);
32 |
33 | // Precompute needed addresss.
34 | address lensHandlesAddress = computeCreateAddress(deployer, deployerNonce);
35 | address migratorAddress = computeCreateAddress(deployer, deployerNonce + 1);
36 | address tokenHandleRegistryAddress = computeCreateAddress(deployer, deployerNonce + 2);
37 |
38 | // Start deployments.
39 | vm.startBroadcast(deployerKey);
40 |
41 | LensHandles lensHandles = new LensHandles(owner, address(hub), migratorAddress);
42 | console.log(address(lensHandles), lensHandlesAddress);
43 |
44 | ProfileMigration migrator = new ProfileMigration(
45 | owner,
46 | address(hub),
47 | lensHandlesAddress,
48 | tokenHandleRegistryAddress
49 | );
50 | console.log(address(migrator), migratorAddress);
51 |
52 | TokenHandleRegistry tokenHandleRegistry = new TokenHandleRegistry(
53 | address(hub),
54 | lensHandlesAddress,
55 | migratorAddress
56 | );
57 | console.log(address(tokenHandleRegistry), tokenHandleRegistryAddress);
58 |
59 | address hubImpl = address(
60 | new LensHub(
61 | followNFTAddress,
62 | collectNFTAddress,
63 | migratorAddress,
64 | lensHandlesAddress,
65 | tokenHandleRegistryAddress
66 | )
67 | );
68 | console.log('New hub impl:', hubImpl);
69 | vm.stopBroadcast();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = 'contracts'
3 | out = 'out'
4 | libs = ['node_modules', 'lib']
5 | test = 'test'
6 | cache_path = 'forge-cache'
7 | fs_permissions = [{ access = "read-write", path = "./"}]
8 | optimizer = true
9 | optimizer_runs = 10
10 | ignored_error_codes = []
11 |
12 | [rpc_endpoints]
13 | polygon = "${POLYGON_RPC_URL}"
14 |
15 | [fuzz]
16 | runs = 50
17 | max_test_rejects = 200000
18 |
19 | [profile.cibuild]
20 | via_ir = true
21 |
22 | [profile.citest]
23 | via_ir = false
24 | ignored_error_codes = ["license", "code-size"]
25 | [profile.citest.fuzz]
26 | runs = 100
27 |
--------------------------------------------------------------------------------
/helper-hardhat-config.ts:
--------------------------------------------------------------------------------
1 | import {
2 | eEthereumNetwork,
3 | ePolygonNetwork,
4 | eXDaiNetwork,
5 | iParamsPerNetwork,
6 | } from './helpers/types';
7 |
8 | import dotenv from 'dotenv';
9 | dotenv.config({});
10 |
11 | const TENDERLY_FORK_ID = process.env.TENDERLY_FORK_ID || '';
12 |
13 | const GWEI = 1000 * 1000 * 1000;
14 |
15 | export const NETWORKS_RPC_URL: iParamsPerNetwork = {
16 | [eEthereumNetwork.kovan]: process.env.KOVAN_RPC_URL,
17 | [eEthereumNetwork.ropsten]: process.env.ROPSTEN_RPC_URL,
18 | [eEthereumNetwork.main]: process.env.MAINNET_RPC_URL,
19 | [eEthereumNetwork.hardhat]: 'http://localhost:8545',
20 | [eEthereumNetwork.harhatevm]: 'http://localhost:8545',
21 | [eEthereumNetwork.tenderlyMain]: `https://rpc.tenderly.co/fork/${TENDERLY_FORK_ID}`,
22 | [ePolygonNetwork.mumbai]: process.env.MUMBAI_RPC_URL,
23 | [ePolygonNetwork.matic]: process.env.POLYGON_RPC_URL,
24 | [eXDaiNetwork.xdai]: 'https://rpc.xdaichain.com/',
25 | };
26 |
--------------------------------------------------------------------------------
/helpers/hardhat-constants.ts:
--------------------------------------------------------------------------------
1 | export const TEST_SNAPSHOT_ID = '0x1';
2 | export const HARDHATEVM_CHAINID = 31337;
3 |
--------------------------------------------------------------------------------
/helpers/test-wallets.ts:
--------------------------------------------------------------------------------
1 | const balance = '1000000000000000000000000';
2 |
3 | export const accounts = [
4 | {
5 | secretKey: '0xc5e8f61d1ab959b397eecc0a37a6517b8e67a0e7cf1f4bce5591f3ed80199122',
6 | balance,
7 | },
8 | {
9 | secretKey: '0xd49743deccbccc5dc7baa8e69e5be03298da8688a15dd202e20f15d5e0e9a9fb',
10 | balance,
11 | },
12 | {
13 | secretKey: '0x23c601ae397441f3ef6f1075dcb0031ff17fb079837beadaf3c84d96c6f3e569',
14 | balance,
15 | },
16 | {
17 | secretKey: '0xee9d129c1997549ee09c0757af5939b2483d80ad649a0eda68e8b0357ad11131',
18 | balance,
19 | },
20 | {
21 | secretKey: '0x87630b2d1de0fbd5044eb6891b3d9d98c34c8d310c852f98550ba774480e47cc',
22 | balance,
23 | },
24 | {
25 | secretKey: '0x275cc4a2bfd4f612625204a20a2280ab53a6da2d14860c47a9f5affe58ad86d4',
26 | balance,
27 | },
28 | {
29 | secretKey: '0xaee25d55ce586148a853ca83fdfacaf7bc42d5762c6e7187e6f8e822d8e6a650',
30 | balance,
31 | },
32 | {
33 | secretKey: '0xa2e0097c961c67ec197b6865d7ecea6caffc68ebeb00e6050368c8f67fc9c588',
34 | balance,
35 | },
36 | ];
37 |
--------------------------------------------------------------------------------
/helpers/types.ts:
--------------------------------------------------------------------------------
1 | export interface SymbolMap {
2 | [symbol: string]: T;
3 | }
4 |
5 | export type eNetwork = eEthereumNetwork | ePolygonNetwork | eXDaiNetwork;
6 |
7 | export enum eEthereumNetwork {
8 | kovan = 'kovan',
9 | ropsten = 'ropsten',
10 | main = 'main',
11 | hardhat = 'hardhat',
12 | tenderlyMain = 'tenderlyMain',
13 | harhatevm = 'harhatevm',
14 | }
15 |
16 | export enum ePolygonNetwork {
17 | matic = 'matic',
18 | mumbai = 'mumbai',
19 | }
20 |
21 | export enum eXDaiNetwork {
22 | xdai = 'xdai',
23 | }
24 |
25 | export enum EthereumNetworkNames {
26 | kovan = 'kovan',
27 | ropsten = 'ropsten',
28 | main = 'main',
29 | matic = 'matic',
30 | mumbai = 'mumbai',
31 | xdai = 'xdai',
32 | }
33 |
34 | export type tEthereumAddress = string;
35 | export type tStringTokenBigUnits = string; // 1 ETH, or 10e6 USDC or 10e18 DAI
36 | export type tStringTokenSmallUnits = string; // 1 wei, or 1 basic unit of USDC, or 1 basic unit of DAI
37 |
38 | export type iParamsPerNetwork =
39 | | iEthereumParamsPerNetwork
40 | | iPolygonParamsPerNetwork
41 | | iXDaiParamsPerNetwork;
42 |
43 | export interface iParamsPerNetworkAll
44 | extends iEthereumParamsPerNetwork,
45 | iPolygonParamsPerNetwork,
46 | iXDaiParamsPerNetwork {}
47 |
48 | export interface iEthereumParamsPerNetwork {
49 | [eEthereumNetwork.harhatevm]: eNetwork;
50 | [eEthereumNetwork.kovan]: eNetwork;
51 | [eEthereumNetwork.ropsten]: eNetwork;
52 | [eEthereumNetwork.main]: eNetwork;
53 | [eEthereumNetwork.hardhat]: eNetwork;
54 | [eEthereumNetwork.tenderlyMain]: eNetwork;
55 | }
56 |
57 | export interface iPolygonParamsPerNetwork {
58 | [ePolygonNetwork.matic]: T;
59 | [ePolygonNetwork.mumbai]: T;
60 | }
61 |
62 | export interface iXDaiParamsPerNetwork {
63 | [eXDaiNetwork.xdai]: T;
64 | }
65 |
66 | export interface ObjectString {
67 | [key: string]: string;
68 | }
69 |
--------------------------------------------------------------------------------
/helpers/wallet-helpers.ts:
--------------------------------------------------------------------------------
1 | import { Wallet, Signer } from 'ethers';
2 | import { DefenderRelaySigner, DefenderRelayProvider } from 'defender-relay-client/lib/ethers';
3 | import dotenv from 'dotenv';
4 |
5 | dotenv.config({ path: '../.env' });
6 |
7 | const PRIVATE_KEY = process.env.PRIVATE_KEY || '';
8 | const MNEMONIC = process.env.MNEMONIC || '';
9 | const DEFENDER_API_KEY = process.env.DEFENDER_API_KEY || '';
10 | const DEFENDER_SECRET_KEY = process.env.DEFENDER_SECRET_KEY || '';
11 |
12 | export const getPrivateKeyWallet = (): Signer => new Wallet(PRIVATE_KEY);
13 |
14 | export const getMnemonicWallet = (): Signer => Wallet.fromMnemonic(MNEMONIC);
15 |
16 | export const getDefenderSigner = (): Signer => {
17 | const credentials = { apiKey: DEFENDER_API_KEY, apiSecret: DEFENDER_SECRET_KEY };
18 | const provider = new DefenderRelayProvider(credentials);
19 | return new DefenderRelaySigner(credentials, provider, { speed: 'fast' });
20 | };
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@aave/lens-protocol",
3 | "version": "2.0.0",
4 | "description": "Composable and decentralized social graph",
5 | "scripts": {
6 | "build": "forge build --sizes",
7 | "coverage": "forge coverage --report lcov && genhtml lcov.info -o report --branch-coverage",
8 | "test": "forge test",
9 | "test:gas": "forge test -vvv --gas-report",
10 | "deploy:local": "hardhat full-deploy --network localhost",
11 | "deploy:mumbai": "hardhat full-deploy --network mumbai"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/aave/lens-protocol"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/aave/lens-protocol/issues"
19 | },
20 | "homepage": "https://github.com/aave/lens-protocol#readme",
21 | "devDependencies": {
22 | "@nomiclabs/hardhat-ethers": "2.0.2",
23 | "@nomiclabs/hardhat-etherscan": "2.1.8",
24 | "@typechain/ethers-v5": "8.0.5",
25 | "@typechain/hardhat": "3.0.0",
26 | "@types/chai": "4.2.22",
27 | "@types/mocha": "9.0.0",
28 | "@types/node": "16.11.11",
29 | "@typescript-eslint/eslint-plugin": "5.5.0",
30 | "@typescript-eslint/parser": "5.5.0",
31 | "chai": "4.3.4",
32 | "defender-relay-client": "1.12.1",
33 | "dotenv": "10.0.0",
34 | "eslint": "8.3.0",
35 | "eslint-config-prettier": "8.3.0",
36 | "eslint-plugin-prettier": "4.0.0",
37 | "ethereum-waffle": "3.4.0",
38 | "ethers": "^5.7.1",
39 | "hardhat": "^2.12.0",
40 | "hardhat-contract-sizer": "2.1.1",
41 | "hardhat-gas-reporter": "1.0.6",
42 | "hardhat-log-remover": "2.0.2",
43 | "hardhat-spdx-license-identifier": "2.0.3",
44 | "hardhat-tracer": "^1.1.0-rc.6",
45 | "husky": "7.0.4",
46 | "prettier": "^2.5.0",
47 | "prettier-plugin-solidity": "^1.1.2",
48 | "solidity-coverage": "^0.8.2",
49 | "ts-generator": "0.1.1",
50 | "ts-node": "10.4.0",
51 | "typechain": "6.0.5",
52 | "typescript": "4.5.2"
53 | },
54 | "husky": {
55 | "hooks": {
56 | "pre-commit": "pretty-quick --staged --pattern 'contracts/**/*.sol' --pattern 'helpers/**/*.ts' --pattern 'test/**/*.ts' --pattern 'tasks/**/*.ts'"
57 | }
58 | },
59 | "dependencies": {
60 | "@openzeppelin/contracts": "4.8.0"
61 | },
62 | "author": "Lens",
63 | "contributors": [
64 | "Alan Donoso Naumczuk",
65 | "Victor Naumik",
66 | "Josh Stevens",
67 | "Peter Michael (Zer0dot)",
68 | "David Racero",
69 | "Emilio Frangella",
70 | "Lasse Herskind",
71 | "Miguel Martinez",
72 | "Steven Valeri"
73 | ],
74 | "license": "MIT",
75 | "keywords": [
76 | "lens",
77 | "protocol",
78 | "aave",
79 | "social",
80 | "graph",
81 | "solidity",
82 | "evm",
83 | "contracts",
84 | "core"
85 | ]
86 | }
87 |
--------------------------------------------------------------------------------
/remappings.txt:
--------------------------------------------------------------------------------
1 | ds-test/=lib/forge-std/lib/ds-test/src/
2 | forge-std/=lib/forge-std/src/
3 | @seadrop/=lib/seadrop/src/
4 | @openzeppelin/=lib/openzeppelin-contracts/
5 |
6 | ERC721A/=lib/seadrop/lib/ERC721A/contracts/
7 | ERC721A-Upgradeable/=lib/seadrop/lib/ERC721A-Upgradeable/contracts/
8 | murky/=lib/seadrop/lib/murky/src/
9 | openzeppelin-contracts/=lib/seadrop/lib/openzeppelin-contracts/contracts/
10 | openzeppelin-contracts-upgradeable/=lib/seadrop/lib/openzeppelin-contracts-upgradeable/contracts/
11 | operator-filter-registry/=lib/seadrop/lib/operator-filter-registry/src/
12 | solmate/=lib/seadrop/lib/solmate/src/
13 | utility-contracts/=lib/seadrop/lib/utility-contracts/src/
14 | create2-scripts/=lib/seadrop/lib/create2-helpers/script/
15 |
16 | solady/=lib/solady/src/
17 |
--------------------------------------------------------------------------------
/scripts/getProxyAdmin.sh:
--------------------------------------------------------------------------------
1 | source .env
2 |
3 | if [[ $1 == "" ]]
4 | then
5 | echo "Usage:"
6 | echo " bash getProxyAdmin.sh [address]"
7 | echo " Where [address] is the TransparentUpgradeableProxy address"
8 | exit 1
9 | fi
10 |
11 | # TransparentUpgradeableProxy implementation slot
12 | adminSlot="0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103"
13 |
14 | rawOldImplAddress=$(cast storage $1 $adminSlot --rpc-url $POLYGON_RPC_URL)
15 |
16 | echo "Admin of $1 TransparentUpgradeableProxy is:"
17 | echo "0x${rawOldImplAddress:26}"
18 |
--------------------------------------------------------------------------------
/scripts/mergeCoverage.sh:
--------------------------------------------------------------------------------
1 | lcov -a lcov.info -a coverage/lcov.info -o lcov_merged.info
2 | lcov -r lcov_merged.info 'test/*' '*contracts/core/modules/collect/*' '*contracts/core/modules/deprecated/*' '*contracts/core/modules/follow/*' '*contracts/core/modules/reference/*' '*contracts/core/modules/*Base.sol' '*contracts/mocks/*' -o lcov_merged_clean.info
3 | genhtml lcov_merged_clean.info --rc lcov_branch_coverage=1 -o coverage_merged -s
4 |
--------------------------------------------------------------------------------
/tasks/helpers/utils.ts:
--------------------------------------------------------------------------------
1 | import '@nomiclabs/hardhat-ethers';
2 | import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
3 | import { Contract, ContractTransaction } from 'ethers';
4 | import fs from 'fs';
5 | import { HardhatRuntimeEnvironment } from 'hardhat/types';
6 |
7 | export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
8 |
9 | export enum ProtocolState {
10 | Unpaused,
11 | PublishingPaused,
12 | Paused,
13 | }
14 |
15 | export function getAddrs(): any {
16 | const json = fs.readFileSync('addresses.json', 'utf8');
17 | const addrs = JSON.parse(json);
18 | return addrs;
19 | }
20 |
21 | export async function waitForTx(tx: Promise) {
22 | await (await tx).wait();
23 | }
24 |
25 | export async function deployContract(tx: any): Promise {
26 | const result = await tx;
27 | await result.deployTransaction.wait();
28 | return result;
29 | }
30 |
31 | export async function deployWithVerify(
32 | tx: any,
33 | args: any,
34 | contractPath: string
35 | ): Promise {
36 | const deployedContract = await deployContract(tx);
37 | let count = 0;
38 | let maxTries = 8;
39 | const runtimeHRE = require('hardhat');
40 | while (true) {
41 | await delay(10000);
42 | try {
43 | console.log('Verifying contract at', deployedContract.address);
44 | await runtimeHRE.run('verify:verify', {
45 | address: deployedContract.address,
46 | constructorArguments: args,
47 | contract: contractPath,
48 | });
49 | break;
50 | } catch (error) {
51 | if (String(error).includes('Already Verified')) {
52 | console.log(
53 | `Already verified contract at ${contractPath} at address ${deployedContract.address}`
54 | );
55 | break;
56 | }
57 | if (++count == maxTries) {
58 | console.log(
59 | `Failed to verify contract at ${contractPath} at address ${deployedContract.address}, error: ${error}`
60 | );
61 | break;
62 | }
63 | console.log(`Retrying... Retry #${count}, last error: ${error}`);
64 | }
65 | }
66 |
67 | return deployedContract;
68 | }
69 |
70 | export async function initEnv(hre: HardhatRuntimeEnvironment): Promise {
71 | const ethers = hre.ethers; // This allows us to access the hre (Hardhat runtime environment)'s injected ethers instance easily
72 |
73 | const accounts = await ethers.getSigners(); // This returns an array of the default signers connected to the hre's ethers instance
74 | const governance = accounts[1];
75 | const treasury = accounts[2];
76 | const user = accounts[3];
77 |
78 | return [governance, treasury, user];
79 | }
80 |
81 | async function delay(ms: number) {
82 | return new Promise((resolve) => setTimeout(resolve, ms));
83 | }
84 |
--------------------------------------------------------------------------------
/tasks/list-storage.ts:
--------------------------------------------------------------------------------
1 | import '@nomiclabs/hardhat-ethers';
2 | import { task } from 'hardhat/config';
3 |
4 | task('list-storage', '').setAction(async ({}, hre) => {
5 | const ethers = hre.ethers;
6 | const addr = '0xDb46d1Dc155634FbC732f92E853b10B288AD5a1d';
7 | for (let i = 0; i < 100; ++i) {
8 | const storageSlot = await ethers.provider.getStorageAt(addr, i);
9 | console.log(`Hub proxy storage at slot ${i}: ${storageSlot}`);
10 | }
11 | });
12 |
--------------------------------------------------------------------------------
/tasks/whitelist-currency.ts:
--------------------------------------------------------------------------------
1 | import '@nomiclabs/hardhat-ethers';
2 | import { task } from 'hardhat/config';
3 | import { ModuleGlobals__factory } from '../typechain-types';
4 | import { waitForTx } from './helpers/utils';
5 |
6 | task('whitelist-currency', 'whitelists a currency in the module globals')
7 | .addParam('gov')
8 | .addParam('globals')
9 | .addParam('currency')
10 | .addParam('whitelist')
11 | .setAction(async ({ gov, globals, currency, whitelist }, hre) => {
12 | const ethers = hre.ethers;
13 | const governance = await ethers.getSigner(gov);
14 |
15 | const moduleGlobals = ModuleGlobals__factory.connect(globals, governance);
16 |
17 | await waitForTx(moduleGlobals.connect(governance).whitelistCurrency(currency, whitelist));
18 | });
19 |
--------------------------------------------------------------------------------
/test/Constants.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.13;
3 |
4 | uint256 constant ISSECP256K1_CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337;
5 | bytes32 constant ADMIN_SLOT = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1);
6 | bytes32 constant PROXY_IMPLEMENTATION_STORAGE_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
7 |
8 | uint256 constant PROFILE_GUARDIAN_COOLDOWN = 7 days;
9 | uint256 constant HANDLE_GUARDIAN_COOLDOWN = 7 days;
10 |
--------------------------------------------------------------------------------
/test/ERC721.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.15;
3 |
4 | import 'test/base/BaseTest.t.sol';
5 |
6 | abstract contract ERC721Test is BaseTest {
7 | function _getERC721TokenAddress() internal view virtual returns (address);
8 |
9 | function _mintERC721(address to) internal virtual returns (uint256);
10 | }
11 |
--------------------------------------------------------------------------------
/test/helpers/ArrayHelpers.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.13;
3 |
4 | import {Types} from 'contracts/libraries/constants/Types.sol';
5 |
6 | contract ArrayHelpers {
7 | function testArrayHelpers() public {
8 | // Prevents being counted in Foundry Coverage
9 | }
10 |
11 | function _emptyUint256Array() internal pure returns (uint256[] memory) {
12 | uint256[] memory ret = new uint256[](0);
13 | return ret;
14 | }
15 |
16 | function _emptyPubTypesArray() internal pure returns (Types.PublicationType[] memory) {
17 | Types.PublicationType[] memory ret = new Types.PublicationType[](0);
18 | return ret;
19 | }
20 |
21 | function _toUint256Array(uint256 n) internal pure returns (uint256[] memory) {
22 | uint256[] memory ret = new uint256[](1);
23 | ret[0] = n;
24 | return ret;
25 | }
26 |
27 | function _toUint256Array(uint256 n0, uint256 n1) internal pure returns (uint256[] memory) {
28 | uint256[] memory ret = new uint256[](2);
29 | ret[0] = n0;
30 | ret[1] = n1;
31 | return ret;
32 | }
33 |
34 | function _emptyBytesArray() internal pure returns (bytes[] memory) {
35 | bytes[] memory ret = new bytes[](0);
36 | return ret;
37 | }
38 |
39 | function _toBytesArray(bytes memory b) internal pure returns (bytes[] memory) {
40 | bytes[] memory ret = new bytes[](1);
41 | ret[0] = b;
42 | return ret;
43 | }
44 |
45 | function _toBytesArray(bytes memory b0, bytes memory b1) internal pure returns (bytes[] memory) {
46 | bytes[] memory ret = new bytes[](2);
47 | ret[0] = b0;
48 | ret[1] = b1;
49 | return ret;
50 | }
51 |
52 | function _toBoolArray(bool b) internal pure returns (bool[] memory) {
53 | bool[] memory ret = new bool[](1);
54 | ret[0] = b;
55 | return ret;
56 | }
57 |
58 | function _toBoolArray(bool b0, bool b1) internal pure returns (bool[] memory) {
59 | bool[] memory ret = new bool[](2);
60 | ret[0] = b0;
61 | ret[1] = b1;
62 | return ret;
63 | }
64 |
65 | function _emptyAddressArray() internal pure returns (address[] memory) {
66 | address[] memory ret = new address[](0);
67 | return ret;
68 | }
69 |
70 | function _toAddressArray(address a) internal pure returns (address[] memory) {
71 | address[] memory ret = new address[](1);
72 | ret[0] = a;
73 | return ret;
74 | }
75 |
76 | function _toAddressArray(address a0, address a1) internal pure returns (address[] memory) {
77 | address[] memory ret = new address[](2);
78 | ret[0] = a0;
79 | ret[1] = a1;
80 | return ret;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/test/helpers/Events.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity >=0.6.0;
4 |
5 | library TestEvents {
6 | // Non-Lens Events
7 | event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
8 | event Upgraded(address indexed implementation);
9 | event AdminChanged(address previousAdmin, address newAdmin);
10 | }
11 |
--------------------------------------------------------------------------------
/test/helpers/ForkManagement.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.13;
3 |
4 | import 'forge-std/Script.sol';
5 |
6 | contract ForkManagement is Script {
7 | using stdJson for string;
8 |
9 | function testForkManagement() public {
10 | // Prevents being counted in Foundry Coverage
11 | }
12 |
13 | string forkEnv;
14 | bool fork;
15 | string network;
16 | string json;
17 | uint256 forkBlockNumber;
18 |
19 | modifier onlyFork() {
20 | if (bytes(forkEnv).length == 0) return;
21 | _;
22 | }
23 |
24 | // TODO: Move somewhere else
25 | function isEnvSet(string memory key) internal view returns (bool) {
26 | try vm.envString(key) {
27 | return true;
28 | } catch {
29 | return false;
30 | }
31 | }
32 |
33 | // TODO: Move somewhere else
34 | // TODO: Replace with forge-std/StdJson.sol::keyExists(...) when/if this PR is approved:
35 | // https://github.com/foundry-rs/forge-std/pull/226
36 | function keyExists(string memory key) internal view returns (bool) {
37 | return json.parseRaw(key).length > 0;
38 | }
39 |
40 | constructor() {
41 | // TODO: Replace with envOr when it's released
42 | forkEnv = isEnvSet('TESTING_FORK') ? vm.envString('TESTING_FORK') : '';
43 |
44 | if (bytes(forkEnv).length > 0) {
45 | fork = true;
46 | console.log('\n\n Testing using %s fork', forkEnv);
47 | loadJson();
48 |
49 | network = getNetwork();
50 |
51 | if (isEnvSet('FORK_BLOCK')) {
52 | forkBlockNumber = vm.envUint('FORK_BLOCK');
53 | vm.createSelectFork(network, forkBlockNumber);
54 | console.log('Fork Block number (FIXED BLOCK):', forkBlockNumber);
55 | } else {
56 | vm.createSelectFork(network);
57 | forkBlockNumber = block.number;
58 | console.log('Fork Block number:', forkBlockNumber);
59 | }
60 |
61 | checkNetworkParams();
62 | }
63 | }
64 |
65 | function loadJson() internal {
66 | string memory root = vm.projectRoot();
67 | string memory path = string.concat(root, '/addresses.json');
68 | json = vm.readFile(path);
69 | }
70 |
71 | function checkNetworkParams() internal returns (uint256 chainId) {
72 | network = json.readString(string.concat('.', forkEnv, '.network'));
73 | chainId = json.readUint(string.concat('.', forkEnv, '.chainId'));
74 |
75 | console.log('\nTarget environment:', forkEnv);
76 | console.log('Network:', network);
77 | if (block.chainid != chainId) revert('Wrong chainId');
78 | console.log('ChainId:', chainId);
79 | }
80 |
81 | function getNetwork() internal returns (string memory) {
82 | return json.readString(string.concat('.', forkEnv, '.network'));
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/test/mocks/MockActionModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {IPublicationActionModule} from 'contracts/interfaces/IPublicationActionModule.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {MockModule} from 'test/mocks/MockModule.sol';
8 |
9 | /**
10 | * @dev This is a simple mock Action module to be used for testing revert cases on processAction.
11 | */
12 | contract MockActionModule is MockModule, IPublicationActionModule {
13 | error MockActionModuleReverted();
14 |
15 | function testMockActionModule() public {
16 | // Prevents being counted in Foundry Coverage
17 | }
18 |
19 | // Reverts if `data` does not decode as `true`.
20 | function initializePublicationAction(
21 | uint256 /** profileId*/,
22 | uint256 /** pubId*/,
23 | address /** transactionExecutor*/,
24 | bytes calldata data
25 | ) external pure override returns (bytes memory) {
26 | return _decodeFlagAndRevertIfFalse(data);
27 | }
28 |
29 | // Reverts if `processActionParams.actionModuleData` does not decode as `true`.
30 | function processPublicationAction(
31 | Types.ProcessActionParams calldata processActionParams
32 | ) external pure override returns (bytes memory) {
33 | return _decodeFlagAndRevertIfFalse(processActionParams.actionModuleData);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/test/mocks/MockCollectModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ICollectModule} from 'contracts/interfaces/ICollectModule.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {MockModule} from 'test/mocks/MockModule.sol';
8 |
9 | /**
10 | * @title FreeCollectModule
11 | * @author Lens Protocol
12 | *
13 | * @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface.
14 | *
15 | * This module works by allowing all collects.
16 | */
17 | contract MockCollectModule is MockModule, ICollectModule {
18 | function testMockCollectModule() public {
19 | // Prevents being counted in Foundry Coverage
20 | }
21 |
22 | /**
23 | * @dev There is nothing needed at initialization.
24 | */
25 | function initializePublicationCollectModule(
26 | uint256,
27 | uint256,
28 | address,
29 | bytes calldata data
30 | ) external pure override returns (bytes memory) {
31 | return _decodeFlagAndRevertIfFalse(data);
32 | }
33 |
34 | /**
35 | * @dev Processes a collect by:
36 | * 1. Ensuring the collector is a follower, if needed
37 | */
38 | function processCollect(
39 | Types.ProcessCollectParams calldata processCollectParams
40 | ) external pure override returns (bytes memory) {
41 | return _decodeFlagAndRevertIfFalse(processCollectParams.data);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/mocks/MockCurrency.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
6 |
7 | /**
8 | * @dev A simple mock currency to be used for testing.
9 | */
10 | contract MockCurrency is ERC20('Currency', 'MCY') {
11 | function testMockCurrency() public {
12 | // Prevents being counted in Foundry Coverage
13 | }
14 |
15 | function mint(address to, uint256 amount) external {
16 | _mint(to, amount);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/mocks/MockDeprecatedCollectModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILegacyCollectModule} from 'contracts/interfaces/ILegacyCollectModule.sol';
6 | import {MockModule} from 'test/mocks/MockModule.sol';
7 |
8 | /**
9 | * @title FreeCollectModule
10 | * @author Lens Protocol
11 | *
12 | * @notice This is a simple Lens CollectModule implementation, inheriting from the ICollectModule interface.
13 | *
14 | * This module works by allowing all collects.
15 | */
16 | contract MockDeprecatedCollectModule is MockModule, ILegacyCollectModule {
17 | function testMockDeprecatedCollectModule() public {
18 | // Prevents being counted in Foundry Coverage
19 | }
20 |
21 | /**
22 | * @dev There is nothing needed at initialization.
23 | */
24 | function initializePublicationCollectModule(
25 | uint256,
26 | uint256,
27 | bytes calldata data
28 | ) external pure override returns (bytes memory) {
29 | _decodeFlagAndRevertIfFalse(data);
30 | return '';
31 | }
32 |
33 | /**
34 | * @dev Processes a collect by:
35 | * 1. Ensuring the collector is a follower, if needed
36 | */
37 | function processCollect(
38 | uint256 /* referrerProfileId */,
39 | address /* collector */,
40 | uint256 /* profileId */,
41 | uint256 /* pubId */,
42 | bytes calldata data
43 | ) external pure override {
44 | _decodeFlagAndRevertIfFalse(data);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/mocks/MockDeprecatedFollowModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILegacyFollowModule} from 'contracts/interfaces/ILegacyFollowModule.sol';
6 |
7 | /**
8 | * @dev This is a simple mock follow module to be used for testing.
9 | */
10 | contract MockDeprecatedFollowModule is ILegacyFollowModule {
11 | function testMockDeprecatedFollowModule() public {
12 | // Prevents being counted in Foundry Coverage
13 | }
14 |
15 | function initializeFollowModule(uint256, bytes calldata data) external pure override returns (bytes memory) {
16 | uint256 number = abi.decode(data, (uint256));
17 | require(number == 1, 'MockFollowModule: invalid');
18 | return new bytes(0);
19 | }
20 |
21 | function processFollow(address follower, uint256 profileId, bytes calldata data) external override {}
22 |
23 | function isFollowing(uint256, address, uint256) external pure override returns (bool) {
24 | return true;
25 | }
26 |
27 | function followModuleTransferHook(
28 | uint256 profileId,
29 | address from,
30 | address to,
31 | uint256 followNFTTokenId
32 | ) external override {}
33 | }
34 |
--------------------------------------------------------------------------------
/test/mocks/MockDeprecatedReferenceModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILegacyReferenceModule} from 'contracts/interfaces/ILegacyReferenceModule.sol';
6 |
7 | /**
8 | * @dev This is a simple mock follow module to be used for testing.
9 | */
10 | contract MockDeprecatedReferenceModule is ILegacyReferenceModule {
11 | function testMockDeprecatedReferenceModule() public {
12 | // Prevents being counted in Foundry Coverage
13 | }
14 |
15 | function initializeReferenceModule(
16 | uint256,
17 | uint256,
18 | bytes calldata data
19 | ) external pure override returns (bytes memory) {
20 | uint256 number = abi.decode(data, (uint256));
21 | require(number == 1, 'MockDeprecatedReferenceModule: invalid');
22 | return new bytes(0);
23 | }
24 |
25 | function processComment(
26 | uint256 profileId,
27 | uint256 pointedProfileId,
28 | uint256 pointedPubId,
29 | bytes calldata data
30 | ) external override {}
31 |
32 | function processMirror(
33 | uint256 profileId,
34 | uint256 pointedProfileId,
35 | uint256 pointedPubId,
36 | bytes calldata data
37 | ) external override {}
38 | }
39 |
--------------------------------------------------------------------------------
/test/mocks/MockERC721RecipientWithRevertFlag.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.15;
3 |
4 | import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
5 |
6 | contract MockERC721RecipientWithRevertFlag is IERC721Receiver {
7 | error MockERC721RecipientReverted();
8 |
9 | function testMockERC721RecipientWithRevertFlag() public {
10 | // Prevents being counted in Foundry Coverage
11 | }
12 |
13 | bool revertFlag;
14 |
15 | function revertOnNextCall() public {
16 | revertFlag = true;
17 | }
18 |
19 | function onERC721Received(
20 | address /* operator */,
21 | address /* from */,
22 | uint256 /* id */,
23 | bytes calldata data
24 | ) public virtual override returns (bytes4) {
25 | if (revertFlag || (data.length > 0 && abi.decode(data, (bool)))) {
26 | revert MockERC721RecipientReverted();
27 | }
28 | return IERC721Receiver.onERC721Received.selector;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/test/mocks/MockFollowModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
6 |
7 | /**
8 | * @dev This is a simple mock follow module to be used for testing.
9 | */
10 | contract MockFollowModule is IFollowModule {
11 | function testMockFollowModule() public {
12 | // Prevents being counted in Foundry Coverage
13 | }
14 |
15 | function initializeFollowModule(
16 | uint256 /* profileId */,
17 | address /* transactionExecutor */,
18 | bytes calldata data
19 | ) external pure override returns (bytes memory) {
20 | uint256 number = abi.decode(data, (uint256));
21 | require(number == 1, 'MockFollowModule: invalid');
22 | return new bytes(0);
23 | }
24 |
25 | function processFollow(
26 | uint256 followerProfileId,
27 | uint256 followTokenId,
28 | address transactionExecutor,
29 | uint256 profileId,
30 | bytes calldata data
31 | ) external pure override returns (bytes memory) {}
32 | }
33 |
--------------------------------------------------------------------------------
/test/mocks/MockFollowModuleWithRevertFlag.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {IFollowModule} from 'contracts/interfaces/IFollowModule.sol';
6 |
7 | /**
8 | * @dev This is a simple mock follow module to be used for testing revert cases on processFollow.
9 | */
10 | contract MockFollowModuleWithRevertFlag is IFollowModule {
11 | error MockFollowModuleReverted();
12 |
13 | function testMockFollowModuleWithRevertFlag() public {
14 | // Prevents being counted in Foundry Coverage
15 | }
16 |
17 | function initializeFollowModule(
18 | uint256 /* profileId */,
19 | address /* transactionExecutor */,
20 | bytes calldata /* data */
21 | ) external pure override returns (bytes memory) {
22 | return new bytes(0);
23 | }
24 |
25 | function processFollow(
26 | uint256 /* followerProfileId */,
27 | uint256 /* followTokenId */,
28 | address /* transactionExecutor */,
29 | uint256 /* profileId */,
30 | bytes calldata data
31 | ) external pure override returns (bytes memory) {
32 | if (abi.decode(data, (bool))) {
33 | revert MockFollowModuleReverted();
34 | }
35 | return '';
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/mocks/MockModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | abstract contract MockModule {
6 | error MockModuleReverted();
7 |
8 | function testMockModule() public {
9 | // Prevents being counted in Foundry Coverage
10 | }
11 |
12 | // Reverts if the flag decoded from the data is not `true`.
13 | function _decodeFlagAndRevertIfFalse(bytes memory data) internal pure returns (bytes memory) {
14 | bool shouldItSucceed = abi.decode(data, (bool));
15 | if (!shouldItSucceed) {
16 | revert MockModuleReverted();
17 | }
18 | return data;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/mocks/MockNFT.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.13;
4 |
5 | import {LensBaseERC721} from 'contracts/base/LensBaseERC721.sol';
6 |
7 | contract MockNFT is LensBaseERC721 {
8 | function testMockNFT() public {
9 | // Prevents being counted in Foundry Coverage
10 | }
11 |
12 | function name() public pure override returns (string memory) {
13 | return 'Mock NFT';
14 | }
15 |
16 | function symbol() public pure override returns (string memory) {
17 | return 'NFT';
18 | }
19 |
20 | function tokenURI(uint256 /* tokenId */) external pure override returns (string memory) {
21 | return 'https://ipfs.io/ipfs/QmNZiPk974vDsPmQii3YbrMKfi12KTSNM7XMiYyiea4VYZ';
22 | }
23 |
24 | function mint(address to, uint256 nftId) external {
25 | _mint(to, nftId);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/mocks/MockNonERC721Recipient.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.15;
3 |
4 | contract MockNonERC721Recipient {
5 | function testMockNonERC721Recipient() public {
6 | // Prevents being counted in Foundry Coverage
7 | }
8 |
9 | // This contract should never have an onERC721Received function
10 | }
11 |
--------------------------------------------------------------------------------
/test/mocks/MockProfileCreationProxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {ILensHub} from 'contracts/interfaces/ILensHub.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {Errors} from 'contracts/libraries/constants/Errors.sol';
8 |
9 | /**
10 | * @title MockProfileCreationProxy
11 | * @author Lens Protocol
12 | *
13 | * @notice This is a proxy contract that enforces ".test" handle suffixes and adds char validations at profile creation.
14 | */
15 | contract MockProfileCreationProxy {
16 | function testMockProfileCreationProxy() public {
17 | // Prevents being counted in Foundry Coverage
18 | }
19 |
20 | ILensHub immutable LENS_HUB;
21 |
22 | constructor(ILensHub hub) {
23 | LENS_HUB = hub;
24 | }
25 |
26 | function proxyCreateProfile(Types.CreateProfileParams memory createProfileParams) external {
27 | LENS_HUB.createProfile(createProfileParams);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/test/mocks/MockReferenceModule.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.15;
4 |
5 | import {IReferenceModule} from 'contracts/interfaces/IReferenceModule.sol';
6 | import {Types} from 'contracts/libraries/constants/Types.sol';
7 | import {MockModule} from 'test/mocks/MockModule.sol';
8 |
9 | /**
10 | * @dev This is a simple mock follow module to be used for testing.
11 | */
12 | contract MockReferenceModule is MockModule, IReferenceModule {
13 | function testMockReferenceModule() public {
14 | // Prevents being counted in Foundry Coverage
15 | }
16 |
17 | function initializeReferenceModule(
18 | uint256,
19 | uint256,
20 | address,
21 | bytes calldata data
22 | ) external pure override returns (bytes memory) {
23 | return _decodeFlagAndRevertIfFalse(data);
24 | }
25 |
26 | function processComment(
27 | Types.ProcessCommentParams calldata processCommentParams
28 | ) external pure override returns (bytes memory) {
29 | return _decodeFlagAndRevertIfFalse(processCommentParams.data);
30 | }
31 |
32 | function processQuote(
33 | Types.ProcessQuoteParams calldata processQuoteParams
34 | ) external pure override returns (bytes memory) {
35 | return _decodeFlagAndRevertIfFalse(processQuoteParams.data);
36 | }
37 |
38 | function processMirror(
39 | Types.ProcessMirrorParams calldata processMirrorParams
40 | ) external pure override returns (bytes memory) {
41 | return _decodeFlagAndRevertIfFalse(processMirrorParams.data);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/mocks/MockTokenHolderContract.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.10;
4 |
5 | import {IERC721} from '@openzeppelin/contracts/token/ERC721/IERC721.sol';
6 | import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
7 | import {ERC721Burnable} from '@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol';
8 |
9 | interface IGuardedToken is IERC721 {
10 | function DANGER__disableTokenGuardian() external;
11 |
12 | function enableTokenGuardian() external;
13 |
14 | function burn(uint256 tokenId) external;
15 | }
16 |
17 | contract MockTokenHolderContract is IERC721Receiver {
18 | address _collection;
19 | uint256 _tokenId;
20 |
21 | ////////////////////// SETTERS & DEPOSIT FUNCTIONS ////////////
22 |
23 | function onERC721Received(
24 | address, /* operator */
25 | address, /* from */
26 | uint256 tokenId,
27 | bytes calldata /* data */
28 | ) public virtual override returns (bytes4) {
29 | _collection = msg.sender;
30 | _tokenId = tokenId;
31 | return IERC721Receiver.onERC721Received.selector;
32 | }
33 |
34 | function depositNft(
35 | address collection,
36 | address from,
37 | uint256 tokenId
38 | ) external {
39 | _collection = collection;
40 | _tokenId = tokenId;
41 | IERC721(collection).transferFrom(from, address(this), tokenId);
42 | }
43 |
44 | function setCollection(address collection) external {
45 | _collection = collection;
46 | }
47 |
48 | function getTokenId() external view returns (uint256) {
49 | return _tokenId;
50 | }
51 |
52 | ////////////////////// LOCKING MECHANISM FUNCTIONS ////////////
53 |
54 | function executeDisableTokenGuardian() external {
55 | IGuardedToken(_collection).DANGER__disableTokenGuardian();
56 | }
57 |
58 | function executeEnableTokenGuardian() external {
59 | IGuardedToken(_collection).enableTokenGuardian();
60 | }
61 |
62 | ////////////////////// EIP-721 FUNCTIONS //////////////////////
63 |
64 | function executeSafeTransferFrom(address to) external {
65 | IGuardedToken(_collection).safeTransferFrom(address(this), to, _tokenId);
66 | }
67 |
68 | function executeTransferFrom(address to) external {
69 | IGuardedToken(_collection).transferFrom(address(this), to, _tokenId);
70 | }
71 |
72 | function executeApprove(address to) external {
73 | IGuardedToken(_collection).approve(to, _tokenId);
74 | }
75 |
76 | function executeSetApprovalForAll(address operator, bool approved) external {
77 | IGuardedToken(_collection).setApprovalForAll(operator, approved);
78 | }
79 |
80 | function executeBurn() external {
81 | ERC721Burnable(_collection).burn(_tokenId);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/test/mocks/MockWrongReturnDataERC721Recipient.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 | pragma solidity ^0.8.15;
3 |
4 | import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
5 |
6 | contract MockWrongReturnDataERC721Recipient is IERC721Receiver {
7 | function testMockWrongReturnDataERC721Recipient() public {
8 | // Prevents being counted in Foundry Coverage
9 | }
10 |
11 | bytes4 wrongReturnValue;
12 |
13 | constructor(bytes4 returnValue) {
14 | if (returnValue == IERC721Receiver.onERC721Received.selector) {
15 | revert('Only wrong values can be passed to this mock contract');
16 | }
17 | wrongReturnValue = returnValue;
18 | }
19 |
20 | function onERC721Received(address, address, uint256, bytes calldata) public virtual override returns (bytes4) {
21 | return wrongReturnValue;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/modules/act/collect/BaseFeeCollectModule.base.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.10;
3 |
4 | import 'test/base/BaseTest.t.sol';
5 | import {SimpleFeeCollectModule} from 'contracts/modules/act/collect/SimpleFeeCollectModule.sol';
6 | import {BaseFeeCollectModuleInitData} from 'contracts/modules/interfaces/IBaseFeeCollectModule.sol';
7 | import {MockCurrency} from 'test/mocks/MockCurrency.sol';
8 |
9 | contract BaseFeeCollectModuleBase is BaseTest {
10 | function testBaseFeeCollectModuleBase() public {
11 | // Prevents being counted in Foundry Coverage
12 | }
13 |
14 | using stdJson for string;
15 | address baseFeeCollectModule;
16 | address constant collectPublicationAction = address(0xC011EC7AC7104);
17 |
18 | MockCurrency currency;
19 |
20 | BaseFeeCollectModuleInitData exampleInitData;
21 |
22 | uint256 constant DEFAULT_COLLECT_LIMIT = 3;
23 | uint16 constant REFERRAL_FEE_BPS = 250;
24 |
25 | function setUp() public virtual override {
26 | super.setUp();
27 | exampleInitData.amount = 1 ether;
28 | exampleInitData.collectLimit = 0;
29 | exampleInitData.currency = address(currency);
30 | exampleInitData.referralFee = 0;
31 | exampleInitData.followerOnly = false;
32 | exampleInitData.endTimestamp = 0;
33 | exampleInitData.recipient = defaultAccount.owner;
34 | }
35 |
36 | // Deploy & Whitelist BaseFeeCollectModule
37 | constructor() BaseTest() {
38 | if (fork && keyExists(string(abi.encodePacked('.', forkEnv, '.SimpleFeeCollectModule')))) {
39 | baseFeeCollectModule = address(
40 | SimpleFeeCollectModule(
41 | json.readAddress(string(abi.encodePacked('.', forkEnv, '.SimpleFeeCollectModule')))
42 | )
43 | );
44 | console.log('Testing against already deployed module at:', baseFeeCollectModule);
45 | } else {
46 | vm.prank(deployer);
47 | baseFeeCollectModule = address(
48 | new SimpleFeeCollectModule(address(hub), collectPublicationAction, address(moduleGlobals))
49 | );
50 | }
51 | currency = new MockCurrency();
52 | vm.prank(modulesGovernance);
53 | moduleGlobals.whitelistCurrency(address(currency), true);
54 | }
55 |
56 | function getEncodedInitData() internal virtual returns (bytes memory) {
57 | return abi.encode(exampleInitData);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/modules/act/collect/MultirecipientCollectModule.base.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.10;
3 |
4 | import 'forge-std/Test.sol';
5 | import 'test/modules/act/collect/BaseFeeCollectModule.base.sol';
6 | import {MultirecipientFeeCollectModule, MultirecipientFeeCollectModuleInitData, RecipientData} from 'contracts/modules/act/collect/MultirecipientFeeCollectModule.sol';
7 |
8 | contract MultirecipientCollectModuleBase is BaseFeeCollectModuleBase {
9 | function testMultirecipientCollectModuleBase() public {
10 | // Prevents being counted in Foundry Coverage
11 | }
12 |
13 | using stdJson for string;
14 | uint16 constant BPS_MAX = 10000;
15 | uint256 MAX_RECIPIENTS = 5;
16 |
17 | MultirecipientFeeCollectModule multirecipientFeeCollectModule;
18 | MultirecipientFeeCollectModuleInitData multirecipientExampleInitData;
19 |
20 | function setUp() public virtual override {
21 | super.setUp();
22 | }
23 |
24 | // Deploy & Whitelist MultirecipientFeeCollectModule
25 | constructor() BaseTest() {
26 | if (fork && keyExists(string(abi.encodePacked('.', forkEnv, '.MultirecipientFeeCollectModule')))) {
27 | multirecipientFeeCollectModule = MultirecipientFeeCollectModule(
28 | json.readAddress(string(abi.encodePacked('.', forkEnv, '.MultirecipientFeeCollectModule')))
29 | );
30 | console.log('Testing against already deployed module at:', address(multirecipientFeeCollectModule));
31 | } else {
32 | vm.prank(deployer);
33 | multirecipientFeeCollectModule = new MultirecipientFeeCollectModule(
34 | hubProxyAddr,
35 | collectPublicationAction,
36 | address(moduleGlobals)
37 | );
38 | }
39 | baseFeeCollectModule = address(multirecipientFeeCollectModule);
40 | currency = new MockCurrency();
41 | vm.prank(modulesGovernance);
42 | moduleGlobals.whitelistCurrency(address(currency), true);
43 | }
44 |
45 | function getEncodedInitData() internal virtual override returns (bytes memory) {
46 | multirecipientExampleInitData.amount = exampleInitData.amount;
47 | multirecipientExampleInitData.collectLimit = exampleInitData.collectLimit;
48 | multirecipientExampleInitData.currency = exampleInitData.currency;
49 | multirecipientExampleInitData.referralFee = exampleInitData.referralFee;
50 | multirecipientExampleInitData.followerOnly = exampleInitData.followerOnly;
51 | multirecipientExampleInitData.endTimestamp = exampleInitData.endTimestamp;
52 | if (multirecipientExampleInitData.recipients.length == 0)
53 | multirecipientExampleInitData.recipients.push(
54 | RecipientData({recipient: exampleInitData.recipient, split: BPS_MAX})
55 | );
56 |
57 | return abi.encode(multirecipientExampleInitData);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/modules/follow/RevertFollowModule.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: UNLICENSED
2 | pragma solidity ^0.8.10;
3 |
4 | import 'test/base/BaseTest.t.sol';
5 | import {RevertFollowModule} from 'contracts/modules/follow/RevertFollowModule.sol';
6 | import {Errors as ModuleErrors} from 'contracts/modules/constants/Errors.sol';
7 |
8 | contract RevertFollowModuleTest is BaseTest {
9 | using stdJson for string;
10 | RevertFollowModule revertFollowModule;
11 |
12 | // Deploy & Whitelist RevertFollowModule
13 | constructor() TestSetup() {
14 | if (fork && keyExists(string(abi.encodePacked('.', forkEnv, '.RevertFollowModule')))) {
15 | revertFollowModule = RevertFollowModule(
16 | json.readAddress(string(abi.encodePacked('.', forkEnv, '.RevertFollowModule')))
17 | );
18 | console.log('Testing against already deployed module at:', address(revertFollowModule));
19 | } else {
20 | vm.prank(deployer);
21 | revertFollowModule = new RevertFollowModule();
22 | }
23 | }
24 |
25 | // RevertFollowModule doesn't need initialization, so this always returns an empty bytes array and is
26 | // callable by anyone
27 | function testInitialize(address from, uint256 profileId) public {
28 | vm.prank(from);
29 | revertFollowModule.initializeFollowModule(profileId, address(0), '');
30 | }
31 |
32 | // Negatives
33 | function testCannotProcessFollow(
34 | address from,
35 | uint256 followerProfileId,
36 | uint256 followerTokenId,
37 | address transactionExecutor,
38 | uint256 profileId
39 | ) public {
40 | vm.assume(from != address(0));
41 | vm.assume(followerProfileId != 0);
42 | vm.assume(followerTokenId != 0);
43 | vm.assume(profileId != 0);
44 |
45 | vm.expectRevert(ModuleErrors.FollowInvalid.selector);
46 |
47 | vm.prank(from);
48 | revertFollowModule.processFollow(followerProfileId, followerTokenId, transactionExecutor, profileId, '');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "module": "commonjs",
5 | "strict": true,
6 | "esModuleInterop": true,
7 | "outDir": "dist",
8 | "noImplicitAny": false,
9 | "resolveJsonModule": true
10 | },
11 | "include": ["./scripts", "./test"],
12 | "files": ["./hardhat.config.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/verifyStorageSlots.sh:
--------------------------------------------------------------------------------
1 | # This script compares the Storage Layout differences before/after the upgrade ("old"/"new" implementations).
2 | # The address of previous implentation if fetched from TransparentProxy "implementation()" slot.
3 | # The previous implementation source code is fetched from the block explorer.
4 | # New implementation is assumed to be in the current repo
5 | # Storage Layouts are generated from both implementations and compared using diff
6 | # (It is normal for the numbers in end of type names to be different)
7 | source .env
8 |
9 | if [[ $1 == "" ]]
10 | then
11 | echo "Usage:"
12 | echo " verifyStorageSlots.sh [network] [contractName]"
13 | echo " e.g. network (required): polygon or mumbai"
14 | echo " e.g. contractName (optional): LensHub is default"
15 | echo ""
16 | echo "Example:"
17 | echo " verifyStorageSlots.sh polygon LensHub"
18 | exit 1
19 | fi
20 |
21 | # TransparentUpgradeableProxy implementation slot
22 | implementationSlot="0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
23 |
24 | if [[ $2 != "" ]]
25 | then
26 | contractName=$2
27 | else
28 | contractName="LensHub"
29 | fi
30 |
31 | case $1 in
32 | 'polygon' | 'matic')
33 | echo "Network: Polygon mainnet"
34 | rpcUrl=$POLYGON_RPC_URL
35 | proxyAddress=$LENS_HUB_POLYGON
36 | blockexplorerKey=$POLYGONSCAN_MAINNET_KEY
37 | chain='polygon'
38 | ;;
39 | 'mumbai')
40 | echo "Network: Polygon mainnet"
41 | rpcUrl=$MUMBAI_RPC_URL
42 | proxyAddress=$LENS_HUB_MUMBAI
43 | blockexplorerKey=$POLYGONSCAN_MUMBAI_KEY
44 | chain='mumbai'
45 | ;;
46 | *)
47 | echo "ERROR: Unsupported network"
48 | exit 1
49 | ;;
50 | esac
51 |
52 | echo "Proxy address:" $proxyAddress
53 | echo "Contract name:" $contractName
54 |
55 | # Fetching the old implementation address
56 | rawOldImplAddress=$(cast storage $proxyAddress $implementationSlot --rpc-url $rpcUrl)
57 | oldImplAddress="0x${rawOldImplAddress:(-40)}"
58 | echo "Old Implementation address: $oldImplAddress"
59 |
60 | # Fetching the old implementation source code and saving it to oldImpl folder
61 | rm -rf oldImpl
62 | cast etherscan-source -d oldImpl $oldImplAddress --chain $chain --etherscan-api-key $blockexplorerKey
63 | echo "Old Implementation code saved to ./oldImpl/"
64 |
65 | # Generating the Storage Layout JSON of the old implementation
66 | echo "Generating the Storage Layout JSON of the old implementation..."
67 | forge inspect $contractName storage --contracts oldImpl > oldImplStorageLayout.json
68 |
69 | # Generating the Storage Layout JSON of the new implementation
70 | echo "Generating the Storage Layout JSON of the new implementation..."
71 | forge inspect $contractName storage > newImplStorageLayout.json
72 |
73 | diff <(awk '/astId/{next} /"types"/{found=0} {if(found) print} /"storage"/{found=1}' oldImplStorageLayout.json) <(awk '/astId/{next} /"types"/{found=0} {if(found) print} /"storage"/{found=1}' newImplStorageLayout.json) -c
74 |
--------------------------------------------------------------------------------