├── .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 | --------------------------------------------------------------------------------