├── .github └── workflows │ ├── analyze.yaml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── artifacts ├── abi │ ├── Elections.json │ ├── assets │ │ ├── lsp7 │ │ │ ├── DigitalAssetDrop.json │ │ │ ├── GenesisDigitalAsset.json │ │ │ └── MintableDigitalAsset.json │ │ └── lsp8 │ │ │ ├── CollectorIdentifiableDigitalAsset.json │ │ │ └── MintableIdentifiableDigitalAsset.json │ ├── drops │ │ ├── LSP7DropsDigitalAsset.json │ │ ├── LSP7DropsLightAsset.json │ │ ├── LSP8DropsDigitalAsset.json │ │ └── LSP8DropsLightAsset.json │ ├── marketplace │ │ ├── Participant.json │ │ ├── lsp7 │ │ │ ├── LSP7Listings.json │ │ │ ├── LSP7Marketplace.json │ │ │ ├── LSP7Offers.json │ │ │ └── LSP7Orders.json │ │ └── lsp8 │ │ │ ├── LSP8Auctions.json │ │ │ ├── LSP8Listings.json │ │ │ ├── LSP8Marketplace.json │ │ │ ├── LSP8Offers.json │ │ │ └── LSP8Orders.json │ ├── page │ │ └── PageName.json │ ├── pool │ │ ├── IDepositContract.json │ │ └── Vault.json │ └── profiles │ │ ├── ProfilesOracle.json │ │ └── ProfilesReverseLookup.json ├── bytecode │ ├── 42 │ │ ├── assets │ │ │ ├── lsp7 │ │ │ │ ├── DigitalAssetDrop.bin │ │ │ │ └── MintableDigitalAsset.bin │ │ │ └── lsp8 │ │ │ │ └── MintableIdentifiableDigitalAsset.bin │ │ └── drops │ │ │ ├── LSP7DropsDigitalAsset.bin │ │ │ ├── LSP7DropsLightAsset.bin │ │ │ ├── LSP8DropsDigitalAsset.bin │ │ │ └── LSP8DropsLightAsset.bin │ ├── 4201 │ │ ├── assets │ │ │ ├── lsp7 │ │ │ │ ├── DigitalAssetDrop.bin │ │ │ │ └── MintableDigitalAsset.bin │ │ │ └── lsp8 │ │ │ │ └── MintableIdentifiableDigitalAsset.bin │ │ └── drops │ │ │ ├── LSP7DropsDigitalAsset.bin │ │ │ ├── LSP7DropsLightAsset.bin │ │ │ ├── LSP8DropsDigitalAsset.bin │ │ │ └── LSP8DropsLightAsset.bin │ ├── 8453 │ │ ├── assets │ │ │ ├── lsp7 │ │ │ │ ├── DigitalAssetDrop.bin │ │ │ │ └── MintableDigitalAsset.bin │ │ │ └── lsp8 │ │ │ │ └── MintableIdentifiableDigitalAsset.bin │ │ └── drops │ │ │ ├── LSP7DropsDigitalAsset.bin │ │ │ ├── LSP7DropsLightAsset.bin │ │ │ ├── LSP8DropsDigitalAsset.bin │ │ │ └── LSP8DropsLightAsset.bin │ └── 84532 │ │ ├── assets │ │ ├── lsp7 │ │ │ ├── DigitalAssetDrop.bin │ │ │ └── MintableDigitalAsset.bin │ │ └── lsp8 │ │ │ └── MintableIdentifiableDigitalAsset.bin │ │ └── drops │ │ ├── LSP7DropsDigitalAsset.bin │ │ ├── LSP7DropsLightAsset.bin │ │ ├── LSP8DropsDigitalAsset.bin │ │ └── LSP8DropsLightAsset.bin └── data │ ├── 42 │ └── genesis.json │ └── 4201 │ └── genesis.json ├── audits ├── 2023-1-9-UniversalPage.md └── 2024-2-1-UniversalPage-Vault-security-review.pdf ├── foundry.toml ├── remappings.txt ├── slither.config.json ├── slither.db.json ├── src ├── Elections.sol ├── assets │ ├── lsp7 │ │ ├── DigitalAssetDrop.sol │ │ ├── GenesisDigitalAsset.sol │ │ └── MintableDigitalAsset.sol │ └── lsp8 │ │ ├── CollectorIdentifiableDigitalAsset.sol │ │ ├── ICollectorIdentifiableDigitalAsset.sol │ │ └── MintableIdentifiableDigitalAsset.sol ├── common │ ├── IndexedDrop.sol │ ├── LSP8CompatibleERC721.sol │ ├── LSP8CompatibleERC721Enumerable.sol │ ├── Points.sol │ ├── Royalties.sol │ └── Withdrawable.sol ├── drops │ ├── DropsDigitalAsset.sol │ ├── DropsLightAsset.sol │ ├── LSP7DropsDigitalAsset.sol │ ├── LSP7DropsLightAsset.sol │ ├── LSP8DropsDigitalAsset.sol │ └── LSP8DropsLightAsset.sol ├── marketplace │ ├── IParticipant.sol │ ├── Participant.sol │ ├── common │ │ ├── Base.sol │ │ └── Module.sol │ ├── lsp7 │ │ ├── ILSP7Listings.sol │ │ ├── ILSP7Offers.sol │ │ ├── ILSP7Orders.sol │ │ ├── LSP7Listings.sol │ │ ├── LSP7Marketplace.sol │ │ ├── LSP7Offers.sol │ │ └── LSP7Orders.sol │ └── lsp8 │ │ ├── ILSP8Auctions.sol │ │ ├── ILSP8Listings.sol │ │ ├── ILSP8Offers.sol │ │ ├── ILSP8Orders.sol │ │ ├── LSP8Auctions.sol │ │ ├── LSP8Listings.sol │ │ ├── LSP8Marketplace.sol │ │ ├── LSP8Offers.sol │ │ └── LSP8Orders.sol ├── page │ └── PageName.sol ├── pool │ ├── IDepositContract.sol │ └── Vault.sol └── profiles │ ├── ProfilesOracle.sol │ └── ProfilesReverseLookup.sol └── test ├── assets ├── lsp7 │ ├── DigitalAssetDrop.t.sol │ ├── DigitalAssetMock.sol │ ├── GenesisDigitalAsset.t.sol │ └── MintableDigitalAsset.t.sol └── lsp8 │ ├── CollectorIdentifiableDigitalAsset.t.sol │ └── MintableIdentifiableDigitalAsset.t.sol ├── common ├── AssetDropMock.sol └── IndexedDrop.t.sol ├── drops ├── LSP7DropsDigitalAsset.t.sol ├── LSP7DropsLightAsset.t.sol ├── LSP8DropsDigitalAsset.t.sol └── LSP8DropsLightAsset.t.sol ├── marketplace ├── Participant.t.sol ├── lsp7 │ ├── LSP7DigitalAssetMock.sol │ ├── LSP7Listings.t.sol │ ├── LSP7Marketplace.t.sol │ ├── LSP7Offers.t.sol │ └── LSP7Orders.t.sol └── lsp8 │ ├── LSP8Auctions.t.sol │ ├── LSP8DigitalAssetMock.sol │ ├── LSP8Listings.t.sol │ ├── LSP8Marketplace.t.sol │ ├── LSP8Offers.t.sol │ └── LSP8Orders.t.sol ├── page └── PageName.t.sol ├── pool └── Vault.t.sol ├── profiles └── ProfilesReverseLookup.t.sol └── utils └── profile.sol /.github/workflows/analyze.yaml: -------------------------------------------------------------------------------- 1 | name: analyze 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | analyze: 10 | strategy: 11 | fail-fast: true 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | submodules: recursive 18 | 19 | - name: Install Foundry 20 | uses: foundry-rs/foundry-toolchain@v1 21 | with: 22 | version: nightly 23 | 24 | - name: Install dependencies 25 | run: forge install 26 | 27 | - name: Build project 28 | run: forge build --build-info --skip */test/** */script/** --force 29 | 30 | - name: Run Slither 31 | uses: crytic/slither-action@v0.3.0 32 | id: slither 33 | with: 34 | sarif: results.sarif 35 | fail-on: none 36 | node-version: 18 37 | ignore-compile: true 38 | 39 | - name: Upload SARIF file 40 | uses: github/codeql-action/upload-sarif@v2 41 | with: 42 | sarif_file: ${{ steps.slither.outputs.sarif }} 43 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: recursive 21 | 22 | - name: Install Foundry 23 | uses: foundry-rs/foundry-toolchain@v1 24 | with: 25 | version: nightly 26 | 27 | - name: Run Forge build 28 | run: | 29 | forge --version 30 | forge build --sizes 31 | id: build 32 | 33 | - name: Run Forge tests 34 | run: | 35 | forge test -vvv 36 | id: test 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | broadcast/ 4 | docs/ 5 | .env* 6 | 7 | tools/playground.sh 8 | scripts/Playground.s.sol 9 | scripts/data.json 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/lsp-smart-contracts"] 8 | path = lib/lsp-smart-contracts 9 | url = https://github.com/lukso-network/lsp-smart-contracts 10 | [submodule "lib/openzeppelin-contracts-upgradeable"] 11 | path = lib/openzeppelin-contracts-upgradeable 12 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 13 | [submodule "lib/solidity-bytes-utils"] 14 | path = lib/solidity-bytes-utils 15 | url = https://github.com/GNSPS/solidity-bytes-utils 16 | [submodule "lib/murky"] 17 | path = lib/murky 18 | url = https://github.com/dmfxyz/murky 19 | [submodule "lib/erc725"] 20 | path = lib/erc725 21 | url = https://github.com/ERC725Alliance/ERC725 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "src", 3 | "solidity.packageDefaultDependenciesDirectory": "lib", 4 | "editor.formatOnSave": true, 5 | "[solidity]": { 6 | "editor.defaultFormatter": "JuanBlanco.solidity" 7 | }, 8 | "solidity.formatter": "forge", 9 | "solidity.compileUsingRemoteVersion": "v0.8.17" 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universal Page Contracts 2 | 3 | [![test](https://github.com/Universal-Page/contracts/actions/workflows/test.yml/badge.svg)](https://github.com/Universal-Page/contracts/actions/workflows/test.yml) 4 | [![analyze](https://github.com/Universal-Page/contracts/actions/workflows/analyze.yaml/badge.svg)](https://github.com/Universal-Page/contracts/actions/workflows/analyze.yaml) 5 | 6 | | network | contract / library | address | 7 | | ------- | --------------------- | ------------------------------------------ | 8 | | base | PageName | 0x679cc9e6d189e68884be00f18c5f77ff08988c97 | 9 | | base | Points | 0x3582f474F6E9FB087651b135d6224500A89e6f44 | 10 | | base | Royalties | 0xa29aeaabb5DA0CC3635576933a66c1B714f058C1 | 11 | | base | ProfilesReverseLookup | 0x7d6da08a9d13cec8649215f8bbd9dca101c22659 | 12 | | base | Participant | 0xeff07d835ebd1e6a7a785d00d4c701220aa6aea8 | 13 | | base | LSP7Listings | 0xe04cf97440cd191096c4103f9c48abd96184fb8d | 14 | | base | LSP7Offers | 0x44cd7d06ceb509370b75e426ea3c12824a665e36 | 15 | | base | LSP7Orders | 0xdf9defd55365b7b073cae009cf53dd830902c5a7 | 16 | | base | LSP7Marketplace | 0xc9c940a35fc8d3522085b991ce3e1a920354f19a | 17 | | base | LSP8Listings | 0xf069f9b8e0f96d742c6dfd3d78b0e382f3411207 | 18 | | base | LSP8Offers | 0xaebcc2c80abacb7e4d928d4c0a52c7bbeba4c4be | 19 | | base | LSP8Auctions | 0x39456bcd4d450e55f851f97c30df828a4e1f6c66 | 20 | | base | LSP8Marketplace | 0xe9f0feab3d50ccbe40d99f669fe1e89172908cdf | 21 | | lukso | PageName | 0x39456bcd4d450e55f851f97c30df828a4e1f6c66 | 22 | | lukso | GenesisDigitalAsset | 0x8da488c29fb873c9561ccf5ff44dda6c1deddc37 | 23 | | lukso | CollectorDigitalAsset | 0x5599d0ae8576250db2b9a9975fd3db1f6399b4fd | 24 | | lukso | Participant | 0xa29aeaabb5da0cc3635576933a66c1b714f058c1 | 25 | | lukso | LSP7Listings | 0xe7f5c709d62bcc3701f4c0cb871eb77e301283b5 | 26 | | lukso | LSP7Offers | 0xb2379f3f3c623cd2ed18e97e407cdda8fe6c6da6 | 27 | | lukso | LSP7Orders | 0x07d815d546072547471d9cde244367d274268b35 | 28 | | lukso | LSP7Marketplace | 0xe04cf97440cd191096c4103f9c48abd96184fb8d | 29 | | lukso | LSP8Listings | 0x4faab47b234c7f5da411429ee86cb15cb0754354 | 30 | | lukso | LSP8Offers | 0xed189b51455c9714aa49b0c55529469c512b10b6 | 31 | | lukso | LSP8Orders | 0xf76b34c79b8d4eca2a05c1302a0fc3e46bf97973 | 32 | | lukso | LSP8Auctions | 0x6eee8a19198bf39f2cefc24713acbdcc3c016dec | 33 | | lukso | LSP8Marketplace | 0x6807c995602eaf523a95a6b97acc4da0d3894655 | 34 | | lukso | Points | 0x157668416776c78EaB825D0d3969d75DC7dD7C0D | 35 | | lukso | Royalties | 0x391B24e80d85587C1cb698f0cD7Dfb7191D6875F | 36 | | lukso | Vault | 0xa5b37d755b97c272853b9726c905414706a0553a | 37 | | lukso | Elections | 0xd813fd267a5d3d10adbe9d22ba6dc7fda2ddf517 | 38 | | lukso | ProfilesOracle | 0x482a6fd801fe3290a49e465c168ad9f8772b8d7e | 39 | | lukso | ProfilesReverseLookup | 0xa0eb05c666fcf6cbeca77e14ec43cb5d5a852601 | 40 | | lukso-testnet | PageName | 0x8b08eeb9183081de7e2d4ae49fad4afb56e31ab4 | 41 | | lukso-testnet | GenesisDigitalAsset | 0xc06bcd7a286308861bd99da220acbc8901949fbd | 42 | | lukso-testnet | CollectorDigitalAsset | 0x2eef6216274bf0ede21a8a55cbb5b896bb82ac8b | 43 | | lukso-testnet | Participant | 0x5a485297a1b909032a6b7000354f3322047028ee | 44 | | lukso-testnet | LSP7Listings | 0xf3a20e7bc566940ed1e707c6d7d05497cf6527f1 | 45 | | lukso-testnet | LSP7Offers | 0x8f69db0bc0a1d156210259a154b73b7aa63f4631 | 46 | | lukso-testnet | LSP7Orders | 0x80e62ece29d2ae6a7fec34db5a9cefe4e34f40a9 | 47 | | lukso-testnet | LSP7Marketplace | 0x61c3dd3476a88de7a2bae7e2bc55889185faea1e | 48 | | lukso-testnet | LSP8Listings | 0x1dabeddbc94847b4ca9027073e545f67917a84f6 | 49 | | lukso-testnet | LSP8Offers | 0x84c0b26747a4f997ab1bfe5110a9579de2c0aeaf | 50 | | lukso-testnet | LSP8Orders | 0x3e81a952670a5df4062296d644dd5bf05cd475cb | 51 | | lukso-testnet | LSP8Auctions | 0xb20f814e55720e477640717bfbc139cf663e1ab4 | 52 | | lukso-testnet | LSP8Marketplace | 0x6364738eb197115aece87591dff51d554535d1f8 | 53 | | lukso-testnet | Points | 0x3582f474F6E9FB087651b135d6224500A89e6f44 | 54 | | lukso-testnet | Royalties | 0x1c51619209EFE37C759e4a9Ca91F1e68A96E19E3 | 55 | | lukso-testnet | Elections | 0xbe69df047c7e10766cbe5e8bd2fac3dc18a9b745 | 56 | | lukso-testnet | ProfilesReverseLookup | 0x953eef8151770c4cc60ec27468acee85eb8d81f8 | 57 | | base-sepolia | PageName | 0xeff07d835ebd1e6a7a785d00d4c701220aa6aea8 | 58 | | base-sepolia | Points | 0xa29aeaabb5DA0CC3635576933a66c1B714f058C1 | 59 | | base-sepolia | Royalties | 0x7D6DA08a9d13cEC8649215F8bbD9dcA101c22659 | 60 | | base-sepolia | ProfilesReverseLookup | 0x3582f474f6e9fb087651b135d6224500a89e6f44 | 61 | | base-sepolia | Participant | 0x59e468e64c23a44d6340cb2da6a701e9a8dcdc2b | 62 | | base-sepolia | LSP7Listings | 0x08e187a4adbe87e411917e0abe6d283461ca96c0 | 63 | | base-sepolia | LSP7Offers | 0x44cd7d06ceb509370b75e426ea3c12824a665e36 | 64 | | base-sepolia | LSP7Orders | 0xdf9defd55365b7b073cae009cf53dd830902c5a7 | 65 | | base-sepolia | LSP7Marketplace | 0xc9c940a35fc8d3522085b991ce3e1a920354f19a | 66 | | base-sepolia | LSP8Listings | 0xf069f9b8e0f96d742c6dfd3d78b0e382f3411207 | 67 | | base-sepolia | LSP8Offers | 0xaebcc2c80abacb7e4d928d4c0a52c7bbeba4c4be | 68 | | base-sepolia | LSP8Auctions | 0x39456bcd4d450e55f851f97c30df828a4e1f6c66 | 69 | | base-sepolia | LSP8Marketplace | 0xe9f0feab3d50ccbe40d99f669fe1e89172908cdf | 70 | 71 | ## Analyze 72 | 73 | Run `slither` by `slither . --triage-mode`. 74 | 75 | ## License 76 | 77 | See [LGPL-2.1 license](LICENSE) 78 | -------------------------------------------------------------------------------- /artifacts/abi/assets/lsp7/DigitalAssetDrop.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "constructor", 4 | "inputs": [ 5 | { 6 | "name": "asset_", 7 | "type": "address", 8 | "internalType": "contract ILSP7DigitalAsset" 9 | }, 10 | { 11 | "name": "root_", 12 | "type": "bytes32", 13 | "internalType": "bytes32" 14 | }, 15 | { 16 | "name": "owner_", 17 | "type": "address", 18 | "internalType": "address" 19 | } 20 | ], 21 | "stateMutability": "nonpayable" 22 | }, 23 | { 24 | "type": "function", 25 | "name": "asset", 26 | "inputs": [], 27 | "outputs": [ 28 | { 29 | "name": "", 30 | "type": "address", 31 | "internalType": "contract ILSP7DigitalAsset" 32 | } 33 | ], 34 | "stateMutability": "view" 35 | }, 36 | { 37 | "type": "function", 38 | "name": "claim", 39 | "inputs": [ 40 | { 41 | "name": "proof", 42 | "type": "bytes32[]", 43 | "internalType": "bytes32[]" 44 | }, 45 | { 46 | "name": "index", 47 | "type": "uint256", 48 | "internalType": "uint256" 49 | }, 50 | { 51 | "name": "recipient", 52 | "type": "address", 53 | "internalType": "address" 54 | }, 55 | { 56 | "name": "amount", 57 | "type": "uint256", 58 | "internalType": "uint256" 59 | } 60 | ], 61 | "outputs": [], 62 | "stateMutability": "nonpayable" 63 | }, 64 | { 65 | "type": "function", 66 | "name": "dispose", 67 | "inputs": [ 68 | { 69 | "name": "beneficiary", 70 | "type": "address", 71 | "internalType": "address" 72 | } 73 | ], 74 | "outputs": [], 75 | "stateMutability": "nonpayable" 76 | }, 77 | { 78 | "type": "function", 79 | "name": "isClaimed", 80 | "inputs": [ 81 | { 82 | "name": "index", 83 | "type": "uint256", 84 | "internalType": "uint256" 85 | } 86 | ], 87 | "outputs": [ 88 | { 89 | "name": "", 90 | "type": "bool", 91 | "internalType": "bool" 92 | } 93 | ], 94 | "stateMutability": "view" 95 | }, 96 | { 97 | "type": "function", 98 | "name": "owner", 99 | "inputs": [], 100 | "outputs": [ 101 | { 102 | "name": "", 103 | "type": "address", 104 | "internalType": "address" 105 | } 106 | ], 107 | "stateMutability": "view" 108 | }, 109 | { 110 | "type": "function", 111 | "name": "renounceOwnership", 112 | "inputs": [], 113 | "outputs": [], 114 | "stateMutability": "nonpayable" 115 | }, 116 | { 117 | "type": "function", 118 | "name": "transferOwnership", 119 | "inputs": [ 120 | { 121 | "name": "newOwner", 122 | "type": "address", 123 | "internalType": "address" 124 | } 125 | ], 126 | "outputs": [], 127 | "stateMutability": "nonpayable" 128 | }, 129 | { 130 | "type": "event", 131 | "name": "Claimed", 132 | "inputs": [ 133 | { 134 | "name": "index", 135 | "type": "uint256", 136 | "indexed": true, 137 | "internalType": "uint256" 138 | }, 139 | { 140 | "name": "recipient", 141 | "type": "address", 142 | "indexed": true, 143 | "internalType": "address" 144 | }, 145 | { 146 | "name": "amount", 147 | "type": "uint256", 148 | "indexed": false, 149 | "internalType": "uint256" 150 | } 151 | ], 152 | "anonymous": false 153 | }, 154 | { 155 | "type": "event", 156 | "name": "Disposed", 157 | "inputs": [ 158 | { 159 | "name": "beneficiary", 160 | "type": "address", 161 | "indexed": true, 162 | "internalType": "address" 163 | }, 164 | { 165 | "name": "amount", 166 | "type": "uint256", 167 | "indexed": false, 168 | "internalType": "uint256" 169 | } 170 | ], 171 | "anonymous": false 172 | }, 173 | { 174 | "type": "event", 175 | "name": "OwnershipTransferred", 176 | "inputs": [ 177 | { 178 | "name": "previousOwner", 179 | "type": "address", 180 | "indexed": true, 181 | "internalType": "address" 182 | }, 183 | { 184 | "name": "newOwner", 185 | "type": "address", 186 | "indexed": true, 187 | "internalType": "address" 188 | } 189 | ], 190 | "anonymous": false 191 | }, 192 | { 193 | "type": "error", 194 | "name": "AlreadyClaimed", 195 | "inputs": [ 196 | { 197 | "name": "index", 198 | "type": "uint256", 199 | "internalType": "uint256" 200 | }, 201 | { 202 | "name": "data", 203 | "type": "bytes", 204 | "internalType": "bytes" 205 | } 206 | ] 207 | }, 208 | { 209 | "type": "error", 210 | "name": "ClaimingUnavailable", 211 | "inputs": [] 212 | }, 213 | { 214 | "type": "error", 215 | "name": "InvalidBeneficiary", 216 | "inputs": [ 217 | { 218 | "name": "beneficiary", 219 | "type": "address", 220 | "internalType": "address" 221 | } 222 | ] 223 | }, 224 | { 225 | "type": "error", 226 | "name": "InvalidClaim", 227 | "inputs": [ 228 | { 229 | "name": "index", 230 | "type": "uint256", 231 | "internalType": "uint256" 232 | }, 233 | { 234 | "name": "data", 235 | "type": "bytes", 236 | "internalType": "bytes" 237 | } 238 | ] 239 | }, 240 | { 241 | "type": "error", 242 | "name": "OwnableCallerNotTheOwner", 243 | "inputs": [ 244 | { 245 | "name": "callerAddress", 246 | "type": "address", 247 | "internalType": "address" 248 | } 249 | ] 250 | }, 251 | { 252 | "type": "error", 253 | "name": "OwnableCannotSetZeroAddressAsOwner", 254 | "inputs": [] 255 | } 256 | ] 257 | -------------------------------------------------------------------------------- /artifacts/abi/marketplace/Participant.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "constructor", 4 | "inputs": [], 5 | "stateMutability": "nonpayable" 6 | }, 7 | { 8 | "type": "function", 9 | "name": "collectorAsset", 10 | "inputs": [], 11 | "outputs": [ 12 | { 13 | "name": "", 14 | "type": "address", 15 | "internalType": "contract ICollectorIdentifiableDigitalAsset" 16 | } 17 | ], 18 | "stateMutability": "view" 19 | }, 20 | { 21 | "type": "function", 22 | "name": "feeDiscountFor", 23 | "inputs": [ 24 | { 25 | "name": "profile", 26 | "type": "address", 27 | "internalType": "address" 28 | } 29 | ], 30 | "outputs": [ 31 | { 32 | "name": "", 33 | "type": "uint32", 34 | "internalType": "uint32" 35 | } 36 | ], 37 | "stateMutability": "view" 38 | }, 39 | { 40 | "type": "function", 41 | "name": "genesisAsset", 42 | "inputs": [], 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "address", 47 | "internalType": "contract ILSP7DigitalAsset" 48 | } 49 | ], 50 | "stateMutability": "view" 51 | }, 52 | { 53 | "type": "function", 54 | "name": "initialize", 55 | "inputs": [ 56 | { 57 | "name": "owner_", 58 | "type": "address", 59 | "internalType": "address" 60 | } 61 | ], 62 | "outputs": [], 63 | "stateMutability": "nonpayable" 64 | }, 65 | { 66 | "type": "function", 67 | "name": "owner", 68 | "inputs": [], 69 | "outputs": [ 70 | { 71 | "name": "", 72 | "type": "address", 73 | "internalType": "address" 74 | } 75 | ], 76 | "stateMutability": "view" 77 | }, 78 | { 79 | "type": "function", 80 | "name": "pause", 81 | "inputs": [], 82 | "outputs": [], 83 | "stateMutability": "nonpayable" 84 | }, 85 | { 86 | "type": "function", 87 | "name": "paused", 88 | "inputs": [], 89 | "outputs": [ 90 | { 91 | "name": "", 92 | "type": "bool", 93 | "internalType": "bool" 94 | } 95 | ], 96 | "stateMutability": "view" 97 | }, 98 | { 99 | "type": "function", 100 | "name": "renounceOwnership", 101 | "inputs": [], 102 | "outputs": [], 103 | "stateMutability": "nonpayable" 104 | }, 105 | { 106 | "type": "function", 107 | "name": "setCollectorAsset", 108 | "inputs": [ 109 | { 110 | "name": "collectorAsset_", 111 | "type": "address", 112 | "internalType": "contract ICollectorIdentifiableDigitalAsset" 113 | } 114 | ], 115 | "outputs": [], 116 | "stateMutability": "nonpayable" 117 | }, 118 | { 119 | "type": "function", 120 | "name": "setGenesisAsset", 121 | "inputs": [ 122 | { 123 | "name": "genesisAsset_", 124 | "type": "address", 125 | "internalType": "contract ILSP7DigitalAsset" 126 | } 127 | ], 128 | "outputs": [], 129 | "stateMutability": "nonpayable" 130 | }, 131 | { 132 | "type": "function", 133 | "name": "transferOwnership", 134 | "inputs": [ 135 | { 136 | "name": "newOwner", 137 | "type": "address", 138 | "internalType": "address" 139 | } 140 | ], 141 | "outputs": [], 142 | "stateMutability": "nonpayable" 143 | }, 144 | { 145 | "type": "function", 146 | "name": "unpause", 147 | "inputs": [], 148 | "outputs": [], 149 | "stateMutability": "nonpayable" 150 | }, 151 | { 152 | "type": "event", 153 | "name": "AssetFeeDiscountChanged", 154 | "inputs": [ 155 | { 156 | "name": "asset", 157 | "type": "address", 158 | "indexed": true, 159 | "internalType": "address" 160 | }, 161 | { 162 | "name": "previousDiscountPoints", 163 | "type": "uint32", 164 | "indexed": false, 165 | "internalType": "uint32" 166 | }, 167 | { 168 | "name": "newDiscountPoints", 169 | "type": "uint32", 170 | "indexed": false, 171 | "internalType": "uint32" 172 | } 173 | ], 174 | "anonymous": false 175 | }, 176 | { 177 | "type": "event", 178 | "name": "CollectorAssetChanged", 179 | "inputs": [ 180 | { 181 | "name": "previousCollectorAsset", 182 | "type": "address", 183 | "indexed": true, 184 | "internalType": "address" 185 | }, 186 | { 187 | "name": "newCollectorAsset", 188 | "type": "address", 189 | "indexed": true, 190 | "internalType": "address" 191 | } 192 | ], 193 | "anonymous": false 194 | }, 195 | { 196 | "type": "event", 197 | "name": "GenesisAssetChanged", 198 | "inputs": [ 199 | { 200 | "name": "previousGenesisAsset", 201 | "type": "address", 202 | "indexed": true, 203 | "internalType": "address" 204 | }, 205 | { 206 | "name": "newGenesisAsset", 207 | "type": "address", 208 | "indexed": true, 209 | "internalType": "address" 210 | } 211 | ], 212 | "anonymous": false 213 | }, 214 | { 215 | "type": "event", 216 | "name": "Initialized", 217 | "inputs": [ 218 | { 219 | "name": "version", 220 | "type": "uint8", 221 | "indexed": false, 222 | "internalType": "uint8" 223 | } 224 | ], 225 | "anonymous": false 226 | }, 227 | { 228 | "type": "event", 229 | "name": "OwnershipTransferred", 230 | "inputs": [ 231 | { 232 | "name": "previousOwner", 233 | "type": "address", 234 | "indexed": true, 235 | "internalType": "address" 236 | }, 237 | { 238 | "name": "newOwner", 239 | "type": "address", 240 | "indexed": true, 241 | "internalType": "address" 242 | } 243 | ], 244 | "anonymous": false 245 | }, 246 | { 247 | "type": "event", 248 | "name": "Paused", 249 | "inputs": [ 250 | { 251 | "name": "account", 252 | "type": "address", 253 | "indexed": false, 254 | "internalType": "address" 255 | } 256 | ], 257 | "anonymous": false 258 | }, 259 | { 260 | "type": "event", 261 | "name": "Unpaused", 262 | "inputs": [ 263 | { 264 | "name": "account", 265 | "type": "address", 266 | "indexed": false, 267 | "internalType": "address" 268 | } 269 | ], 270 | "anonymous": false 271 | }, 272 | { 273 | "type": "error", 274 | "name": "OwnableCallerNotTheOwner", 275 | "inputs": [ 276 | { 277 | "name": "callerAddress", 278 | "type": "address", 279 | "internalType": "address" 280 | } 281 | ] 282 | }, 283 | { 284 | "type": "error", 285 | "name": "OwnableCannotSetZeroAddressAsOwner", 286 | "inputs": [] 287 | } 288 | ] 289 | -------------------------------------------------------------------------------- /artifacts/abi/pool/IDepositContract.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "deposit", 5 | "inputs": [ 6 | { 7 | "name": "pubkey", 8 | "type": "bytes", 9 | "internalType": "bytes" 10 | }, 11 | { 12 | "name": "withdrawal_credentials", 13 | "type": "bytes", 14 | "internalType": "bytes" 15 | }, 16 | { 17 | "name": "signature", 18 | "type": "bytes", 19 | "internalType": "bytes" 20 | }, 21 | { 22 | "name": "deposit_data_root", 23 | "type": "bytes32", 24 | "internalType": "bytes32" 25 | } 26 | ], 27 | "outputs": [], 28 | "stateMutability": "payable" 29 | }, 30 | { 31 | "type": "function", 32 | "name": "get_deposit_count", 33 | "inputs": [], 34 | "outputs": [ 35 | { 36 | "name": "", 37 | "type": "bytes", 38 | "internalType": "bytes" 39 | } 40 | ], 41 | "stateMutability": "view" 42 | }, 43 | { 44 | "type": "function", 45 | "name": "get_deposit_root", 46 | "inputs": [], 47 | "outputs": [ 48 | { 49 | "name": "", 50 | "type": "bytes32", 51 | "internalType": "bytes32" 52 | } 53 | ], 54 | "stateMutability": "view" 55 | }, 56 | { 57 | "type": "event", 58 | "name": "DepositEvent", 59 | "inputs": [ 60 | { 61 | "name": "pubkey", 62 | "type": "bytes", 63 | "indexed": false, 64 | "internalType": "bytes" 65 | }, 66 | { 67 | "name": "withdrawal_credentials", 68 | "type": "bytes", 69 | "indexed": false, 70 | "internalType": "bytes" 71 | }, 72 | { 73 | "name": "amount", 74 | "type": "bytes", 75 | "indexed": false, 76 | "internalType": "bytes" 77 | }, 78 | { 79 | "name": "signature", 80 | "type": "bytes", 81 | "indexed": false, 82 | "internalType": "bytes" 83 | }, 84 | { 85 | "name": "index", 86 | "type": "bytes", 87 | "indexed": false, 88 | "internalType": "bytes" 89 | } 90 | ], 91 | "anonymous": false 92 | } 93 | ] 94 | -------------------------------------------------------------------------------- /artifacts/abi/profiles/ProfilesReverseLookup.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "function", 4 | "name": "profilesOf", 5 | "inputs": [ 6 | { 7 | "name": "controller", 8 | "type": "address", 9 | "internalType": "address" 10 | } 11 | ], 12 | "outputs": [ 13 | { 14 | "name": "", 15 | "type": "address[]", 16 | "internalType": "address[]" 17 | } 18 | ], 19 | "stateMutability": "view" 20 | }, 21 | { 22 | "type": "function", 23 | "name": "register", 24 | "inputs": [ 25 | { 26 | "name": "controller", 27 | "type": "address", 28 | "internalType": "address" 29 | }, 30 | { 31 | "name": "profile", 32 | "type": "address", 33 | "internalType": "address" 34 | }, 35 | { 36 | "name": "data", 37 | "type": "bytes", 38 | "internalType": "bytes" 39 | } 40 | ], 41 | "outputs": [], 42 | "stateMutability": "nonpayable" 43 | }, 44 | { 45 | "type": "function", 46 | "name": "registered", 47 | "inputs": [ 48 | { 49 | "name": "controller", 50 | "type": "address", 51 | "internalType": "address" 52 | }, 53 | { 54 | "name": "profile", 55 | "type": "address", 56 | "internalType": "address" 57 | } 58 | ], 59 | "outputs": [ 60 | { 61 | "name": "", 62 | "type": "bool", 63 | "internalType": "bool" 64 | } 65 | ], 66 | "stateMutability": "view" 67 | }, 68 | { 69 | "type": "function", 70 | "name": "unregister", 71 | "inputs": [ 72 | { 73 | "name": "controller", 74 | "type": "address", 75 | "internalType": "address" 76 | }, 77 | { 78 | "name": "profile", 79 | "type": "address", 80 | "internalType": "address" 81 | }, 82 | { 83 | "name": "data", 84 | "type": "bytes", 85 | "internalType": "bytes" 86 | } 87 | ], 88 | "outputs": [], 89 | "stateMutability": "nonpayable" 90 | }, 91 | { 92 | "type": "event", 93 | "name": "ProfileRegistered", 94 | "inputs": [ 95 | { 96 | "name": "controller", 97 | "type": "address", 98 | "indexed": true, 99 | "internalType": "address" 100 | }, 101 | { 102 | "name": "profile", 103 | "type": "address", 104 | "indexed": true, 105 | "internalType": "address" 106 | }, 107 | { 108 | "name": "data", 109 | "type": "bytes", 110 | "indexed": false, 111 | "internalType": "bytes" 112 | } 113 | ], 114 | "anonymous": false 115 | }, 116 | { 117 | "type": "event", 118 | "name": "ProfileUnregistered", 119 | "inputs": [ 120 | { 121 | "name": "controller", 122 | "type": "address", 123 | "indexed": true, 124 | "internalType": "address" 125 | }, 126 | { 127 | "name": "profile", 128 | "type": "address", 129 | "indexed": true, 130 | "internalType": "address" 131 | }, 132 | { 133 | "name": "data", 134 | "type": "bytes", 135 | "indexed": false, 136 | "internalType": "bytes" 137 | } 138 | ], 139 | "anonymous": false 140 | }, 141 | { 142 | "type": "error", 143 | "name": "AlreadyRegistered", 144 | "inputs": [ 145 | { 146 | "name": "controller", 147 | "type": "address", 148 | "internalType": "address" 149 | }, 150 | { 151 | "name": "profile", 152 | "type": "address", 153 | "internalType": "address" 154 | } 155 | ] 156 | }, 157 | { 158 | "type": "error", 159 | "name": "NotRegistered", 160 | "inputs": [ 161 | { 162 | "name": "controller", 163 | "type": "address", 164 | "internalType": "address" 165 | }, 166 | { 167 | "name": "profile", 168 | "type": "address", 169 | "internalType": "address" 170 | } 171 | ] 172 | }, 173 | { 174 | "type": "error", 175 | "name": "Unathorized", 176 | "inputs": [] 177 | }, 178 | { 179 | "type": "error", 180 | "name": "UnathorizedController", 181 | "inputs": [ 182 | { 183 | "name": "controller", 184 | "type": "address", 185 | "internalType": "address" 186 | }, 187 | { 188 | "name": "profile", 189 | "type": "address", 190 | "internalType": "address" 191 | } 192 | ] 193 | } 194 | ] 195 | -------------------------------------------------------------------------------- /artifacts/bytecode/42/assets/lsp7/DigitalAssetDrop.bin: -------------------------------------------------------------------------------- 1 | 0x60a060405234801561001057600080fd5b50604051610c71380380610c7183398101604081905261002f916101ad565b600180556001600160a01b03831661007e5760405162461bcd60e51b815260206004820152600d60248201526c6173736574206973207a65726f60981b60448201526064015b60405180910390fd5b60008290036100be5760405162461bcd60e51b815260206004820152600c60248201526b726f6f74206973207a65726f60a01b6044820152606401610075565b6001600160a01b0381166101045760405162461bcd60e51b815260206004820152600d60248201526c6f776e6572206973207a65726f60981b6044820152606401610075565b6001600160a01b03831660805261011a82600255565b6101238161012b565b5050506101f0565b6000546001600160a01b0382811691161461019557600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0383161790555b50565b6001600160a01b038116811461019557600080fd5b6000806000606084860312156101c257600080fd5b83516101cd81610198565b6020850151604086015191945092506101e581610198565b809150509250925092565b608051610a5261021f6000396000818160870152818161024f0152818161034c01526104450152610a526000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80639e34070f1161005b5780639e34070f146100e1578063c8edd8ab14610104578063f05c55ac14610117578063f2fde38b1461012a57600080fd5b806338d52e0f14610082578063715018a6146100c65780638da5cb5b146100d0575b600080fd5b6100a97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ce61013d565b005b6000546001600160a01b03166100a9565b6100f46100ef366004610874565b610151565b60405190151581526020016100bd565b6100ce6101123660046108a9565b610162565b6100ce61012536600461093d565b6102bb565b6100ce61013836600461093d565b6104ae565b6101456104ff565b61014f6000610554565b565b600061015c826105da565b92915050565b61016a61061b565b6101ca8585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152505060408051336020820152908101869052879250606001905060405160208183030381529060405261068e565b816001600160a01b0316837f4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed0268360405161020691815260200190565b60405180910390a3604051633b06cddd60e11b81523060048201526001600160a01b0383811660248301526044820183905260006064830181905260a0608484015260a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561029357600080fd5b505af11580156102a7573d6000803e3d6000fd5b505050506102b460018055565b5050505050565b6102c36104ff565b6102cb61061b565b6001600160a01b03811661031b576040517f1a3b45fd0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024015b60405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561039b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bf9190610958565b9050816001600160a01b03167feb44e1b23fad598a86840f12d9ab99216d186e1eeebb5ef8e3b3d152ba7cbc7e826040516103fc91815260200190565b60405180910390a2604051633b06cddd60e11b81523060048201526001600160a01b038381166024830152604482018390526001606483015260a06084830152600060a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561048957600080fd5b505af115801561049d573d6000803e3d6000fd5b50505050506104ab60018055565b50565b6104b66104ff565b6001600160a01b0381166104f6576040517f1ad8836c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104ab81610554565b336105126000546001600160a01b031690565b6001600160a01b03161461014f576040517fbf1169c5000000000000000000000000000000000000000000000000000000008152336004820152602401610312565b6000546001600160a01b038281169116146104ab57600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0383167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905550565b6000806105e961010084610987565b905060006105f96101008561099b565b60009283526003602052604090922054600190921b9182169091149392505050565b600260015403610687576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610312565b6002600155565b6002546106c7576040517f5058901600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106d0826105da565b1561070b5781816040517f331e2c3f0000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b600082826040516020016107209291906109af565b60408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061076284600254836107ab565b61079c5782826040517f4a0e7b130000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b6107a5836107c1565b50505050565b6000826107b885846107ff565b14949350505050565b60006107cf61010083610987565b905060006107df6101008461099b565b6000928352600360205260409092208054600190931b9092179091555050565b600081815b845181101561083a576108308286838151811061082357610823610a06565b6020026020010151610842565b9150600101610804565b509392505050565b600081831061085e57600082815260208490526040902061086d565b60008381526020839052604090205b9392505050565b60006020828403121561088657600080fd5b5035919050565b80356001600160a01b03811681146108a457600080fd5b919050565b6000806000806000608086880312156108c157600080fd5b853567ffffffffffffffff808211156108d957600080fd5b818801915088601f8301126108ed57600080fd5b8135818111156108fc57600080fd5b8960208260051b850101111561091157600080fd5b60209283019750955050860135925061092c6040870161088d565b949793965091946060013592915050565b60006020828403121561094f57600080fd5b61086d8261088d565b60006020828403121561096a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261099657610996610971565b500490565b6000826109aa576109aa610971565b500690565b8281526000602060406020840152835180604085015260005b818110156109e4578581018301518582016060015282016109c8565b506000606082860101526060601f19601f830116850101925050509392505050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220b4f3b1e27611ed7ad5887b686c22d8693c479c81c1c47bd55fc8c73d82ff444264736f6c63430008160033 2 | -------------------------------------------------------------------------------- /artifacts/bytecode/4201/assets/lsp7/DigitalAssetDrop.bin: -------------------------------------------------------------------------------- 1 | 0x60a060405234801561001057600080fd5b50604051610c71380380610c7183398101604081905261002f916101ad565b600180556001600160a01b03831661007e5760405162461bcd60e51b815260206004820152600d60248201526c6173736574206973207a65726f60981b60448201526064015b60405180910390fd5b60008290036100be5760405162461bcd60e51b815260206004820152600c60248201526b726f6f74206973207a65726f60a01b6044820152606401610075565b6001600160a01b0381166101045760405162461bcd60e51b815260206004820152600d60248201526c6f776e6572206973207a65726f60981b6044820152606401610075565b6001600160a01b03831660805261011a82600255565b6101238161012b565b5050506101f0565b6000546001600160a01b0382811691161461019557600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0383161790555b50565b6001600160a01b038116811461019557600080fd5b6000806000606084860312156101c257600080fd5b83516101cd81610198565b6020850151604086015191945092506101e581610198565b809150509250925092565b608051610a5261021f6000396000818160870152818161024f0152818161034c01526104450152610a526000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80639e34070f1161005b5780639e34070f146100e1578063c8edd8ab14610104578063f05c55ac14610117578063f2fde38b1461012a57600080fd5b806338d52e0f14610082578063715018a6146100c65780638da5cb5b146100d0575b600080fd5b6100a97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ce61013d565b005b6000546001600160a01b03166100a9565b6100f46100ef366004610874565b610151565b60405190151581526020016100bd565b6100ce6101123660046108a9565b610162565b6100ce61012536600461093d565b6102bb565b6100ce61013836600461093d565b6104ae565b6101456104ff565b61014f6000610554565b565b600061015c826105da565b92915050565b61016a61061b565b6101ca8585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152505060408051336020820152908101869052879250606001905060405160208183030381529060405261068e565b816001600160a01b0316837f4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed0268360405161020691815260200190565b60405180910390a3604051633b06cddd60e11b81523060048201526001600160a01b0383811660248301526044820183905260006064830181905260a0608484015260a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561029357600080fd5b505af11580156102a7573d6000803e3d6000fd5b505050506102b460018055565b5050505050565b6102c36104ff565b6102cb61061b565b6001600160a01b03811661031b576040517f1a3b45fd0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024015b60405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561039b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bf9190610958565b9050816001600160a01b03167feb44e1b23fad598a86840f12d9ab99216d186e1eeebb5ef8e3b3d152ba7cbc7e826040516103fc91815260200190565b60405180910390a2604051633b06cddd60e11b81523060048201526001600160a01b038381166024830152604482018390526001606483015260a06084830152600060a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561048957600080fd5b505af115801561049d573d6000803e3d6000fd5b50505050506104ab60018055565b50565b6104b66104ff565b6001600160a01b0381166104f6576040517f1ad8836c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104ab81610554565b336105126000546001600160a01b031690565b6001600160a01b03161461014f576040517fbf1169c5000000000000000000000000000000000000000000000000000000008152336004820152602401610312565b6000546001600160a01b038281169116146104ab57600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0383167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905550565b6000806105e961010084610987565b905060006105f96101008561099b565b60009283526003602052604090922054600190921b9182169091149392505050565b600260015403610687576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610312565b6002600155565b6002546106c7576040517f5058901600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106d0826105da565b1561070b5781816040517f331e2c3f0000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b600082826040516020016107209291906109af565b60408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061076284600254836107ab565b61079c5782826040517f4a0e7b130000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b6107a5836107c1565b50505050565b6000826107b885846107ff565b14949350505050565b60006107cf61010083610987565b905060006107df6101008461099b565b6000928352600360205260409092208054600190931b9092179091555050565b600081815b845181101561083a576108308286838151811061082357610823610a06565b6020026020010151610842565b9150600101610804565b509392505050565b600081831061085e57600082815260208490526040902061086d565b60008381526020839052604090205b9392505050565b60006020828403121561088657600080fd5b5035919050565b80356001600160a01b03811681146108a457600080fd5b919050565b6000806000806000608086880312156108c157600080fd5b853567ffffffffffffffff808211156108d957600080fd5b818801915088601f8301126108ed57600080fd5b8135818111156108fc57600080fd5b8960208260051b850101111561091157600080fd5b60209283019750955050860135925061092c6040870161088d565b949793965091946060013592915050565b60006020828403121561094f57600080fd5b61086d8261088d565b60006020828403121561096a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261099657610996610971565b500490565b6000826109aa576109aa610971565b500690565b8281526000602060406020840152835180604085015260005b818110156109e4578581018301518582016060015282016109c8565b506000606082860101526060601f19601f830116850101925050509392505050565b634e487b7160e01b600052603260045260246000fdfea2646970667358221220b2855f3193586b92fba8f6d80164ca07c6b5f060abd415cd9d2cde89eb79f39364736f6c63430008160033 2 | -------------------------------------------------------------------------------- /artifacts/bytecode/8453/assets/lsp7/DigitalAssetDrop.bin: -------------------------------------------------------------------------------- 1 | 0x60a060405234801561001057600080fd5b50604051610c71380380610c7183398101604081905261002f916101ad565b600180556001600160a01b03831661007e5760405162461bcd60e51b815260206004820152600d60248201526c6173736574206973207a65726f60981b60448201526064015b60405180910390fd5b60008290036100be5760405162461bcd60e51b815260206004820152600c60248201526b726f6f74206973207a65726f60a01b6044820152606401610075565b6001600160a01b0381166101045760405162461bcd60e51b815260206004820152600d60248201526c6f776e6572206973207a65726f60981b6044820152606401610075565b6001600160a01b03831660805261011a82600255565b6101238161012b565b5050506101f0565b6000546001600160a01b0382811691161461019557600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0383161790555b50565b6001600160a01b038116811461019557600080fd5b6000806000606084860312156101c257600080fd5b83516101cd81610198565b6020850151604086015191945092506101e581610198565b809150509250925092565b608051610a5261021f6000396000818160870152818161024f0152818161034c01526104450152610a526000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80639e34070f1161005b5780639e34070f146100e1578063c8edd8ab14610104578063f05c55ac14610117578063f2fde38b1461012a57600080fd5b806338d52e0f14610082578063715018a6146100c65780638da5cb5b146100d0575b600080fd5b6100a97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ce61013d565b005b6000546001600160a01b03166100a9565b6100f46100ef366004610874565b610151565b60405190151581526020016100bd565b6100ce6101123660046108a9565b610162565b6100ce61012536600461093d565b6102bb565b6100ce61013836600461093d565b6104ae565b6101456104ff565b61014f6000610554565b565b600061015c826105da565b92915050565b61016a61061b565b6101ca8585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152505060408051336020820152908101869052879250606001905060405160208183030381529060405261068e565b816001600160a01b0316837f4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed0268360405161020691815260200190565b60405180910390a3604051633b06cddd60e11b81523060048201526001600160a01b0383811660248301526044820183905260006064830181905260a0608484015260a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561029357600080fd5b505af11580156102a7573d6000803e3d6000fd5b505050506102b460018055565b5050505050565b6102c36104ff565b6102cb61061b565b6001600160a01b03811661031b576040517f1a3b45fd0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024015b60405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561039b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bf9190610958565b9050816001600160a01b03167feb44e1b23fad598a86840f12d9ab99216d186e1eeebb5ef8e3b3d152ba7cbc7e826040516103fc91815260200190565b60405180910390a2604051633b06cddd60e11b81523060048201526001600160a01b038381166024830152604482018390526001606483015260a06084830152600060a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561048957600080fd5b505af115801561049d573d6000803e3d6000fd5b50505050506104ab60018055565b50565b6104b66104ff565b6001600160a01b0381166104f6576040517f1ad8836c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104ab81610554565b336105126000546001600160a01b031690565b6001600160a01b03161461014f576040517fbf1169c5000000000000000000000000000000000000000000000000000000008152336004820152602401610312565b6000546001600160a01b038281169116146104ab57600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0383167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905550565b6000806105e961010084610987565b905060006105f96101008561099b565b60009283526003602052604090922054600190921b9182169091149392505050565b600260015403610687576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610312565b6002600155565b6002546106c7576040517f5058901600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106d0826105da565b1561070b5781816040517f331e2c3f0000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b600082826040516020016107209291906109af565b60408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061076284600254836107ab565b61079c5782826040517f4a0e7b130000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b6107a5836107c1565b50505050565b6000826107b885846107ff565b14949350505050565b60006107cf61010083610987565b905060006107df6101008461099b565b6000928352600360205260409092208054600190931b9092179091555050565b600081815b845181101561083a576108308286838151811061082357610823610a06565b6020026020010151610842565b9150600101610804565b509392505050565b600081831061085e57600082815260208490526040902061086d565b60008381526020839052604090205b9392505050565b60006020828403121561088657600080fd5b5035919050565b80356001600160a01b03811681146108a457600080fd5b919050565b6000806000806000608086880312156108c157600080fd5b853567ffffffffffffffff808211156108d957600080fd5b818801915088601f8301126108ed57600080fd5b8135818111156108fc57600080fd5b8960208260051b850101111561091157600080fd5b60209283019750955050860135925061092c6040870161088d565b949793965091946060013592915050565b60006020828403121561094f57600080fd5b61086d8261088d565b60006020828403121561096a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261099657610996610971565b500490565b6000826109aa576109aa610971565b500690565b8281526000602060406020840152835180604085015260005b818110156109e4578581018301518582016060015282016109c8565b506000606082860101526060601f19601f830116850101925050509392505050565b634e487b7160e01b600052603260045260246000fdfea264697066735822122090835f49f14edac192f631092f8722505f961d9fb7f8bf779dc4aa25a1f18b8c64736f6c63430008160033 2 | -------------------------------------------------------------------------------- /artifacts/bytecode/84532/assets/lsp7/DigitalAssetDrop.bin: -------------------------------------------------------------------------------- 1 | 0x60a060405234801561001057600080fd5b50604051610c71380380610c7183398101604081905261002f916101ad565b600180556001600160a01b03831661007e5760405162461bcd60e51b815260206004820152600d60248201526c6173736574206973207a65726f60981b60448201526064015b60405180910390fd5b60008290036100be5760405162461bcd60e51b815260206004820152600c60248201526b726f6f74206973207a65726f60a01b6044820152606401610075565b6001600160a01b0381166101045760405162461bcd60e51b815260206004820152600d60248201526c6f776e6572206973207a65726f60981b6044820152606401610075565b6001600160a01b03831660805261011a82600255565b6101238161012b565b5050506101f0565b6000546001600160a01b0382811691161461019557600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0383161790555b50565b6001600160a01b038116811461019557600080fd5b6000806000606084860312156101c257600080fd5b83516101cd81610198565b6020850151604086015191945092506101e581610198565b809150509250925092565b608051610a5261021f6000396000818160870152818161024f0152818161034c01526104450152610a526000f3fe608060405234801561001057600080fd5b506004361061007d5760003560e01c80639e34070f1161005b5780639e34070f146100e1578063c8edd8ab14610104578063f05c55ac14610117578063f2fde38b1461012a57600080fd5b806338d52e0f14610082578063715018a6146100c65780638da5cb5b146100d0575b600080fd5b6100a97f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b6100ce61013d565b005b6000546001600160a01b03166100a9565b6100f46100ef366004610874565b610151565b60405190151581526020016100bd565b6100ce6101123660046108a9565b610162565b6100ce61012536600461093d565b6102bb565b6100ce61013836600461093d565b6104ae565b6101456104ff565b61014f6000610554565b565b600061015c826105da565b92915050565b61016a61061b565b6101ca8585808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152505060408051336020820152908101869052879250606001905060405160208183030381529060405261068e565b816001600160a01b0316837f4ec90e965519d92681267467f775ada5bd214aa92c0dc93d90a5e880ce9ed0268360405161020691815260200190565b60405180910390a3604051633b06cddd60e11b81523060048201526001600160a01b0383811660248301526044820183905260006064830181905260a0608484015260a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561029357600080fd5b505af11580156102a7573d6000803e3d6000fd5b505050506102b460018055565b5050505050565b6102c36104ff565b6102cb61061b565b6001600160a01b03811661031b576040517f1a3b45fd0000000000000000000000000000000000000000000000000000000081526001600160a01b03821660048201526024015b60405180910390fd5b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561039b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103bf9190610958565b9050816001600160a01b03167feb44e1b23fad598a86840f12d9ab99216d186e1eeebb5ef8e3b3d152ba7cbc7e826040516103fc91815260200190565b60405180910390a2604051633b06cddd60e11b81523060048201526001600160a01b038381166024830152604482018390526001606483015260a06084830152600060a48301527f0000000000000000000000000000000000000000000000000000000000000000169063760d9bba9060c401600060405180830381600087803b15801561048957600080fd5b505af115801561049d573d6000803e3d6000fd5b50505050506104ab60018055565b50565b6104b66104ff565b6001600160a01b0381166104f6576040517f1ad8836c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6104ab81610554565b336105126000546001600160a01b031690565b6001600160a01b03161461014f576040517fbf1169c5000000000000000000000000000000000000000000000000000000008152336004820152602401610312565b6000546001600160a01b038281169116146104ab57600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0383167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905550565b6000806105e961010084610987565b905060006105f96101008561099b565b60009283526003602052604090922054600190921b9182169091149392505050565b600260015403610687576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610312565b6002600155565b6002546106c7576040517f5058901600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6106d0826105da565b1561070b5781816040517f331e2c3f0000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b600082826040516020016107209291906109af565b60408051601f198184030181528282528051602091820120908301520160405160208183030381529060405280519060200120905061076284600254836107ab565b61079c5782826040517f4a0e7b130000000000000000000000000000000000000000000000000000000081526004016103129291906109af565b6107a5836107c1565b50505050565b6000826107b885846107ff565b14949350505050565b60006107cf61010083610987565b905060006107df6101008461099b565b6000928352600360205260409092208054600190931b9092179091555050565b600081815b845181101561083a576108308286838151811061082357610823610a06565b6020026020010151610842565b9150600101610804565b509392505050565b600081831061085e57600082815260208490526040902061086d565b60008381526020839052604090205b9392505050565b60006020828403121561088657600080fd5b5035919050565b80356001600160a01b03811681146108a457600080fd5b919050565b6000806000806000608086880312156108c157600080fd5b853567ffffffffffffffff808211156108d957600080fd5b818801915088601f8301126108ed57600080fd5b8135818111156108fc57600080fd5b8960208260051b850101111561091157600080fd5b60209283019750955050860135925061092c6040870161088d565b949793965091946060013592915050565b60006020828403121561094f57600080fd5b61086d8261088d565b60006020828403121561096a57600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b60008261099657610996610971565b500490565b6000826109aa576109aa610971565b500690565b8281526000602060406020840152835180604085015260005b818110156109e4578581018301518582016060015282016109c8565b506000606082860101526060601f19601f830116850101925050509392505050565b634e487b7160e01b600052603260045260246000fdfea26469706673582212207907fea6ae8636b630e0f5da328af3d31fde0c021ee84b0fd4703fd2188ae3f164736f6c63430008160033 2 | -------------------------------------------------------------------------------- /artifacts/data/4201/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "0xA0260828430969DBd995794180B904BFE0b88c9b", 3 | "claims": { 4 | "0": { 5 | "amount": 1, 6 | "index": 0, 7 | "profile": "0x37Fa9FB05C2E3e9541a59B891321E9c4b8246442", 8 | "proof": [ 9 | "0xec66453ce4a43e93253436b481c9481974fd3de9d654d1936cddafe8c93f5130", 10 | "0x5523ff0100f4ee5ec131edccd5b0b6a7d07926fc3b5c0c0d96a8cb6a257a42f3" 11 | ] 12 | }, 13 | "1": { 14 | "amount": 2, 15 | "index": 1, 16 | "profile": "0x07F15aB9F47A004A21552E6b8C81ad2b37ff52D5", 17 | "proof": [ 18 | "0xd7862617e4e919ce949243f5d85404b6b10f5c09b173fe2ab99a1fd46acee801", 19 | "0x5523ff0100f4ee5ec131edccd5b0b6a7d07926fc3b5c0c0d96a8cb6a257a42f3" 20 | ] 21 | }, 22 | "2": { 23 | "amount": 3, 24 | "index": 2, 25 | "profile": "0x64f134CfF78A8A6EC55eF1992A36798C84460b26", 26 | "proof": [ 27 | "0x0000000000000000000000000000000000000000000000000000000000000000", 28 | "0x4b5c387764f24a9b6cb6e13c1452bf8b6f3691b1f3867ffb30128629c49bcea9" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /audits/2023-1-9-UniversalPage.md: -------------------------------------------------------------------------------- 1 | # Security Assessment 2 | 3 | - Reviewer: UniversalPage 4 | - Date: Jan 9th, 2023 5 | 6 | ## Executive Summary 7 | 8 | This report was prepared for UniversalPage smart contracts in order to identify common issues and vulnerabilities in the source code. A comprehensive examination of the code was conducted using symbolic execution, static analysis, and manual code review. 9 | 10 | The auditing process focuses on identifying attack vectors that pose the following risks: 11 | - Loss of funds and/or NFTs by both UniversalPage and its customers 12 | - Unauthorized access to change parameters and/or behavior of smart contracts 13 | 14 | The security assessment found a range of issues, including critical and informational ones. The critical and major issues have been fixed and tested both in code and manually. 15 | 16 | ## Introduction 17 | 18 | The review was performed on the following contracts: 19 | | Contract | Description | 20 | | - | - | 21 | | LSP7CreatorDigitalAsset, LSP8CreatorIdentifiableDigitalAsset| Assets deployed by users on UniversalPage | 22 | | LSP7DropDigitalAsset | LSP7 drop contract for explicitly listed accounts to claim preset number of tokens | 23 | | Withdrawable | Enables contracts to accept and withdraw funds to a single beneficiary | 24 | | ReentrancyGuard | Prevents reentrancy attack on set methods | 25 | | LSP8FixedDropDigitalAsset | LSP8 asset with fixed parameters (price, limits, etc) enabling minting of tokens and collecting of proceeds | 26 | | LSP7Listing, LSP8Listings | Marketplace contracts enabling users to manage listings | 27 | | LSP8Offers, LSP8Offers | Marketplace contracts enabling users to make offers on listings | 28 | | LSP8Auctions | Marketplace contract enabling users to manager auctions for listed assets | 29 | | LSP7Marketplace, LSP8Marketplace | Marketplace contracts enabling users to transact by buying, accepting offers and auctions bids | 30 | | Participant | Marketplace contract to tailor users transactions and experience | 31 | | UniversalPageName | LSP8 asset representing unique page name on UniversalPage | 32 | | UniversalPageNameController | Enforces controls on UniversalPageName tokens | 33 | | PaymentProcessor | Facilitates explicit transfers between users | 34 | | GenesisDigitalAsset | LSP7 asset representing attandance of UniversalPage mainnet launch | 35 | 36 | ## Methodology 37 | 38 | The contracts have been assessed for common vulnerabilities such as reentrancy attacks, and access control. The code is modularized files being less than 200 lines. Functionality of the code has been verified through excessive unit and component testing. The following table shows the code coverage of critical contracts and components of the system: 39 | 40 | | Contract | % Statements | % Branches | Functions | 41 | | - | -: | -: | -: | 42 | | ReentrancyGuard | 100 | 50 | 100 | 43 | | Withdrawable | 100 | 58.33 | 100 | 44 | | LSP8FixedDropDigitalAsset | 100 | 71.43 | 100 | 45 | | LSP7Listings | 100 | 74.19 | 100 | 46 | | LSP7Marketplace | 100 | 75 | 100 | 47 | | LSP7Offers | 100 | 61.36 | 100 | 48 | | LSP8Auctions | 100 | 59.68 | 85.71 | 49 | | LSP8Listings | 100 | 75 | 100 | 50 | | LSP8Marketplace | 97.22 | 73.33 | 87.5 | 51 | | LSP8Offers | 100 | 62.5 | 100 | 52 | | MarketplaceBase | 60.71 | 39.29 | 44.44 | 53 | | MarketplaceModule | 93.75 | 62.5 | 90.91 | 54 | | UniversalPageName | 95.83 | 68.75 | 87.5 | 55 | | UniversalPageNameController | 100 | 82.76 | 100 | 56 | 57 | Overall, including basic contracts and utilities: 87.85% statements are covered. 58 | 59 | Additional security analysis tools have been applied: 60 | - [Mythril](https://github.com/ConsenSys/mythril) - security analysis tool for EVM bytecode performs symbolic analysis. 61 | - [Slither](https://github.com/crytic/slither) - security analysis tool to detect common valnurabilities and patterns. 62 | 63 | ## Findings 64 | 65 | The tool analysis did not detect any major or critical issues. Some of results included: storage variables not being initialized, strict equality, reentrancy, and ignoring return values. All issues were manually reviewed and verified for correctness through unit tests. 66 | 67 | The following contracts transact user and/or UniversalPage assets: 68 | - LSP7DropDigitalAsset (funds/NFTs) 69 | - `claim` transfers N tokens from the contract. It verifies allowlist and whether tokens being claimed. This prevents unathorized claims and reentrancy. 70 | - `dispose` transfers all tokens from contract. It requires owner of the contract to call the method. 71 | - LSP7Marketplace, LSP8Marketplace (funds/NFTs) 72 | - `buy` accepts funds and verifies number of tokens and total price. Retains fee amount in the contract, pays out royalties and seller amounts. Verifies exact total paid by buyer. Transfers paid number of tokens to the buyer. Prevents reentrancy. 73 | - `acceptOffer` closes offer and receives offer balance. Retains fee amount in the contract, pays out royalties and seller amounts. Verifies exact total paid by buyer. Transfers paid number of tokens to the buyer. Prevents reentrancy. 74 | - LSP8Marketplace (funds/NFTs) 75 | - `acceptHighestBid` verifies auction is accepted by auction creator. Checks for highest bid, verifies bid's listing, auction, buyer, and receives bid balance. Retains fee amount in the contract, pays out royalties and seller amounts. Verifies exact total paid by buyer. Transfers paid number of tokens to the buyer. Prevents reentrancy. 76 | - LSP8FixedDropDigitalAsset (funds) 77 | - `mint` accepts funds, mints, and transfers tokens. Prevents a high level of bot attacks, multi mints, checks funds according to a price, records balances for owner and UniversalPage. 78 | - `claim` withdraws funds from the contract. Checks requester's balances, and prevents reentrancy. 79 | - LSP7Offers, LSP8Offers (funds) 80 | - `makeOffer` accepts funds per buyer and records total balance per listing. 81 | - `cancelOffer` disburses offer funds per listing and buyer to requester. Checks requester and offer made by the same buyer, pays out offer's total balance, and prevents reentrancy. 82 | - `closeOffer` disburses offer funds per listing and buyer to marketplace contract. Checks requester has marketplace role (granted to marketplace and auctions contracts only). 83 | - LSP8Auctions (funds) 84 | - `makeBid` accepts funds per listing. Records total accumulated bid amount. Prevents reentrancy. 85 | - `cancelBid` disburses total bid balance per listing. Checks for requester and buyer being the same account, and prevents reentrancy. 86 | - UniversalPageName (funds) 87 | - `reserve` accepts funds and verifies prices and balances restrictions. Mints token and prevents reetrnacy. 88 | - `release` burns user tokens. Prevents reentrancy and verifies operator of the token. 89 | - PaymentProcessor (funds) 90 | - `transfer` accepts funds and prevents reentrancy. Checks for zero amount. Pays out full amount to recipient. 91 | 92 | ## Conclusion 93 | 94 | Identified contracts and corresponding functionality are confirmed to be correct and high quality code by running automated tests, security analysis tools, and doing continues manual review and test. All identified major, critical, and informational issues were fixed and/or mitigated. 95 | 96 | UniversalPage has performed a best-effort audit of UniversalPage contracts. UniversalPage adheres to industry standards, applying the best practices in code quality, and security. The audit does not guarantee the security or functionality of the smart contract. Not all possible conditions were tested, and a user solely responsible for the proper use and handling of the smart contracts. 97 | -------------------------------------------------------------------------------- /audits/2024-2-1-UniversalPage-Vault-security-review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Universal-Page/contracts/91893d701ef041a8a4f9d83b69d5b04da4dc9789/audits/2024-2-1-UniversalPage-Vault-security-review.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | extra_output_files = ["metadata"] 6 | fs_permissions = [ 7 | { access = "read", path = "./scripts/" }, 8 | { access = "read-write", path = "./artifacts/" }, 9 | ] 10 | 11 | # solidity compiler 12 | solc = "0.8.22" 13 | 14 | # Increase optimizer_runs 15 | optimizer = true 16 | optimizer-runs = 1_000 17 | 18 | [fuzz] 19 | # Fuzz more than the default 256 20 | runs = 1_000 21 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @erc725/smart-contracts/=lib/erc725/implementations/ 2 | @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ 3 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 4 | @lukso/=lib/lsp-smart-contracts/packages/ 5 | ds-test/=lib/forge-std/lib/ds-test/src/ 6 | forge-std/=lib/forge-std/src/ 7 | solidity-bytes-utils/=lib/solidity-bytes-utils/ 8 | murky/=lib/murky/src/ 9 | -------------------------------------------------------------------------------- /slither.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "filter_paths": "lib/|test/" 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/lsp7/DigitalAssetDrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 5 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 6 | import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 7 | import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; 8 | import {IndexedDrop} from "../../common/IndexedDrop.sol"; 9 | 10 | contract DigitalAssetDrop is OwnableUnset, ReentrancyGuard, IndexedDrop { 11 | event Claimed(uint256 indexed index, address indexed recipient, uint256 amount); 12 | event Disposed(address indexed beneficiary, uint256 amount); 13 | 14 | error InvalidBeneficiary(address beneficiary); 15 | 16 | ILSP7DigitalAsset public immutable asset; 17 | 18 | constructor(ILSP7DigitalAsset asset_, bytes32 root_, address owner_) { 19 | require(address(asset_) != address(0), "asset is zero"); 20 | require(root_ != 0, "root is zero"); 21 | require(owner_ != address(0), "owner is zero"); 22 | asset = asset_; 23 | _setRoot(root_); 24 | _setOwner(owner_); 25 | } 26 | 27 | function isClaimed(uint256 index) external view returns (bool) { 28 | return _isClaimed(index); 29 | } 30 | 31 | function claim(bytes32[] calldata proof, uint256 index, address recipient, uint256 amount) external nonReentrant { 32 | _claim(proof, index, abi.encode(msg.sender, amount)); 33 | emit Claimed(index, recipient, amount); 34 | asset.transfer(address(this), recipient, amount, false, ""); 35 | } 36 | 37 | function dispose(address beneficiary) external onlyOwner nonReentrant { 38 | if (beneficiary == address(0)) { 39 | revert InvalidBeneficiary(beneficiary); 40 | } 41 | uint256 amount = asset.balanceOf(address(this)); 42 | emit Disposed(beneficiary, amount); 43 | asset.transfer(address(this), beneficiary, amount, true, ""); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/assets/lsp7/GenesisDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol"; 5 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 6 | 7 | contract GenesisDigitalAsset is LSP7DigitalAsset { 8 | error InvalidBeneficiary(); 9 | error UnathorizedAccount(address account); 10 | 11 | event BeneficiaryChanged(address indexed oldBeneficiary, address indexed newBeneficiary); 12 | 13 | address public beneficiary; 14 | 15 | constructor(string memory name_, string memory symbol_, address newOwner_, address newBeneficiary_) 16 | LSP7DigitalAsset(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, true) 17 | { 18 | _setBeneficiary(newBeneficiary_); 19 | } 20 | 21 | function setBeneficiary(address newBeneficiary) external onlyOwner { 22 | _setBeneficiary(newBeneficiary); 23 | } 24 | 25 | function _setBeneficiary(address newBeneficiary) internal virtual { 26 | if (newBeneficiary == address(0)) { 27 | revert InvalidBeneficiary(); 28 | } 29 | if (beneficiary != newBeneficiary) { 30 | address oldBeneficiary = beneficiary; 31 | beneficiary = newBeneficiary; 32 | emit BeneficiaryChanged(oldBeneficiary, newBeneficiary); 33 | } 34 | } 35 | 36 | function reserve(uint256 amount) external onlyOwner { 37 | _mint(beneficiary, amount, true, ""); 38 | } 39 | 40 | function release(uint256 amount) external { 41 | uint256 allowance = authorizedAmountFor(msg.sender, beneficiary); 42 | if (allowance < amount) { 43 | revert UnathorizedAccount(msg.sender); 44 | } 45 | _burn(beneficiary, amount, ""); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/lsp7/MintableDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP7Mintable} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/presets/LSP7Mintable.sol"; 5 | import {LSP7DigitalAssetCore} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol"; 6 | import {LSP7CappedSupply} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol"; 7 | 8 | contract MintableDigitalAsset is LSP7Mintable, LSP7CappedSupply { 9 | constructor( 10 | string memory name_, 11 | string memory symbol_, 12 | address newOwner_, 13 | uint256 lsp4TokenType_, 14 | bool isNonDivisible_, 15 | uint256 tokenSupplyCap_ 16 | ) LSP7Mintable(name_, symbol_, newOwner_, lsp4TokenType_, isNonDivisible_) LSP7CappedSupply(tokenSupplyCap_) {} 17 | 18 | function _mint(address to, uint256 amount, bool allowNonLSP1Recipient, bytes memory data) 19 | internal 20 | virtual 21 | override(LSP7DigitalAssetCore, LSP7CappedSupply) 22 | { 23 | super._mint(to, amount, allowNonLSP1Recipient, data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/lsp8/CollectorIdentifiableDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 5 | import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 6 | import {LSP8IdentifiableDigitalAsset} from 7 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; 8 | import {LSP8CappedSupply} from 9 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol"; 10 | import {LSP8Enumerable} from 11 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol"; 12 | import {LSP8IdentifiableDigitalAssetCore} from 13 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol"; 14 | import {_LSP8_TOKENID_FORMAT_UNIQUE_ID} from 15 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; 16 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 17 | import {Withdrawable} from "../../common/Withdrawable.sol"; 18 | import {ICollectorIdentifiableDigitalAsset} from "./ICollectorIdentifiableDigitalAsset.sol"; 19 | 20 | contract CollectorIdentifiableDigitalAsset is 21 | ICollectorIdentifiableDigitalAsset, 22 | LSP8IdentifiableDigitalAsset, 23 | LSP8CappedSupply, 24 | LSP8Enumerable, 25 | ReentrancyGuard, 26 | Withdrawable 27 | { 28 | error InvalidController(); 29 | error UnauthorizedPurchase(address recipient, bytes32[] tokenIds, uint256 totalPrice); 30 | error InvalidTokenId(bytes32 tokenId); 31 | 32 | event TokensPurchased(address indexed recipient, bytes32[] tokenIds, uint256 totalPaid); 33 | event ControllerChanged(address indexed oldController, address indexed newController); 34 | 35 | mapping(uint256 => bool) private _reservedTokenIds; 36 | address public controller; 37 | 38 | constructor( 39 | string memory name_, 40 | string memory symbol_, 41 | address newOwner_, 42 | address controller_, 43 | uint256 tokenSupplyCap_ 44 | ) 45 | LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, _LSP8_TOKENID_FORMAT_UNIQUE_ID) 46 | LSP8CappedSupply(tokenSupplyCap_) 47 | { 48 | _setController(controller_); 49 | } 50 | 51 | receive() external payable override(LSP8IdentifiableDigitalAsset, Withdrawable) { 52 | _doReceive(); 53 | } 54 | 55 | function tokenTierOf(bytes32 tokenId) external view override returns (uint8 tier) { 56 | _existsOrError(tokenId); 57 | tier = uint8(uint256(tokenId) & 0xF); 58 | } 59 | 60 | function tokenIndexOf(bytes32 tokenId) public view override returns (uint16 index) { 61 | _existsOrError(tokenId); 62 | index = uint16((uint256(tokenId) >> 4) & 0xFFFF); 63 | } 64 | 65 | function setController(address newController) external onlyOwner { 66 | _setController(newController); 67 | } 68 | 69 | function _setController(address newController) private { 70 | if (newController == address(0)) { 71 | revert InvalidController(); 72 | } 73 | address oldController = controller; 74 | if (oldController == newController) { 75 | revert InvalidController(); 76 | } 77 | controller = newController; 78 | emit ControllerChanged(oldController, newController); 79 | } 80 | 81 | function purchase(address recipient, bytes32[] calldata tokenIds, uint8 v, bytes32 r, bytes32 s) 82 | external 83 | payable 84 | override 85 | nonReentrant 86 | { 87 | bytes32 hash = keccak256(abi.encodePacked(address(this), block.chainid, recipient, tokenIds, msg.value)); 88 | if (ECDSA.recover(hash, v, r, s) != controller) { 89 | revert UnauthorizedPurchase(recipient, tokenIds, msg.value); 90 | } 91 | uint256 amount = tokenIds.length; 92 | for (uint256 i = 0; i < amount; i++) { 93 | _mint(recipient, tokenIds[i], true, ""); 94 | } 95 | emit TokensPurchased(recipient, tokenIds, msg.value); 96 | } 97 | 98 | function _mint(address to, bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data) 99 | internal 100 | virtual 101 | override(LSP8IdentifiableDigitalAssetCore, LSP8CappedSupply) 102 | { 103 | super._mint(to, tokenId, allowNonLSP1Recipient, data); 104 | uint256 index = tokenIndexOf(tokenId); 105 | if (_reservedTokenIds[index]) { 106 | revert InvalidTokenId(tokenId); 107 | } 108 | _reservedTokenIds[index] = true; 109 | } 110 | 111 | function _beforeTokenTransfer(address from, address to, bytes32 tokenId, bytes memory data) 112 | internal 113 | virtual 114 | override(LSP8IdentifiableDigitalAssetCore, LSP8Enumerable) 115 | { 116 | super._beforeTokenTransfer(from, to, tokenId, data); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/assets/lsp8/ICollectorIdentifiableDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ILSP8IdentifiableDigitalAsset} from 5 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol"; 6 | 7 | interface ICollectorIdentifiableDigitalAsset is ILSP8IdentifiableDigitalAsset { 8 | /// A tier of a token if any. A tier is a number from 0 to 3. 3 is the highest tier. 9 | /// @param tokenId A token id to get a tier for. 10 | function tokenTierOf(bytes32 tokenId) external view returns (uint8 tier); 11 | 12 | /// An index of a token. 13 | /// @param tokenId A token id to get an index for. 14 | function tokenIndexOf(bytes32 tokenId) external view returns (uint16 index); 15 | 16 | /// Purchases tokens from the contract and transfers them to a recipient. 17 | /// Amount paid must be equal to the price multipled by the number of tokens. 18 | /// @param recipient A recipient of tokens. 19 | /// @param tokenIds A list of token ids to purchase. 20 | /// @param v A signature v. 21 | /// @param r A signature r. 22 | /// @param s A signature s. 23 | function purchase(address recipient, bytes32[] calldata tokenIds, uint8 v, bytes32 r, bytes32 s) external payable; 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/lsp8/MintableIdentifiableDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP8IdentifiableDigitalAssetCore} from 5 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol"; 6 | import {LSP8Mintable} from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol"; 7 | import {LSP8Enumerable} from 8 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol"; 9 | import {LSP8CappedSupply} from 10 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol"; 11 | 12 | contract MintableIdentifiableDigitalAsset is LSP8Mintable, LSP8Enumerable, LSP8CappedSupply { 13 | constructor( 14 | string memory name_, 15 | string memory symbol_, 16 | address newOwner_, 17 | uint256 tokenIdType_, 18 | uint256 lsp8TokenIdSchema_, 19 | uint256 tokenSupplyCap_ 20 | ) LSP8Mintable(name_, symbol_, newOwner_, tokenIdType_, lsp8TokenIdSchema_) LSP8CappedSupply(tokenSupplyCap_) { 21 | // noop 22 | } 23 | 24 | function _mint(address to, bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data) 25 | internal 26 | virtual 27 | override(LSP8IdentifiableDigitalAssetCore, LSP8CappedSupply) 28 | { 29 | super._mint(to, tokenId, allowNonLSP1Recipient, data); 30 | } 31 | 32 | function _beforeTokenTransfer(address from, address to, bytes32 tokenId, bytes memory data) 33 | internal 34 | virtual 35 | override(LSP8IdentifiableDigitalAssetCore, LSP8Enumerable) 36 | { 37 | super._beforeTokenTransfer(from, to, tokenId, data); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/IndexedDrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 5 | 6 | abstract contract IndexedDrop { 7 | error ClaimingUnavailable(); 8 | error InvalidClaim(uint256 index, bytes data); 9 | error AlreadyClaimed(uint256 index, bytes data); 10 | 11 | bytes32 private _root; 12 | mapping(uint256 => uint256) private _claimed; 13 | 14 | function root() internal view returns (bytes32) { 15 | return _root; 16 | } 17 | 18 | function _hasRoot() internal view returns (bool) { 19 | return _root != 0; 20 | } 21 | 22 | function _setRoot(bytes32 newRoot) internal { 23 | _root = newRoot; 24 | } 25 | 26 | function _isClaimed(uint256 index) internal view returns (bool) { 27 | uint256 slotIndex = index / 256; 28 | uint256 slotOffset = index % 256; 29 | uint256 value = 1 << slotOffset; 30 | return _claimed[slotIndex] & value == value; 31 | } 32 | 33 | function _markClaimed(uint256 index) private { 34 | uint256 slotIndex = index / 256; 35 | uint256 slotOffset = index % 256; 36 | _claimed[slotIndex] = _claimed[slotIndex] | (1 << slotOffset); 37 | } 38 | 39 | function _claim(bytes32[] memory proof, uint256 index, bytes memory data) internal { 40 | if (!_hasRoot()) { 41 | revert ClaimingUnavailable(); 42 | } 43 | if (_isClaimed(index)) { 44 | revert AlreadyClaimed(index, data); 45 | } 46 | bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, data)))); 47 | if (!MerkleProof.verify(proof, _root, leaf)) { 48 | revert InvalidClaim(index, data); 49 | } 50 | _markClaimed(index); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/common/LSP8CompatibleERC721Enumerable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | pragma solidity ^0.8.12; 3 | 4 | import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; 5 | import {IERC721Enumerable, IERC721} from "@openzeppelin/contracts/interfaces/IERC721Enumerable.sol"; 6 | import { 7 | LSP8IdentifiableDigitalAsset, 8 | LSP8IdentifiableDigitalAssetCore 9 | } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; 10 | import {LSP8Enumerable} from 11 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol"; 12 | 13 | abstract contract LSP8CompatibleERC721Enumerable is IERC721Enumerable, LSP8Enumerable { 14 | function supportsInterface(bytes4 interfaceId) 15 | public 16 | view 17 | virtual 18 | override(IERC165, LSP8IdentifiableDigitalAsset) 19 | returns (bool) 20 | { 21 | return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId); 22 | } 23 | 24 | function totalSupply() 25 | public 26 | view 27 | virtual 28 | override(IERC721Enumerable, LSP8IdentifiableDigitalAssetCore) 29 | returns (uint256) 30 | { 31 | return _existingTokens; 32 | } 33 | 34 | function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) { 35 | bytes32[] memory tokenIds = tokenIdsOf(owner); 36 | return uint256(tokenIds[index]); 37 | } 38 | 39 | function tokenByIndex(uint256 index) public view override returns (uint256) { 40 | return uint256(tokenAt(index)); 41 | } 42 | 43 | function balanceOf(address tokenOwner) 44 | public 45 | view 46 | virtual 47 | override(IERC721, LSP8IdentifiableDigitalAssetCore) 48 | returns (uint256) 49 | { 50 | return super.balanceOf(tokenOwner); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/common/Points.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | library Points { 5 | uint32 public constant BASIS = 100_000; 6 | 7 | function isValid(uint32 points) external pure returns (bool) { 8 | return points <= BASIS; 9 | } 10 | 11 | function multiply(uint32 one, uint32 other) external pure returns (uint32) { 12 | return (one * other) / BASIS; 13 | } 14 | 15 | function realize(uint256 value, uint32 points) external pure returns (uint256) { 16 | return (value * points) / BASIS; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/common/Royalties.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {IERC725Y} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; 5 | import {Points} from "./Points.sol"; 6 | 7 | error InvalidLSP18RoyaltiesPoints(uint32 points); 8 | error InvalidLSP18RoyaltiesData(bytes invalidValue); 9 | 10 | bytes32 constant _LSP18_ROYALTIES_RECIPIENTS_KEY = 0xc0569ca6c9180acc2c3590f36330a36ae19015a19f4e85c28a7631e3317e6b9d; 11 | bytes32 constant _LSP18_ROYALTIES_ENFORCE_PAYMENT = 0x580d62ad353782eca17b89e5900e7df3b13b6f4ca9bbc2f8af8bceb0c3d1ecc6; 12 | uint32 constant _LSP18_ROYALTIES_BASIS = Points.BASIS; 13 | 14 | struct RoyaltiesInfo { 15 | bytes4 interfaceId; 16 | address recipient; 17 | uint32 points; 18 | } 19 | 20 | library Royalties { 21 | function setRoyalties(address asset, RoyaltiesInfo memory info) internal { 22 | if (info.points > _LSP18_ROYALTIES_BASIS) { 23 | revert InvalidLSP18RoyaltiesPoints(info.points); 24 | } 25 | bytes memory entriesData = IERC725Y(asset).getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); 26 | RoyaltiesInfo[] memory entries = _decodeRoyalties(entriesData); 27 | 28 | // find and update existing entry if any 29 | int256 entryIndex = _indexOfRoyaltiesEntry(entries, info.recipient); 30 | 31 | if (entryIndex < 0) { 32 | // adding new entry 33 | entriesData = bytes.concat(entriesData, _encodeRoyaltiesEntry(info)); 34 | } else { 35 | // updating existing entry 36 | entries[uint256(entryIndex)] = info; 37 | entriesData = _encodeRoyalties(entries); 38 | } 39 | 40 | IERC725Y(asset).setData(_LSP18_ROYALTIES_RECIPIENTS_KEY, entriesData); 41 | } 42 | 43 | function royalties(address asset) internal view returns (RoyaltiesInfo[] memory) { 44 | bytes memory value = IERC725Y(asset).getData(_LSP18_ROYALTIES_RECIPIENTS_KEY); 45 | return _decodeRoyalties(value); 46 | } 47 | 48 | function royaltiesPaymentEnforced(address asset) internal view returns (bool) { 49 | bytes memory value = IERC725Y(asset).getData(_LSP18_ROYALTIES_ENFORCE_PAYMENT); 50 | return value.length > 0 && value[0] != 0; 51 | } 52 | 53 | function _indexOfRoyaltiesEntry(RoyaltiesInfo[] memory entries, address recipient) private pure returns (int256) { 54 | uint256 entriesCount = entries.length; 55 | for (uint256 i = 0; i < entriesCount; i++) { 56 | RoyaltiesInfo memory entry = entries[i]; 57 | if (entry.recipient == recipient) { 58 | return int256(i); 59 | } 60 | } 61 | return -1; 62 | } 63 | 64 | function _decodeRoyalties(bytes memory data) private pure returns (RoyaltiesInfo[] memory) { 65 | uint256 dataLength = data.length; 66 | 67 | // count number of entries 68 | uint256 count = 0; 69 | for (uint256 i = 0; i < dataLength;) { 70 | uint16 length; 71 | assembly { 72 | length := mload(add(add(data, 0x2), i)) 73 | } 74 | if (length < 4 /* interfaceId */ + 20 /* recipient */ ) { 75 | revert InvalidLSP18RoyaltiesData(data); 76 | } 77 | unchecked { 78 | i += 2 /* length */ + length; 79 | count++; 80 | } 81 | } 82 | 83 | RoyaltiesInfo[] memory result = new RoyaltiesInfo[](count); 84 | uint256 k = 0; 85 | uint256 j = 0; 86 | 87 | while (k < dataLength) { 88 | uint16 length; 89 | bytes4 interfaceId; 90 | address recipient; 91 | uint32 points; 92 | 93 | assembly { 94 | length := mload(add(add(data, 0x2), k)) 95 | interfaceId := mload(add(add(data, 0x4), add(k, 2))) 96 | recipient := div(mload(add(add(data, 0x20), add(k, 6))), 0x1000000000000000000000000) 97 | } 98 | 99 | // optional points 100 | if (length >= 4 /* interfaceId */ + 20 /* recipient */ + 4 /* points */ ) { 101 | assembly { 102 | points := mload(add(add(data, 0x4), add(k, 26))) 103 | } 104 | } 105 | 106 | // asign entry 107 | result[j] = RoyaltiesInfo(interfaceId, recipient, points); 108 | 109 | // skip any remaining bytes as unsupported 110 | unchecked { 111 | k += 2 /* length */ + length; 112 | j++; 113 | } 114 | } 115 | 116 | return result; 117 | } 118 | 119 | function _encodeRoyaltiesEntry(RoyaltiesInfo memory entry) private pure returns (bytes memory result) { 120 | // determine entry length 121 | uint16 length = 4 /* interfaceId */ + 20; /* recipient */ 122 | if (entry.points > 0) { 123 | unchecked { 124 | length += 4; /* points */ 125 | } 126 | } 127 | 128 | // encode entry 129 | result = bytes.concat(bytes2(length), entry.interfaceId, bytes20(entry.recipient)); 130 | 131 | // optional points 132 | if (entry.points > 0) { 133 | result = bytes.concat(result, bytes4(entry.points)); 134 | } 135 | } 136 | 137 | function _encodeRoyalties(RoyaltiesInfo[] memory entries) private pure returns (bytes memory) { 138 | bytes memory result = new bytes(0); 139 | uint256 count = entries.length; 140 | for (uint256 i = 0; i < count; i++) { 141 | result = bytes.concat(result, _encodeRoyaltiesEntry(entries[i])); 142 | } 143 | return result; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/common/Withdrawable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 5 | 6 | abstract contract Withdrawable is OwnableUnset { 7 | error InvalidBeneficiary(address account); 8 | error DispositionFailure(address beneficiary, uint256 amount); 9 | 10 | event ValueReceived(address indexed sender, uint256 indexed value); 11 | event ValueWithdrawn(address indexed beneficiary, uint256 indexed value); 12 | event BeneficiaryChanged(address indexed oldBeneficiary, address indexed newBeneficiary); 13 | 14 | address public beneficiary; 15 | 16 | receive() external payable virtual { 17 | _doReceive(); 18 | } 19 | 20 | function _doReceive() internal virtual { 21 | if (msg.value > 0) { 22 | emit ValueReceived(msg.sender, msg.value); 23 | } 24 | } 25 | 26 | function setBeneficiary(address newBeneficiary) external onlyOwner { 27 | _setBeneficiary(newBeneficiary); 28 | } 29 | 30 | function _setBeneficiary(address newBeneficiary) internal virtual { 31 | if (newBeneficiary == address(0)) { 32 | revert InvalidBeneficiary(newBeneficiary); 33 | } 34 | if (beneficiary != newBeneficiary) { 35 | address oldBeneficiary = beneficiary; 36 | beneficiary = newBeneficiary; 37 | emit BeneficiaryChanged(oldBeneficiary, newBeneficiary); 38 | } 39 | } 40 | 41 | function withdraw(uint256 amount) external onlyOwner { 42 | if (beneficiary == address(0)) { 43 | revert InvalidBeneficiary(beneficiary); 44 | } 45 | (bool success,) = beneficiary.call{value: amount}(""); 46 | if (!success) { 47 | revert DispositionFailure(beneficiary, amount); 48 | } 49 | emit ValueWithdrawn(beneficiary, amount); 50 | } 51 | 52 | // reserved space (10 slots) 53 | uint256[9] private _reserved; 54 | } 55 | -------------------------------------------------------------------------------- /src/drops/DropsDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 5 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 6 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import {IndexedDrop} from "../common/IndexedDrop.sol"; 8 | import {Points} from "../common/Points.sol"; 9 | 10 | abstract contract DropsDigitalAsset is OwnableUnset, ReentrancyGuard, IndexedDrop { 11 | event Activated(); 12 | event Deactivated(); 13 | event Claimed(address indexed account, address indexed beneficiary, uint256 amount); 14 | event ConfigurationChanged(uint256 startTime, uint256 mintPrice, uint256 profileMintLimit, bytes32 allowlistRoot); 15 | 16 | error Inactive(); 17 | error ZeroAddress(); 18 | error ZeroAmount(); 19 | error ClaimInvalidAmount(uint256 amount); 20 | error ProfileMintZeroLimit(); 21 | error InvalidServiceFee(uint256 fee); 22 | error UnpaidClaim(address account, uint256 amount); 23 | error MintInvalidSignature(); 24 | error MintInvalidAmount(uint256 amount); 25 | error MintExceedLimit(uint256 amount); 26 | error MintDisallowed(); 27 | error InvalidStartTime(uint256 startTime); 28 | 29 | uint32 public immutable serviceFeePoints; 30 | address public immutable service; 31 | address public immutable verifier; 32 | uint256 public startTime; 33 | uint256 public mintPrice; 34 | uint256 public profileMintLimit; 35 | bool public activated; 36 | mapping(address => uint256) private _balances; 37 | 38 | modifier whenActivate() { 39 | if (!activated || block.timestamp < startTime) { 40 | revert Inactive(); 41 | } 42 | _; 43 | } 44 | 45 | constructor(address service_, address verifier_, uint32 serviceFeePoints_) { 46 | if (!Points.isValid(serviceFeePoints_)) { 47 | revert InvalidServiceFee(serviceFeePoints_); 48 | } 49 | if (service_ == address(0) || verifier_ == address(0)) { 50 | revert ZeroAddress(); 51 | } 52 | activated = false; 53 | service = service_; 54 | verifier = verifier_; 55 | serviceFeePoints = serviceFeePoints_; 56 | } 57 | 58 | function allowlist() external view returns (bytes32 aRoot) { 59 | aRoot = super.root(); 60 | } 61 | 62 | function activate() external onlyOwner { 63 | _activate(); 64 | } 65 | 66 | function _activate() internal { 67 | if (!activated) { 68 | activated = true; 69 | emit Activated(); 70 | } 71 | } 72 | 73 | function deactivate() external onlyOwner { 74 | if (activated) { 75 | activated = false; 76 | emit Deactivated(); 77 | } 78 | } 79 | 80 | function configure(uint256 startTime_, uint256 mintPrice_, uint256 profileMintLimit_, bytes32 allowlistRoot_) 81 | external 82 | onlyOwner 83 | { 84 | if (startTime_ < block.timestamp) { 85 | revert InvalidStartTime(startTime_); 86 | } 87 | if (profileMintLimit_ == 0) { 88 | revert ProfileMintZeroLimit(); 89 | } 90 | startTime = startTime_; 91 | mintPrice = mintPrice_; 92 | profileMintLimit = profileMintLimit_; 93 | _setRoot(allowlistRoot_); 94 | emit ConfigurationChanged(startTime_, mintPrice_, profileMintLimit_, allowlistRoot_); 95 | } 96 | 97 | function mint(address recipient, uint256 amount, uint8 v, bytes32 r, bytes32 s) 98 | external 99 | payable 100 | whenActivate 101 | nonReentrant 102 | { 103 | if (_hasRoot()) { 104 | revert MintDisallowed(); 105 | } 106 | _mintSigned(recipient, amount, v, r, s); 107 | } 108 | 109 | function mintAllowlist( 110 | bytes32[] calldata proof, 111 | uint256 index, 112 | address recipient, 113 | uint256 amount, 114 | uint8 v, 115 | bytes32 r, 116 | bytes32 s 117 | ) external payable whenActivate nonReentrant { 118 | _claim(proof, index, abi.encode(msg.sender)); 119 | _mintSigned(recipient, amount, v, r, s); 120 | } 121 | 122 | function _mintSigned(address recipient, uint256 amount, uint8 v, bytes32 r, bytes32 s) private { 123 | bytes32 hash = keccak256(abi.encodePacked(address(this), block.chainid, recipient, amount, msg.value)); 124 | if (ECDSA.recover(hash, v, r, s) != verifier) { 125 | revert MintInvalidSignature(); 126 | } 127 | uint256 newBalance = balanceOf(recipient) + amount; 128 | if (newBalance > profileMintLimit) { 129 | revert MintExceedLimit(newBalance); 130 | } 131 | uint256 totalPrice = amount * mintPrice; 132 | if (msg.value != totalPrice) { 133 | revert MintInvalidAmount(msg.value); 134 | } 135 | uint256 serviceFeeAmount = Points.realize(totalPrice, serviceFeePoints); 136 | _balances[owner()] += totalPrice - serviceFeeAmount; 137 | _balances[service] += serviceFeeAmount; 138 | _doMint(recipient, amount, totalPrice); 139 | } 140 | 141 | function claimBalanceOf(address account) external view returns (uint256) { 142 | return _balances[account]; 143 | } 144 | 145 | function claim(address beneficiary, uint256 amount) external nonReentrant { 146 | if (beneficiary == address(0)) { 147 | revert ZeroAddress(); 148 | } 149 | if (amount == 0) { 150 | revert ZeroAmount(); 151 | } 152 | uint256 balance = _balances[msg.sender]; 153 | if (balance < amount) { 154 | revert ClaimInvalidAmount(amount); 155 | } 156 | _balances[msg.sender] -= amount; 157 | (bool success,) = beneficiary.call{value: amount}(""); 158 | if (!success) { 159 | revert UnpaidClaim(beneficiary, amount); 160 | } 161 | emit Claimed(msg.sender, beneficiary, amount); 162 | } 163 | 164 | function balanceOf(address tokenOwner) public view virtual returns (uint256); 165 | 166 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal virtual; 167 | } 168 | -------------------------------------------------------------------------------- /src/drops/DropsLightAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; 5 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 6 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import {Points} from "../common/Points.sol"; 8 | 9 | abstract contract DropsLightAsset is OwnableUnset, ReentrancyGuard { 10 | event Claimed(address indexed account, address indexed recipient, uint256 amount); 11 | 12 | error ZeroAddress(); 13 | error ZeroAmount(); 14 | error ClaimInvalidAmount(uint256 amount); 15 | error InvalidServiceFee(uint256 fee); 16 | error UnpaidClaim(address account, uint256 amount); 17 | error MintInvalidSignature(); 18 | error MintInvalidAmount(uint256 amount); 19 | error MintExceedLimit(uint256 amount); 20 | 21 | address public immutable beneficiary; 22 | uint32 public immutable serviceFeePoints; 23 | address public immutable service; 24 | address public immutable verifier; 25 | mapping(address => uint256) private _balances; 26 | mapping(address => uint256) private _mintNonce; 27 | 28 | constructor(address beneficiary_, address service_, address verifier_, uint32 serviceFeePoints_) { 29 | if (!Points.isValid(serviceFeePoints_)) { 30 | revert InvalidServiceFee(serviceFeePoints_); 31 | } 32 | if (service_ == address(0) || verifier_ == address(0)) { 33 | revert ZeroAddress(); 34 | } 35 | beneficiary = beneficiary_; 36 | service = service_; 37 | verifier = verifier_; 38 | serviceFeePoints = serviceFeePoints_; 39 | } 40 | 41 | function mintNonceOf(address recipient) external view returns (uint256) { 42 | return _mintNonce[recipient]; 43 | } 44 | 45 | function _useMintNonce(address recipient) internal returns (uint256) { 46 | return _mintNonce[recipient]++; 47 | } 48 | 49 | function mint(address recipient, uint256 amount, uint8 v, bytes32 r, bytes32 s) external payable nonReentrant { 50 | uint256 totalPrice = msg.value; 51 | bytes32 hash = keccak256( 52 | abi.encodePacked(address(this), block.chainid, recipient, _useMintNonce(recipient), amount, totalPrice) 53 | ); 54 | if (ECDSA.recover(hash, v, r, s) != verifier) { 55 | revert MintInvalidSignature(); 56 | } 57 | uint256 serviceFeeAmount = Points.realize(totalPrice, serviceFeePoints); 58 | _balances[beneficiary] += totalPrice - serviceFeeAmount; 59 | _balances[service] += serviceFeeAmount; 60 | _doMint(recipient, amount, totalPrice); 61 | } 62 | 63 | function claimBalanceOf(address account) external view returns (uint256) { 64 | return _balances[account]; 65 | } 66 | 67 | function claim(address recipient, uint256 amount) external nonReentrant { 68 | if (recipient == address(0)) { 69 | revert ZeroAddress(); 70 | } 71 | if (amount == 0) { 72 | revert ZeroAmount(); 73 | } 74 | uint256 balance = _balances[msg.sender]; 75 | if (balance < amount) { 76 | revert ClaimInvalidAmount(amount); 77 | } 78 | _balances[msg.sender] -= amount; 79 | (bool success,) = recipient.call{value: amount}(""); 80 | if (!success) { 81 | revert UnpaidClaim(recipient, amount); 82 | } 83 | emit Claimed(msg.sender, recipient, amount); 84 | } 85 | 86 | function balanceOf(address tokenOwner) public view virtual returns (uint256); 87 | 88 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal virtual; 89 | } 90 | -------------------------------------------------------------------------------- /src/drops/LSP7DropsDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import { 5 | LSP7DigitalAsset, 6 | LSP7DigitalAssetCore 7 | } from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol"; 8 | import {LSP7CappedSupply} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol"; 9 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 10 | import {DropsDigitalAsset} from "./DropsDigitalAsset.sol"; 11 | 12 | contract LSP7DropsDigitalAsset is LSP7CappedSupply, DropsDigitalAsset { 13 | event Minted(address indexed recipient, uint256 amount, uint256 totalPrice); 14 | 15 | constructor( 16 | string memory name_, 17 | string memory symbol_, 18 | address newOwner_, 19 | address service_, 20 | address verifier_, 21 | uint256 tokenSupplyCap_, 22 | uint32 serviceFeePoints_ 23 | ) 24 | LSP7DigitalAsset(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, true) 25 | LSP7CappedSupply(tokenSupplyCap_) 26 | DropsDigitalAsset(service_, verifier_, serviceFeePoints_) 27 | {} 28 | 29 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal override { 30 | emit Minted(recipient, amount, totalPrice); 31 | _mint(recipient, amount, false, ""); 32 | } 33 | 34 | function balanceOf(address tokenOwner) 35 | public 36 | view 37 | virtual 38 | override(LSP7DigitalAssetCore, DropsDigitalAsset) 39 | returns (uint256) 40 | { 41 | return super.balanceOf(tokenOwner); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/drops/LSP7DropsLightAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import { 5 | LSP7DigitalAsset, 6 | LSP7DigitalAssetCore 7 | } from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol"; 8 | import {LSP7CappedSupply} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/extensions/LSP7CappedSupply.sol"; 9 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 10 | import {DropsLightAsset} from "./DropsLightAsset.sol"; 11 | 12 | contract LSP7DropsLightAsset is LSP7CappedSupply, DropsLightAsset { 13 | event Minted(address indexed recipient, uint256 amount, uint256 totalPrice); 14 | 15 | constructor( 16 | string memory name_, 17 | string memory symbol_, 18 | address newOwner_, 19 | address beneficiary_, 20 | address service_, 21 | address verifier_, 22 | uint256 tokenSupplyCap_, 23 | uint32 serviceFeePoints_ 24 | ) 25 | LSP7DigitalAsset(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, true) 26 | LSP7CappedSupply(tokenSupplyCap_) 27 | DropsLightAsset(beneficiary_, service_, verifier_, serviceFeePoints_) 28 | {} 29 | 30 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal override { 31 | emit Minted(recipient, amount, totalPrice); 32 | _mint(recipient, amount, true, ""); 33 | } 34 | 35 | function balanceOf(address tokenOwner) 36 | public 37 | view 38 | virtual 39 | override(LSP7DigitalAssetCore, DropsLightAsset) 40 | returns (uint256) 41 | { 42 | return super.balanceOf(tokenOwner); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/drops/LSP8DropsDigitalAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import { 5 | LSP8IdentifiableDigitalAsset, 6 | LSP8IdentifiableDigitalAssetCore 7 | } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; 8 | import {LSP8CappedSupply} from 9 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol"; 10 | import {LSP8Enumerable} from 11 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol"; 12 | import {_LSP8_TOKENID_FORMAT_NUMBER} from 13 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; 14 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 15 | import {DropsDigitalAsset} from "./DropsDigitalAsset.sol"; 16 | 17 | contract LSP8DropsDigitalAsset is LSP8CappedSupply, LSP8Enumerable, DropsDigitalAsset { 18 | event Minted(address indexed recipient, bytes32[] tokenIds, uint256 totalPrice); 19 | event DefaultTokenDataChanged(bytes defaultTokenData); 20 | 21 | bytes32 private constant _LSP8_TOKEN_METADATA_BASE_URI_KEY = 22 | 0x1a7628600c3bac7101f53697f48df381ddc36b9015e7d7c9c5633d1252aa2843; 23 | 24 | bytes32 private constant _LSP4_METADATA_KEY = 0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e; 25 | 26 | bytes public defaultTokenUri; 27 | uint256 private _totalMintedTokens; 28 | 29 | constructor( 30 | string memory name_, 31 | string memory symbol_, 32 | address newOwner_, 33 | address service_, 34 | address verifier_, 35 | uint256 tokenSupplyCap_, 36 | uint32 serviceFeePoints_ 37 | ) 38 | LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, _LSP8_TOKENID_FORMAT_NUMBER) 39 | LSP8CappedSupply(tokenSupplyCap_) 40 | DropsDigitalAsset(service_, verifier_, serviceFeePoints_) 41 | { 42 | // noop 43 | } 44 | 45 | function setDefaultTokenUri(bytes calldata newTokenUri) external onlyOwner { 46 | defaultTokenUri = newTokenUri; 47 | emit DefaultTokenDataChanged(newTokenUri); 48 | } 49 | 50 | function _getDataForTokenId(bytes32 tokenId, bytes32 dataKey) internal view override returns (bytes memory) { 51 | bytes memory result = super._getDataForTokenId(tokenId, dataKey); 52 | if (dataKey == _LSP4_METADATA_KEY && result.length == 0) { 53 | bytes memory baseUri = super._getData(_LSP8_TOKEN_METADATA_BASE_URI_KEY); 54 | if (baseUri.length == 0) { 55 | return defaultTokenUri; 56 | } 57 | } 58 | return result; 59 | } 60 | 61 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal override { 62 | // allocate tokens 63 | bytes32[] memory tokenIds = new bytes32[](amount); 64 | uint256 firstTokenId = _totalMintedTokens + 1; 65 | _totalMintedTokens += amount; 66 | for (uint256 i = 0; i < amount; i++) { 67 | tokenIds[i] = bytes32(firstTokenId + i); 68 | } 69 | emit Minted(recipient, tokenIds, totalPrice); 70 | // mint tokens 71 | for (uint256 i = 0; i < amount; i++) { 72 | _mint(recipient, tokenIds[i], false, ""); 73 | } 74 | } 75 | 76 | function balanceOf(address tokenOwner) 77 | public 78 | view 79 | virtual 80 | override(LSP8IdentifiableDigitalAssetCore, DropsDigitalAsset) 81 | returns (uint256) 82 | { 83 | return super.balanceOf(tokenOwner); 84 | } 85 | 86 | function _mint(address to, bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data) 87 | internal 88 | virtual 89 | override(LSP8IdentifiableDigitalAssetCore, LSP8CappedSupply) 90 | { 91 | super._mint(to, tokenId, allowNonLSP1Recipient, data); 92 | } 93 | 94 | function _beforeTokenTransfer(address from, address to, bytes32 tokenId, bytes memory data) 95 | internal 96 | virtual 97 | override(LSP8IdentifiableDigitalAssetCore, LSP8Enumerable) 98 | { 99 | super._beforeTokenTransfer(from, to, tokenId, data); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/drops/LSP8DropsLightAsset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import { 5 | LSP8IdentifiableDigitalAsset, 6 | LSP8IdentifiableDigitalAssetCore 7 | } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; 8 | import {LSP8Enumerable} from 9 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol"; 10 | import {LSP8CappedSupply} from 11 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CappedSupply.sol"; 12 | import { 13 | _LSP8_TOKEN_METADATA_BASE_URI, 14 | _LSP8_TOKENID_FORMAT_NUMBER 15 | } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; 16 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 17 | import {LSP8CompatibleERC721} from "../common/LSP8CompatibleERC721.sol"; 18 | import {LSP8CompatibleERC721Enumerable} from "../common/LSP8CompatibleERC721Enumerable.sol"; 19 | import {DropsLightAsset} from "./DropsLightAsset.sol"; 20 | 21 | contract LSP8DropsLightAsset is 22 | LSP8CompatibleERC721, 23 | LSP8CompatibleERC721Enumerable, 24 | LSP8CappedSupply, 25 | DropsLightAsset 26 | { 27 | event Minted(address indexed recipient, bytes32[] tokenIds, uint256 totalPrice); 28 | event DefaultTokenDataChanged(bytes defaultTokenData); 29 | 30 | bytes32 private constant _LSP4_METADATA_KEY = 0x9afb95cacc9f95858ec44aa8c3b685511002e30ae54415823f406128b85b238e; 31 | 32 | bytes public defaultTokenUri; 33 | uint256 private _totalMintedTokens; 34 | 35 | constructor( 36 | string memory name_, 37 | string memory symbol_, 38 | address newOwner_, 39 | address beneficiary_, 40 | address service_, 41 | address verifier_, 42 | uint256 tokenSupplyCap_, 43 | uint32 serviceFeePoints_ 44 | ) 45 | LSP8CompatibleERC721(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, _LSP8_TOKENID_FORMAT_NUMBER) 46 | LSP8CappedSupply(tokenSupplyCap_) 47 | DropsLightAsset(beneficiary_, service_, verifier_, serviceFeePoints_) 48 | {} 49 | 50 | function setDefaultTokenUri(bytes calldata newTokenUri) external onlyOwner { 51 | defaultTokenUri = newTokenUri; 52 | emit DefaultTokenDataChanged(newTokenUri); 53 | } 54 | 55 | function _getDataForTokenId(bytes32 tokenId, bytes32 dataKey) internal view override returns (bytes memory) { 56 | bytes memory result = super._getDataForTokenId(tokenId, dataKey); 57 | if (dataKey == _LSP4_METADATA_KEY && result.length == 0) { 58 | bytes memory baseUri = super._getData(_LSP8_TOKEN_METADATA_BASE_URI); 59 | if (baseUri.length == 0) { 60 | return defaultTokenUri; 61 | } 62 | } 63 | return result; 64 | } 65 | 66 | function _doMint(address recipient, uint256 amount, uint256 totalPrice) internal override { 67 | // allocate tokens 68 | bytes32[] memory tokenIds = new bytes32[](amount); 69 | uint256 firstTokenId = _totalMintedTokens + 1; 70 | _totalMintedTokens += amount; 71 | for (uint256 i = 0; i < amount; i++) { 72 | tokenIds[i] = bytes32(firstTokenId + i); 73 | } 74 | emit Minted(recipient, tokenIds, totalPrice); 75 | // mint tokens 76 | for (uint256 i = 0; i < amount; i++) { 77 | _mint(recipient, tokenIds[i], true, ""); 78 | } 79 | } 80 | 81 | function supportsInterface(bytes4 interfaceId) 82 | public 83 | view 84 | virtual 85 | override(LSP8CompatibleERC721Enumerable, LSP8CompatibleERC721, LSP8IdentifiableDigitalAsset) 86 | returns (bool) 87 | { 88 | return super.supportsInterface(interfaceId); 89 | } 90 | 91 | function totalSupply() 92 | public 93 | view 94 | virtual 95 | override(LSP8CompatibleERC721Enumerable, LSP8IdentifiableDigitalAssetCore) 96 | returns (uint256) 97 | { 98 | return super.totalSupply(); 99 | } 100 | 101 | function balanceOf(address tokenOwner) 102 | public 103 | view 104 | virtual 105 | override(LSP8CompatibleERC721Enumerable, LSP8CompatibleERC721, LSP8IdentifiableDigitalAssetCore, DropsLightAsset) 106 | returns (uint256) 107 | { 108 | return super.balanceOf(tokenOwner); 109 | } 110 | 111 | function _mint(address to, bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data) 112 | internal 113 | virtual 114 | override(LSP8CappedSupply, LSP8CompatibleERC721, LSP8IdentifiableDigitalAssetCore) 115 | { 116 | super._mint(to, tokenId, allowNonLSP1Recipient, data); 117 | } 118 | 119 | function _beforeTokenTransfer(address from, address to, bytes32 tokenId, bytes memory data) 120 | internal 121 | virtual 122 | override(LSP8Enumerable, LSP8IdentifiableDigitalAssetCore) 123 | { 124 | super._beforeTokenTransfer(from, to, tokenId, data); 125 | } 126 | 127 | function authorizeOperator(address operator, bytes32 tokenId, bytes memory operatorNotificationData) 128 | public 129 | virtual 130 | override(LSP8CompatibleERC721, LSP8IdentifiableDigitalAssetCore) 131 | { 132 | super.authorizeOperator(operator, tokenId, operatorNotificationData); 133 | } 134 | 135 | function _transfer(address from, address to, bytes32 tokenId, bool force, bytes memory data) 136 | internal 137 | virtual 138 | override(LSP8CompatibleERC721, LSP8IdentifiableDigitalAssetCore) 139 | { 140 | super._transfer(from, to, tokenId, force, data); 141 | } 142 | 143 | function _burn(bytes32 tokenId, bytes memory data) 144 | internal 145 | virtual 146 | override(LSP8CompatibleERC721, LSP8IdentifiableDigitalAssetCore) 147 | { 148 | super._burn(tokenId, data); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/marketplace/IParticipant.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | interface IParticipant { 5 | /// Calculates a discount points for a fee on a marketplace. 6 | /// @param profile a universal profile to calculate a discount for 7 | function feeDiscountFor(address profile) external view returns (uint32); 8 | } 9 | -------------------------------------------------------------------------------- /src/marketplace/Participant.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity =0.8.22; 3 | 4 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 5 | import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 6 | import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 7 | import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; 8 | import {ICollectorIdentifiableDigitalAsset} from "../assets/lsp8/ICollectorIdentifiableDigitalAsset.sol"; 9 | import {IParticipant} from "./IParticipant.sol"; 10 | 11 | uint32 constant GENESIS_DISCOUNT = 20_000; 12 | uint32 constant COLLECTOR_TIER_0_DISCOUNT = 35_000; 13 | uint32 constant COLLECTOR_TIER_1_DISCOUNT = 50_000; 14 | uint32 constant COLLECTOR_TIER_2_DISCOUNT = 75_000; 15 | uint32 constant COLLECTOR_TIER_3_DISCOUNT = 90_000; 16 | 17 | contract Participant is IParticipant, OwnableUnset, PausableUpgradeable { 18 | event AssetFeeDiscountChanged(address indexed asset, uint32 previousDiscountPoints, uint32 newDiscountPoints); 19 | event CollectorAssetChanged(address indexed previousCollectorAsset, address indexed newCollectorAsset); 20 | event GenesisAssetChanged(address indexed previousGenesisAsset, address indexed newGenesisAsset); 21 | 22 | ILSP7DigitalAsset public genesisAsset; 23 | ICollectorIdentifiableDigitalAsset public collectorAsset; 24 | 25 | constructor() { 26 | _disableInitializers(); 27 | } 28 | 29 | function initialize(address owner_) external initializer { 30 | __Pausable_init(); 31 | _setOwner(owner_); 32 | } 33 | 34 | function pause() external onlyOwner { 35 | _pause(); 36 | } 37 | 38 | function unpause() external onlyOwner { 39 | _unpause(); 40 | } 41 | 42 | function setCollectorAsset(ICollectorIdentifiableDigitalAsset collectorAsset_) external onlyOwner { 43 | if (address(collectorAsset) != address(collectorAsset_)) { 44 | emit CollectorAssetChanged(address(collectorAsset), address(collectorAsset_)); 45 | collectorAsset = collectorAsset_; 46 | } 47 | } 48 | 49 | function setGenesisAsset(ILSP7DigitalAsset genesisAsset_) external onlyOwner { 50 | if (address(genesisAsset) != address(genesisAsset_)) { 51 | emit GenesisAssetChanged(address(genesisAsset), address(genesisAsset_)); 52 | genesisAsset = genesisAsset_; 53 | } 54 | } 55 | 56 | function feeDiscountFor(address profile) external view override whenNotPaused returns (uint32) { 57 | uint32 maxDiscount = 0; 58 | if ( 59 | (address(genesisAsset) != address(0)) && (genesisAsset.balanceOf(profile) != 0) 60 | && (maxDiscount < GENESIS_DISCOUNT) 61 | ) { 62 | maxDiscount = GENESIS_DISCOUNT; 63 | } 64 | if (address(collectorAsset) != address(0)) { 65 | bytes32[] memory tokenIds = collectorAsset.tokenIdsOf(profile); 66 | for (uint256 i = 0; i < tokenIds.length; i++) { 67 | uint8 tier = collectorAsset.tokenTierOf(tokenIds[i]); 68 | uint32 discount = 0; 69 | if (tier == 0) { 70 | discount = COLLECTOR_TIER_0_DISCOUNT; 71 | } else if (tier == 1) { 72 | discount = COLLECTOR_TIER_1_DISCOUNT; 73 | } else if (tier == 2) { 74 | discount = COLLECTOR_TIER_2_DISCOUNT; 75 | } else if (tier == 3) { 76 | discount = COLLECTOR_TIER_3_DISCOUNT; 77 | } 78 | if (maxDiscount < discount) { 79 | maxDiscount = discount; 80 | } 81 | } 82 | } 83 | return maxDiscount; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/marketplace/common/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 5 | import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 6 | import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 7 | import {Withdrawable} from "../../common/Withdrawable.sol"; 8 | import {Points} from "../../common/Points.sol"; 9 | import {Royalties, RoyaltiesInfo} from "../../common/Royalties.sol"; 10 | import {IParticipant} from "../IParticipant.sol"; 11 | 12 | abstract contract Base is OwnableUnset, ReentrancyGuardUpgradeable, PausableUpgradeable, Withdrawable { 13 | event FeePointsChanged(uint32 oldPoints, uint32 newPoints); 14 | event RoyaltiesThresholdPointsChanged(uint32 oldPoints, uint32 newPoints); 15 | event ParticipantChanged(address indexed oldParticipant, address indexed newParticipant); 16 | 17 | error RoyaltiesExceedThreshold(uint32 royaltiesThresholdPoints, uint256 totalPrice, uint256 totalRoyalties); 18 | 19 | uint32 public feePoints; 20 | uint32 public royaltiesThresholdPoints; 21 | IParticipant public participant; 22 | 23 | function _initialize(address newOwner_, address beneficiary_, IParticipant participant_) 24 | internal 25 | onlyInitializing 26 | { 27 | require(address(participant_) != address(0)); 28 | __ReentrancyGuard_init(); 29 | __Pausable_init(); 30 | _setOwner(newOwner_); 31 | _setBeneficiary(beneficiary_); 32 | participant = participant_; 33 | } 34 | 35 | function pause() external onlyOwner { 36 | _pause(); 37 | } 38 | 39 | function unpause() external onlyOwner { 40 | _unpause(); 41 | } 42 | 43 | function setParticipant(IParticipant participant_) external onlyOwner { 44 | if (address(participant) != address(participant_)) { 45 | emit ParticipantChanged(address(participant), address(participant_)); 46 | participant = participant_; 47 | } 48 | } 49 | 50 | function setFeePoints(uint32 newFeePoints) external onlyOwner { 51 | require(Points.isValid(newFeePoints)); 52 | if (feePoints != newFeePoints) { 53 | uint32 old = feePoints; 54 | feePoints = newFeePoints; 55 | emit FeePointsChanged(old, newFeePoints); 56 | } 57 | } 58 | 59 | function setRoyaltiesThresholdPoints(uint32 newRoyaltiesThresholdPoints) external onlyOwner { 60 | require(Points.isValid(newRoyaltiesThresholdPoints)); 61 | if (royaltiesThresholdPoints != newRoyaltiesThresholdPoints) { 62 | uint32 old = royaltiesThresholdPoints; 63 | royaltiesThresholdPoints = newRoyaltiesThresholdPoints; 64 | emit RoyaltiesThresholdPointsChanged(old, newRoyaltiesThresholdPoints); 65 | } 66 | } 67 | 68 | function _calculateFeeWithDiscount(address seller, uint256 totalPrice) internal view returns (uint256) { 69 | uint32 discountPoints = Points.multiply(feePoints, participant.feeDiscountFor(seller)); 70 | assert(discountPoints <= feePoints); 71 | return Points.realize(totalPrice, feePoints - discountPoints); 72 | } 73 | 74 | function _calculateRoyalties(address asset, uint256 totalPrice) 75 | internal 76 | view 77 | returns (uint256 totalAmount, address[] memory recipients, uint256[] memory amounts) 78 | { 79 | totalAmount = 0; 80 | RoyaltiesInfo[] memory royalties = Royalties.royalties(asset); 81 | recipients = new address[](royalties.length); 82 | amounts = new uint256[](royalties.length); 83 | uint256 count = royalties.length; 84 | for (uint256 i = 0; i < count; i++) { 85 | assert(Points.isValid(royalties[i].points)); 86 | uint256 amount = Points.realize(totalPrice, royalties[i].points); 87 | recipients[i] = royalties[i].recipient; 88 | amounts[i] = amount; 89 | totalAmount += amount; 90 | } 91 | if ((royaltiesThresholdPoints != 0) && (totalAmount > Points.realize(totalPrice, royaltiesThresholdPoints))) { 92 | revert RoyaltiesExceedThreshold(royaltiesThresholdPoints, totalPrice, totalAmount); 93 | } 94 | } 95 | 96 | // reserved space (100 slots) 97 | uint256[99] private _base_reserved; 98 | } 99 | -------------------------------------------------------------------------------- /src/marketplace/common/Module.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {OwnableUnset} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol"; 5 | import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 6 | import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 7 | 8 | bytes32 constant MARKETPLACE_ROLE = keccak256("MARKETPLACE_ROLE"); 9 | bytes32 constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); 10 | 11 | abstract contract Module is OwnableUnset, ReentrancyGuardUpgradeable, PausableUpgradeable { 12 | error IllegalAccess(address account, bytes32 role); 13 | error InvalidAddress(address account); 14 | 15 | // role -> account -> granted (yes/no) 16 | mapping(bytes32 => mapping(address => bool)) private _roles; 17 | 18 | modifier onlyMarketplace() { 19 | if (!hasRole(msg.sender, MARKETPLACE_ROLE)) { 20 | revert IllegalAccess(msg.sender, MARKETPLACE_ROLE); 21 | } 22 | _; 23 | } 24 | 25 | modifier onlyOperator() { 26 | if (!hasRole(msg.sender, OPERATOR_ROLE)) { 27 | revert IllegalAccess(msg.sender, OPERATOR_ROLE); 28 | } 29 | _; 30 | } 31 | 32 | function _initialize(address newOwner_) internal onlyInitializing { 33 | __ReentrancyGuard_init(); 34 | __Pausable_init(); 35 | _setOwner(newOwner_); 36 | } 37 | 38 | function pause() external onlyOwner { 39 | _pause(); 40 | } 41 | 42 | function unpause() external onlyOwner { 43 | _unpause(); 44 | } 45 | 46 | function hasRole(address account, bytes32 role) public view returns (bool) { 47 | return _roles[role][account]; 48 | } 49 | 50 | function grantRole(address account, bytes32 role) external onlyOwner { 51 | _grantRole(account, role); 52 | } 53 | 54 | function revokeRole(address account, bytes32 role) external onlyOwner { 55 | _revokeRole(account, role); 56 | } 57 | 58 | function _grantRole(address account, bytes32 role) internal { 59 | _roles[role][account] = true; 60 | } 61 | 62 | function _revokeRole(address account, bytes32 role) internal { 63 | _roles[role][account] = false; 64 | } 65 | 66 | // reserved space (20 slots) 67 | uint256[19] private _module_reserved; 68 | } 69 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/ILSP7Listings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP7Listing { 5 | address seller; 6 | address asset; 7 | address owner; 8 | uint256 itemCount; 9 | uint256 itemPrice; 10 | uint256 startTime; 11 | uint256 endTime; 12 | } 13 | 14 | interface ILSP7Listings { 15 | /// an asset has been listed with id by an owner 16 | event Listed( 17 | uint256 indexed id, 18 | address indexed asset, 19 | address seller, 20 | address indexed owner, 21 | uint256 itemCount, 22 | uint256 itemPrice, 23 | uint256 startTime, 24 | uint256 endTime 25 | ); 26 | /// a listing for asset has been updated with id 27 | event Updated( 28 | uint256 indexed id, 29 | address indexed asset, 30 | uint256 itemCount, 31 | uint256 itemPrice, 32 | uint256 startTime, 33 | uint256 endTime 34 | ); 35 | /// a listing has been explicitly delisted 36 | event Delisted(uint256 indexed id, address indexed asset); 37 | /// a listing has been partially deducted (e.g. sold) 38 | event Deducted(uint256 indexed id, address indexed asset, uint256 itemCount); 39 | /// a listing has been unlisted due deduction of all available items 40 | event Unlisted(uint256 indexed id, address indexed asset); 41 | 42 | /// total number of listings ever listed 43 | function totalListings() external view returns (uint256); 44 | 45 | /// confirms a listing with id is listed 46 | /// @param id listing id 47 | function isListed(uint256 id) external view returns (bool); 48 | 49 | /// confirms a listing with id is active 50 | /// @param id listing id 51 | function isActiveListing(uint256 id) external view returns (bool); 52 | 53 | /// retrieves a listing by an id or reverts if not listed 54 | /// @param id listing id 55 | function getListing(uint256 id) external view returns (LSP7Listing memory); 56 | 57 | /// lists new asset 58 | /// @param asset asset to list 59 | /// @param owner owner of asset 60 | /// @param itemCount number of items to list 61 | /// @param itemPrice price per item 62 | /// @param startTime time in seconds to make listing available 63 | /// @param secondsUntilEndTime seconds for how long listing is available or zero to make indefinite 64 | /// @return listingId id of a listed item 65 | function list( 66 | address asset, 67 | address owner, 68 | uint256 itemCount, 69 | uint256 itemPrice, 70 | uint256 startTime, 71 | uint256 secondsUntilEndTime 72 | ) external returns (uint256); 73 | 74 | /// updates listed asset 75 | /// @param id listing id 76 | /// @param itemCount number of items to list 77 | /// @param itemPrice price per item 78 | /// @param startTime time in seconds to make listing available 79 | /// @param secondsUntilEndTime seconds for how long listing is available or zero to make it indefinite 80 | function update(uint256 id, uint256 itemCount, uint256 itemPrice, uint256 startTime, uint256 secondsUntilEndTime) 81 | external; 82 | 83 | /// delists an asset by a seller 84 | function delist(uint256 id) external; 85 | 86 | /// deducts a number of items from a listing. When no more items are remained, unlists the listing. 87 | function deduct(uint256 id, uint256 itemCount) external; 88 | } 89 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/ILSP7Offers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP7Offer { 5 | uint256 itemCount; 6 | uint256 totalPrice; 7 | uint256 expirationTime; 8 | } 9 | 10 | interface ILSP7Offers { 11 | /// an offer was made for a listing by a buyer 12 | event Placed( 13 | uint256 indexed listingId, address indexed buyer, uint256 itemCount, uint256 totalPrice, uint256 expirationTime 14 | ); 15 | /// a buyer canceled an offer 16 | event Canceled(uint256 indexed listingId, address indexed buyer, uint256 itemCount, uint256 totalPrice); 17 | /// a seller accepted an offer 18 | event Accepted(uint256 indexed listingId, address indexed buyer, uint256 itemCount, uint256 totalPrice); 19 | 20 | /// confirms an offer has been placed by a buyer 21 | /// @param listingId listing id 22 | /// @param buyer buyer 23 | function isPlacedOffer(uint256 listingId, address buyer) external view returns (bool); 24 | 25 | /// confirms a listing with id is placed and wasn't canceled 26 | /// @param listingId listing id 27 | /// @param buyer buyer 28 | function isActiveOffer(uint256 listingId, address buyer) external view returns (bool); 29 | 30 | /// retrieves an offer for a listing made by a buyer or reverts if not placed 31 | /// @param listingId listing id 32 | /// @param buyer buyer 33 | function getOffer(uint256 listingId, address buyer) external view returns (LSP7Offer memory); 34 | 35 | /// place an offer with a fixed price, number of items and seconds until the offer is expired. 36 | /// @param listingId listing id 37 | /// @param itemCount number of items 38 | /// @param totalPrice total price 39 | /// @param secondsUntilExpiration time in seconds until offer is expired 40 | function place(uint256 listingId, uint256 itemCount, uint256 totalPrice, uint256 secondsUntilExpiration) 41 | external 42 | payable; 43 | 44 | /// cancel an offer by a buyer being a sender. 45 | /// @param listingId listing id 46 | function cancel(uint256 listingId) external; 47 | 48 | /// accept an offer. 49 | /// @param listingId listing id 50 | /// @param buyer buyer 51 | function accept(uint256 listingId, address buyer) external; 52 | } 53 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/ILSP7Orders.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | // Order for LSP7 asset 5 | struct LSP7Order { 6 | uint256 id; 7 | address asset; 8 | address buyer; 9 | uint256 itemPrice; 10 | uint256 itemCount; 11 | } 12 | 13 | interface ILSP7Orders { 14 | /// an order was made for an asset 15 | event Placed(uint256 id, address indexed asset, address indexed buyer, uint256 itemPrice, uint256 itemCount); 16 | /// a buyer canceled an order 17 | event Canceled(uint256 id, address indexed asset, address indexed buyer, uint256 itemPrice, uint256 itemCount); 18 | /// an order was filled 19 | event Filled( 20 | uint256 id, 21 | address indexed asset, 22 | address indexed seller, 23 | address indexed buyer, 24 | uint256 itemPrice, 25 | uint256 fillCount, 26 | uint256 totalCount 27 | ); 28 | 29 | /// confirms an order has been placed by a buyer 30 | /// @param asset asset address 31 | /// @param buyer buyer 32 | function isPlacedOrderOf(address asset, address buyer) external view returns (bool); 33 | 34 | /// retrieves an order for an asset made by a buyer or reverts if not placed 35 | /// @param asset asset address 36 | /// @param buyer buyer 37 | function orderOf(address asset, address buyer) external view returns (LSP7Order memory); 38 | 39 | /// confirms an order has been placed by a buyer 40 | /// @param id order id 41 | function isPlacedOrder(uint256 id) external view returns (bool); 42 | 43 | /// retrieves an order for an asset reverts if not placed 44 | /// @param id order id 45 | function getOrder(uint256 id) external view returns (LSP7Order memory); 46 | 47 | /// place an offer order with a fixed item price, number of items and seconds until the offer is expired. 48 | /// @param asset asset address 49 | /// @param itemPrice item price 50 | /// @param itemCount number of items 51 | /// @return orderId order id 52 | function place(address asset, uint256 itemPrice, uint256 itemCount) external payable returns (uint256); 53 | 54 | /// cancel an order by a buyer being a sender. 55 | /// @param id order id 56 | function cancel(uint256 id) external; 57 | 58 | /// fill an order. 59 | /// @param id order id 60 | /// @param seller seller 61 | /// @param itemCount number of items 62 | function fill(uint256 id, address seller, uint256 itemCount) external; 63 | } 64 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/LSP7Marketplace.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; 5 | import {Base} from "../common/Base.sol"; 6 | import {IParticipant} from "../IParticipant.sol"; 7 | import {ILSP7Listings, LSP7Listing} from "./ILSP7Listings.sol"; 8 | import {ILSP7Orders, LSP7Order} from "./ILSP7Orders.sol"; 9 | 10 | uint8 constant SALE_KIND_MASK = 0xF; 11 | uint8 constant SALE_KIND_SPOT = 0; 12 | uint8 constant SALE_KIND_OFFER = 1; 13 | uint8 constant SALE_KIND_ORDER = 2; 14 | 15 | contract LSP7Marketplace is Base { 16 | event Sold( 17 | address indexed asset, 18 | address indexed seller, 19 | address indexed buyer, 20 | uint256 itemCount, 21 | uint256 totalPaid, 22 | uint256 totalFee, 23 | uint256 totalRoyalties, 24 | bytes data 25 | ); 26 | event RoyaltiesPaidOut( 27 | address indexed asset, uint256 itemCount, uint256 totalPaid, address indexed recipient, uint256 amount 28 | ); 29 | 30 | error InsufficientFunds(uint256 totalPrice, uint256 totalPaid); 31 | error FeesExceedTotalPaid(uint256 totalPaid, uint256 feesAmount, uint256 royaltiesTotalAmount); 32 | error Unpaid(address account, uint256 amount); 33 | error UnathorizedSeller(address account); 34 | 35 | ILSP7Listings public listings; 36 | ILSP7Orders public orders; 37 | 38 | constructor() { 39 | _disableInitializers(); 40 | } 41 | 42 | function initialize( 43 | address newOwner_, 44 | address beneficiary_, 45 | ILSP7Listings listings_, 46 | ILSP7Orders orders_, 47 | IParticipant participant_ 48 | ) external initializer { 49 | Base._initialize(newOwner_, beneficiary_, participant_); 50 | listings = listings_; 51 | orders = orders_; 52 | } 53 | 54 | function setOrders(ILSP7Orders orders_) external onlyOwner { 55 | require(address(orders_) != address(0)); 56 | orders = orders_; 57 | } 58 | 59 | function buy(uint256 listingId, uint256 itemCount, address recipient) external payable whenNotPaused nonReentrant { 60 | LSP7Listing memory listing = listings.getListing(listingId); 61 | if (listing.itemPrice * itemCount != msg.value) { 62 | revert InsufficientFunds(listing.itemPrice * itemCount, msg.value); 63 | } 64 | listings.deduct(listingId, itemCount); 65 | _executeSale( 66 | listing.asset, itemCount, listing.owner, recipient, msg.value, abi.encodePacked(SALE_KIND_SPOT, listingId) 67 | ); 68 | } 69 | 70 | function fillOrder(uint256 orderId, uint256 itemCount) external whenNotPaused nonReentrant { 71 | address seller = msg.sender; 72 | LSP7Order memory order = orders.getOrder(orderId); 73 | orders.fill(orderId, seller, itemCount); 74 | _executeSale( 75 | order.asset, 76 | itemCount, 77 | seller, 78 | order.buyer, 79 | order.itemPrice * itemCount, 80 | abi.encodePacked(SALE_KIND_ORDER, order.id) 81 | ); 82 | } 83 | 84 | function _executeSale( 85 | address asset, 86 | uint256 itemCount, 87 | address seller, 88 | address buyer, 89 | uint256 totalPaid, 90 | bytes memory data 91 | ) private { 92 | (uint256 royaltiesTotalAmount, address[] memory royaltiesRecipients, uint256[] memory royaltiesAmounts) = 93 | _calculateRoyalties(asset, totalPaid); 94 | uint256 feeAmount = _calculateFeeWithDiscount(seller, totalPaid); 95 | if (feeAmount + royaltiesTotalAmount > totalPaid) { 96 | revert FeesExceedTotalPaid(totalPaid, feeAmount, royaltiesTotalAmount); 97 | } 98 | emit Sold(asset, seller, buyer, itemCount, totalPaid, feeAmount, royaltiesTotalAmount, data); 99 | uint256 royaltiesRecipientsCount = royaltiesRecipients.length; 100 | for (uint256 i = 0; i < royaltiesRecipientsCount; i++) { 101 | if (royaltiesAmounts[i] > 0) { 102 | (bool royaltiesPaid,) = royaltiesRecipients[i].call{value: royaltiesAmounts[i]}(""); 103 | if (!royaltiesPaid) { 104 | revert Unpaid(royaltiesRecipients[i], royaltiesAmounts[i]); 105 | } 106 | emit RoyaltiesPaidOut(asset, itemCount, totalPaid, royaltiesRecipients[i], royaltiesAmounts[i]); 107 | } 108 | } 109 | uint256 sellerAmount = totalPaid - feeAmount - royaltiesTotalAmount; 110 | (bool paid,) = seller.call{value: sellerAmount}(""); 111 | if (!paid) { 112 | revert Unpaid(seller, sellerAmount); 113 | } 114 | ILSP7DigitalAsset(asset).transfer(seller, buyer, itemCount, false, data); 115 | } 116 | 117 | // Deprecated events 118 | event Sale( 119 | uint256 indexed listingId, 120 | address indexed asset, 121 | uint256 itemCount, 122 | address indexed seller, 123 | address buyer, 124 | uint256 totalPaid 125 | ); 126 | event RoyaltiesPaid( 127 | uint256 indexed listingId, address indexed asset, uint256 itemCount, address indexed recipient, uint256 amount 128 | ); 129 | event FeePaid(uint256 indexed listingId, address indexed asset, uint256 itemCount, uint256 amount); 130 | } 131 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/LSP7Offers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; 5 | import {Module} from "../common/Module.sol"; 6 | import {ILSP7Listings, LSP7Listing} from "./ILSP7Listings.sol"; 7 | import {ILSP7Offers, LSP7Offer} from "./ILSP7Offers.sol"; 8 | 9 | contract LSP7Offers is ILSP7Offers, Module { 10 | error NotPlaced(uint256 listingId, address buyer); 11 | error InvalidOfferZeroItems(); 12 | error InvalidOfferDuration(uint256 secondsUntilExpiration); 13 | error InvalidOfferTotalPrice(uint256 totalPrice); 14 | error InactiveListing(uint256 listingId); 15 | error InactiveOffer(uint256 listingId, address buyer); 16 | error Unpaid(uint256 listingId, address buyer, uint256 amount); 17 | error InsufficientListingItemCount(uint256 listingId, uint256 listedCount, uint256 offeredCount); 18 | 19 | ILSP7Listings public listings; 20 | // listing id -> buyer -> offer 21 | mapping(uint256 => mapping(address => LSP7Offer)) private _offers; 22 | 23 | constructor() { 24 | _disableInitializers(); 25 | } 26 | 27 | function initialize(address newOwner_, ILSP7Listings listings_) external initializer { 28 | Module._initialize(newOwner_); 29 | listings = listings_; 30 | } 31 | 32 | function isPlacedOffer(uint256 listingId, address buyer) public view override returns (bool) { 33 | return _offers[listingId][buyer].expirationTime != 0; 34 | } 35 | 36 | function isActiveOffer(uint256 listingId, address buyer) public view override returns (bool) { 37 | return _offers[listingId][buyer].expirationTime > block.timestamp; 38 | } 39 | 40 | function getOffer(uint256 listingId, address buyer) public view override returns (LSP7Offer memory) { 41 | if (!isPlacedOffer(listingId, buyer)) { 42 | revert NotPlaced(listingId, buyer); 43 | } 44 | return _offers[listingId][buyer]; 45 | } 46 | 47 | function place(uint256 listingId, uint256 itemCount, uint256 totalPrice, uint256 secondsUntilExpiration) 48 | external 49 | payable 50 | override 51 | whenNotPaused 52 | nonReentrant 53 | { 54 | if (!listings.isActiveListing(listingId)) { 55 | revert InactiveListing(listingId); 56 | } 57 | if (itemCount == 0) { 58 | revert InvalidOfferZeroItems(); 59 | } 60 | if ((secondsUntilExpiration < 1 hours) || (secondsUntilExpiration > 28 days)) { 61 | revert InvalidOfferDuration(secondsUntilExpiration); 62 | } 63 | LSP7Offer memory lastOffer = _offers[listingId][msg.sender]; 64 | uint256 actualTotalPrice = msg.value + lastOffer.totalPrice; 65 | if (actualTotalPrice != totalPrice) { 66 | revert InvalidOfferTotalPrice(totalPrice); 67 | } 68 | uint256 expirationTime = block.timestamp + secondsUntilExpiration; 69 | _offers[listingId][msg.sender] = 70 | LSP7Offer({itemCount: itemCount, totalPrice: actualTotalPrice, expirationTime: expirationTime}); 71 | emit Placed(listingId, msg.sender, itemCount, actualTotalPrice, expirationTime); 72 | } 73 | 74 | function cancel(uint256 listingId) external override whenNotPaused nonReentrant { 75 | LSP7Offer memory offer = getOffer(listingId, msg.sender); 76 | delete _offers[listingId][msg.sender]; 77 | (bool success,) = msg.sender.call{value: offer.totalPrice}(""); 78 | if (!success) { 79 | revert Unpaid(listingId, msg.sender, offer.totalPrice); 80 | } 81 | emit Canceled(listingId, msg.sender, offer.itemCount, offer.totalPrice); 82 | } 83 | 84 | function accept(uint256 listingId, address buyer) external override whenNotPaused nonReentrant onlyMarketplace { 85 | LSP7Listing memory listing = listings.getListing(listingId); 86 | if (!listings.isActiveListing(listingId)) { 87 | revert InactiveListing(listingId); 88 | } 89 | LSP7Offer memory offer = getOffer(listingId, buyer); 90 | if (!isActiveOffer(listingId, buyer)) { 91 | revert InactiveOffer(listingId, buyer); 92 | } 93 | if (offer.itemCount > listing.itemCount) { 94 | revert InsufficientListingItemCount(listingId, listing.itemCount, offer.itemCount); 95 | } 96 | delete _offers[listingId][buyer]; 97 | (bool success,) = msg.sender.call{value: offer.totalPrice}(""); 98 | if (!success) { 99 | revert Unpaid(listingId, msg.sender, offer.totalPrice); 100 | } 101 | emit Accepted(listingId, buyer, offer.itemCount, offer.totalPrice); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/marketplace/lsp7/LSP7Orders.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Module} from "../common/Module.sol"; 5 | import {ILSP7Orders, LSP7Order} from "./ILSP7Orders.sol"; 6 | 7 | contract LSP7Orders is ILSP7Orders, Module { 8 | error Unpaid(address buyer, uint256 amount); 9 | error NotPlacedOf(address asset, address buyer); 10 | error NotPlaced(uint256 id); 11 | error InvalidItemCount(uint256 itemCount); 12 | error AlreadyPlaced(address asset, address buyer); 13 | error InvalidAmount(uint256 expected, uint256 actual); 14 | error InsufficientItemCount(uint256 orderCount, uint256 offeredCount); 15 | 16 | uint256 public totalOrders; 17 | mapping(address asset => mapping(address buyer => uint256)) private _orderIds; 18 | mapping(uint256 id => LSP7Order) private _orders; 19 | 20 | constructor() { 21 | _disableInitializers(); 22 | } 23 | 24 | function initialize(address newOwner_) external initializer { 25 | Module._initialize(newOwner_); 26 | } 27 | 28 | function isPlacedOrderOf(address asset, address buyer) public view override returns (bool) { 29 | return isPlacedOrder(_orderIds[asset][buyer]); 30 | } 31 | 32 | function orderOf(address asset, address buyer) public view override returns (LSP7Order memory) { 33 | if (!isPlacedOrderOf(asset, buyer)) { 34 | revert NotPlacedOf(asset, buyer); 35 | } 36 | return _orders[_orderIds[asset][buyer]]; 37 | } 38 | 39 | function isPlacedOrder(uint256 id) public view override returns (bool) { 40 | LSP7Order memory order = _orders[id]; 41 | return order.itemCount > 0; 42 | } 43 | 44 | function getOrder(uint256 id) public view override returns (LSP7Order memory) { 45 | if (!isPlacedOrder(id)) { 46 | revert NotPlaced(id); 47 | } 48 | return _orders[id]; 49 | } 50 | 51 | function place(address asset, uint256 itemPrice, uint256 itemCount) 52 | external 53 | payable 54 | override 55 | whenNotPaused 56 | nonReentrant 57 | returns (uint256) 58 | { 59 | if (itemCount == 0) { 60 | revert InvalidItemCount(itemCount); 61 | } 62 | address buyer = msg.sender; 63 | if (isPlacedOrderOf(asset, buyer)) { 64 | revert AlreadyPlaced(asset, buyer); 65 | } 66 | uint256 totalValue = itemPrice * itemCount; 67 | if (msg.value != totalValue) { 68 | revert InvalidAmount(totalValue, msg.value); 69 | } 70 | totalOrders += 1; 71 | uint256 orderId = totalOrders; 72 | _orderIds[asset][buyer] = orderId; 73 | _orders[orderId] = 74 | LSP7Order({id: orderId, asset: asset, buyer: buyer, itemPrice: itemPrice, itemCount: itemCount}); 75 | emit Placed(orderId, asset, buyer, itemPrice, itemCount); 76 | return orderId; 77 | } 78 | 79 | function cancel(uint256 id) external override whenNotPaused nonReentrant { 80 | address buyer = msg.sender; 81 | LSP7Order memory order = getOrder(id); 82 | if (order.buyer != buyer) { 83 | revert NotPlacedOf(order.asset, buyer); 84 | } 85 | delete _orders[id]; 86 | delete _orderIds[order.asset][order.buyer]; 87 | uint256 totalValue = order.itemPrice * order.itemCount; 88 | (bool success,) = order.buyer.call{value: totalValue}(""); 89 | if (!success) { 90 | revert Unpaid(order.buyer, totalValue); 91 | } 92 | emit Canceled(order.id, order.asset, order.buyer, order.itemPrice, order.itemCount); 93 | } 94 | 95 | function fill(uint256 id, address seller, uint256 itemCount) 96 | external 97 | override 98 | whenNotPaused 99 | nonReentrant 100 | onlyMarketplace 101 | { 102 | if (itemCount == 0) { 103 | revert InvalidItemCount(itemCount); 104 | } 105 | LSP7Order memory order = getOrder(id); 106 | if (itemCount > order.itemCount) { 107 | revert InsufficientItemCount(order.itemCount, itemCount); 108 | } 109 | _orders[order.id].itemCount -= itemCount; 110 | uint256 totalValue = order.itemPrice * itemCount; 111 | (bool success,) = msg.sender.call{value: totalValue}(""); 112 | if (!success) { 113 | revert Unpaid(msg.sender, totalValue); 114 | } 115 | emit Filled(order.id, order.asset, seller, order.buyer, order.itemPrice, itemCount, order.itemCount); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/ILSP8Auctions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP8Auction { 5 | address seller; 6 | uint256 startPrice; 7 | uint256 startTime; 8 | uint256 endTime; 9 | } 10 | 11 | struct LSP8Bid { 12 | address buyer; 13 | uint256 totalPaid; 14 | } 15 | 16 | interface ILSP8Auctions { 17 | /// an asset has been auctioned 18 | event Issued( 19 | uint256 indexed listingId, 20 | address indexed seller, 21 | address indexed owner, 22 | bytes32 tokenId, 23 | uint256 startPrice, 24 | uint256 startTime, 25 | uint256 endTime 26 | ); 27 | /// an auction was canceled and an item is unlisted 28 | event Canceled(uint256 indexed listingId, address indexed seller, address indexed owner, bytes32 tokenId); 29 | /// an auction has been settled with a seller accepting a highest bid 30 | event Settled( 31 | uint256 indexed listingId, 32 | address seller, 33 | address indexed owner, 34 | bytes32 tokenId, 35 | address indexed buyer, 36 | uint256 totalPaid 37 | ); 38 | /// a buyer placed new bid 39 | event Offered( 40 | uint256 indexed listingId, 41 | address seller, 42 | address indexed owner, 43 | bytes32 tokenId, 44 | address indexed buyer, 45 | uint256 totalPaid 46 | ); 47 | /// a buyer retracted their bid 48 | event Retracted(uint256 indexed listingId, address indexed buyer, uint256 totalPaid); 49 | 50 | /// confirms an auction is issued 51 | /// @param listingId listing id 52 | function isIssued(uint256 listingId) external view returns (bool); 53 | 54 | /// confirms an auction is active 55 | /// @param listingId listing id 56 | function isActiveAuction(uint256 listingId) external view returns (bool); 57 | 58 | /// retrieves an auction by a listing id or reverts if not issued 59 | /// @param listingId listing id 60 | function getAuction(uint256 listingId) external view returns (LSP8Auction memory); 61 | 62 | /// issues an auction 63 | /// @param asset asset to auction 64 | /// @param tokenId token id to auction 65 | /// @param startPrice starting price or a minimum bid price 66 | /// @param startTime time in seconds to make listing available 67 | /// @param secondsUntilEndTime seconds for how long listing is available or zero to make indefinite 68 | /// @return listingId auction listed id 69 | function issue(address asset, bytes32 tokenId, uint256 startPrice, uint256 startTime, uint256 secondsUntilEndTime) 70 | external 71 | returns (uint256); 72 | 73 | /// cancels an auction 74 | /// @param listingId listing id 75 | function cancel(uint256 listingId) external; 76 | 77 | /// settles an auction by accepting a highest bid 78 | /// @param listingId listing id 79 | function settle(uint256 listingId) external; 80 | 81 | /// retrieves a bid for a listing id made by a buyer or reverts if not offered 82 | /// @param listingId listing id 83 | /// @param buyer buyer 84 | function getBid(uint256 listingId, address buyer) external view returns (LSP8Bid memory); 85 | 86 | /// confirms an auction has at least one bid offered 87 | /// @param listingId listing id 88 | function hasBids(uint256 listingId) external view returns (bool); 89 | 90 | /// retrieves the highest bid for a listing id made or reverts if non offered 91 | /// @param listingId listing id 92 | function getHighestBid(uint256 listingId) external view returns (LSP8Bid memory); 93 | 94 | /// offers a new bid or top offs existing if any 95 | /// @param listingId listing id 96 | function offer(uint256 listingId) external payable; 97 | 98 | /// retract a bid or revert if the bid is the current and auction is active 99 | /// @param listingId listing id 100 | function retract(uint256 listingId) external; 101 | } 102 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/ILSP8Listings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP8Listing { 5 | address seller; 6 | address asset; 7 | address owner; 8 | bytes32 tokenId; 9 | uint256 price; 10 | uint256 startTime; 11 | uint256 endTime; 12 | } 13 | 14 | interface ILSP8Listings { 15 | /// an asset has been listed with id by an owner 16 | event Listed( 17 | uint256 indexed id, 18 | address indexed asset, 19 | address seller, 20 | address indexed owner, 21 | bytes32 tokenId, 22 | uint256 price, 23 | uint256 startTime, 24 | uint256 endTime 25 | ); 26 | /// a listing for asset has been updated with id 27 | event Updated(uint256 indexed id, address indexed asset, uint256 price, uint256 startTime, uint256 endTime); 28 | /// a listing has been explicitly delisted 29 | event Delisted(uint256 indexed id, address indexed asset); 30 | /// a listing has been unlisted due being sold 31 | event Unlisted(uint256 indexed id, address indexed asset); 32 | 33 | /// total number of listings ever listed 34 | function totalListings() external view returns (uint256); 35 | 36 | /// confirms a listing with id is listed 37 | /// @param id listing id 38 | function isListed(uint256 id) external view returns (bool); 39 | 40 | /// confirms a listing with id is active 41 | /// @param id listing id 42 | function isActiveListing(uint256 id) external view returns (bool); 43 | 44 | /// retrieves a listing by an id or reverts if not listed 45 | /// @param id listing id 46 | function getListing(uint256 id) external view returns (LSP8Listing memory); 47 | 48 | /// lists new asset 49 | /// @param asset asset to list 50 | /// @param tokenId token id to list 51 | /// @param price price 52 | /// @param startTime time in seconds to make listing available 53 | /// @param secondsUntilEndTime seconds for how long listing is available or zero to make indefinite 54 | /// @return listingId id of a listed item 55 | function list(address asset, bytes32 tokenId, uint256 price, uint256 startTime, uint256 secondsUntilEndTime) 56 | external 57 | returns (uint256); 58 | 59 | /// updates listed asset 60 | /// @param id listing id 61 | /// @param price price 62 | /// @param startTime time in seconds to make listing available 63 | /// @param secondsUntilEndTime seconds for how long listing is available or zero to make it indefinite 64 | function update(uint256 id, uint256 price, uint256 startTime, uint256 secondsUntilEndTime) external; 65 | 66 | /// delists an asset by a seller 67 | function delist(uint256 id) external; 68 | 69 | /// unlists an asset when sold 70 | function unlist(uint256 id) external; 71 | } 72 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/ILSP8Offers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP8Offer { 5 | uint256 price; 6 | uint256 expirationTime; 7 | } 8 | 9 | interface ILSP8Offers { 10 | /// an offer was made for a listing by a buyer 11 | event Placed( 12 | uint256 indexed listingId, address indexed buyer, bytes32 tokenId, uint256 price, uint256 expirationTime 13 | ); 14 | /// a buyer canceled an offer 15 | event Canceled(uint256 indexed listingId, address indexed buyer, uint256 price); 16 | /// a seller accepted an offer 17 | event Accepted(uint256 indexed listingId, address indexed buyer, uint256 price); 18 | 19 | /// confirms an offer has been placed by a buyer 20 | /// @param listingId listing id 21 | /// @param buyer buyer 22 | function isPlacedOffer(uint256 listingId, address buyer) external view returns (bool); 23 | 24 | /// confirms a listing with id is placed and wasn't canceled 25 | /// @param listingId listing id 26 | /// @param buyer buyer 27 | function isActiveOffer(uint256 listingId, address buyer) external view returns (bool); 28 | 29 | /// retrieves an offer for a listing made by a buyer or reverts if not placed 30 | /// @param listingId listing id 31 | /// @param buyer buyer 32 | function getOffer(uint256 listingId, address buyer) external view returns (LSP8Offer memory); 33 | 34 | /// place an offer with a fixed price, number of items and seconds until the offer is expired. 35 | /// @param listingId listing id 36 | /// @param totalPrice total price 37 | /// @param secondsUntilExpiration time in seconds until offer is expired 38 | function place(uint256 listingId, uint256 totalPrice, uint256 secondsUntilExpiration) external payable; 39 | 40 | /// cancel an offer by a buyer being a sender. 41 | /// @param listingId listing id 42 | function cancel(uint256 listingId) external; 43 | 44 | /// accept an offer. 45 | /// @param listingId listing id 46 | /// @param buyer buyer 47 | function accept(uint256 listingId, address buyer) external; 48 | } 49 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/ILSP8Orders.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | struct LSP8Order { 5 | uint256 id; 6 | address asset; 7 | address buyer; 8 | uint256 tokenPrice; 9 | bytes32[] tokenIds; 10 | uint16 tokenCount; 11 | } 12 | 13 | interface ILSP8Orders { 14 | /// an order was made for an asset 15 | event Placed( 16 | uint256 id, 17 | address indexed asset, 18 | address indexed buyer, 19 | uint256 tokenPrice, 20 | bytes32[] tokenIds, 21 | uint16 tokenCount 22 | ); 23 | 24 | /// a buyer canceled an order 25 | event Canceled( 26 | uint256 id, 27 | address indexed asset, 28 | address indexed buyer, 29 | uint256 tokenPrice, 30 | bytes32[] tokenIds, 31 | uint16 tokenCount 32 | ); 33 | 34 | /// an order was filled 35 | event Filled( 36 | uint256 id, 37 | address indexed asset, 38 | address indexed seller, 39 | address indexed buyer, 40 | uint256 tokenPrice, 41 | bytes32[] tokenIds, 42 | uint16 tokenCount 43 | ); 44 | 45 | /// confirms an order has been placed by a buyer 46 | /// @param asset asset address 47 | /// @param buyer buyer 48 | /// @param tokenIds token ids or empty for any tokens. Must be sorted in ascending order. 49 | /// @return true if the order is placed 50 | function isPlacedOrderOf(address asset, address buyer, bytes32[] calldata tokenIds) external view returns (bool); 51 | 52 | /// retrieves an order for an asset made by a buyer or reverts if not placed 53 | /// @param asset asset address 54 | /// @param buyer buyer 55 | /// @param tokenIds token ids or empty for any tokens. Must be sorted in ascending order. 56 | /// @return order order 57 | function orderOf(address asset, address buyer, bytes32[] calldata tokenIds) 58 | external 59 | view 60 | returns (LSP8Order memory); 61 | 62 | /// retrieves all orders for an asset made by a buyer 63 | /// @param asset asset address 64 | /// @param buyer buyer 65 | /// @return orders orders 66 | function ordersOf(address asset, address buyer) external view returns (LSP8Order[] memory); 67 | 68 | /// confirms an order has been placed by a buyer 69 | /// @param id order id 70 | /// @return true if the order is placed 71 | function isPlacedOrder(uint256 id) external view returns (bool); 72 | 73 | /// retrieves an order for an asset reverts if not placed 74 | /// @param id order id 75 | /// @return order order 76 | function getOrder(uint256 id) external view returns (LSP8Order memory); 77 | 78 | /// place an offer order with a fixed token price, token ids and number of tokens. 79 | /// @param asset asset address 80 | /// @param tokenPrice token price 81 | /// @param tokenIds token ids 82 | /// @param tokenCount number of tokens 83 | /// @return orderId order id 84 | function place(address asset, uint256 tokenPrice, bytes32[] calldata tokenIds, uint16 tokenCount) 85 | external 86 | payable 87 | returns (uint256); 88 | 89 | /// cancel an order by a buyer being a sender. 90 | /// @param id order id 91 | /// @dev reverts if the order is not placed 92 | function cancel(uint256 id) external; 93 | 94 | /// fill an order. 95 | /// @param id order id 96 | /// @param seller seller 97 | /// @param tokenIds token ids 98 | /// @dev reverts if the order is not placed 99 | function fill(uint256 id, address seller, bytes32[] calldata tokenIds) external; 100 | } 101 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/LSP8Listings.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ILSP8IdentifiableDigitalAsset} from 5 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol"; 6 | import {Module, MARKETPLACE_ROLE, OPERATOR_ROLE} from "../common/Module.sol"; 7 | import {ILSP8Listings, LSP8Listing} from "./ILSP8Listings.sol"; 8 | 9 | contract LSP8Listings is ILSP8Listings, Module { 10 | error InsufficientAuthorization(address account, bytes32 tokenId); 11 | error InvalidListingTime(uint256 time); 12 | error NotListed(uint256 id); 13 | error UnathorizedSeller(address account); 14 | error InactiveListing(uint256 id); 15 | error AlreadyListed(uint256 id); 16 | 17 | uint256 public totalListings; 18 | // id -> listing 19 | mapping(uint256 => LSP8Listing) private _listings; 20 | // hash(asset, tokenId) -> id 21 | mapping(bytes32 => uint256) private _listingIds; 22 | 23 | constructor() { 24 | _disableInitializers(); 25 | } 26 | 27 | function initialize(address newOwner_) external initializer { 28 | Module._initialize(newOwner_); 29 | } 30 | 31 | function isListed(uint256 id) public view override returns (bool) { 32 | return _listings[id].seller != address(0); 33 | } 34 | 35 | function isActiveListing(uint256 id) public view override returns (bool) { 36 | LSP8Listing memory listing = _listings[id]; 37 | return isListed(id) && (block.timestamp >= listing.startTime) 38 | && ((listing.endTime == 0) || (block.timestamp < listing.endTime)); 39 | } 40 | 41 | function getListing(uint256 id) public view override returns (LSP8Listing memory) { 42 | if (!isListed(id)) { 43 | revert NotListed(id); 44 | } 45 | return _listings[id]; 46 | } 47 | 48 | function list(address asset, bytes32 tokenId, uint256 price, uint256 startTime, uint256 secondsUntilEndTime) 49 | external 50 | override 51 | whenNotPaused 52 | nonReentrant 53 | returns (uint256) 54 | { 55 | address seller = msg.sender; 56 | address owner = ILSP8IdentifiableDigitalAsset(asset).tokenOwnerOf(tokenId); 57 | bool isOperator = ILSP8IdentifiableDigitalAsset(asset).isOperatorFor(seller, tokenId); 58 | if (!isOperator && !hasRole(seller, MARKETPLACE_ROLE)) { 59 | revert InsufficientAuthorization(seller, tokenId); 60 | } 61 | totalListings += 1; 62 | uint256 id = totalListings; 63 | // verify existing possible listing 64 | { 65 | bytes32 existingKey = _listingKey(asset, tokenId); 66 | uint256 existingId = _listingIds[existingKey]; 67 | LSP8Listing memory existingListing = _listings[existingId]; 68 | if (existingListing.seller != address(0)) { 69 | if ( 70 | existingListing.seller == seller 71 | && ((existingListing.endTime == 0) || (block.timestamp < existingListing.endTime)) 72 | ) { 73 | revert AlreadyListed(existingId); 74 | } 75 | delete _listings[existingId]; 76 | emit Delisted(existingId, asset); 77 | } 78 | _listingIds[existingKey] = id; 79 | } 80 | uint256 endTime = 0; 81 | if (secondsUntilEndTime > 0) { 82 | endTime = startTime + secondsUntilEndTime; 83 | } 84 | _listings[id] = LSP8Listing({ 85 | seller: seller, 86 | asset: asset, 87 | owner: owner, 88 | tokenId: tokenId, 89 | price: price, 90 | startTime: startTime, 91 | endTime: endTime 92 | }); 93 | emit Listed(id, asset, seller, owner, tokenId, price, startTime, endTime); 94 | return id; 95 | } 96 | 97 | function update(uint256 id, uint256 price, uint256 startTime, uint256 secondsUntilEndTime) 98 | external 99 | override 100 | whenNotPaused 101 | nonReentrant 102 | { 103 | LSP8Listing memory listing = getListing(id); 104 | if (msg.sender != listing.seller) { 105 | revert UnathorizedSeller(msg.sender); 106 | } 107 | bool isOperator = ILSP8IdentifiableDigitalAsset(listing.asset).isOperatorFor(listing.seller, listing.tokenId); 108 | if (!isOperator) { 109 | revert InsufficientAuthorization(listing.seller, listing.tokenId); 110 | } 111 | uint256 endTime = 0; 112 | if (secondsUntilEndTime > 0) { 113 | endTime = startTime + secondsUntilEndTime; 114 | } 115 | _listings[id] = LSP8Listing({ 116 | seller: listing.seller, 117 | asset: listing.asset, 118 | owner: listing.owner, 119 | tokenId: listing.tokenId, 120 | price: price, 121 | startTime: startTime, 122 | endTime: endTime 123 | }); 124 | emit Updated(id, listing.asset, price, startTime, endTime); 125 | } 126 | 127 | function delist(uint256 id) external override whenNotPaused nonReentrant { 128 | LSP8Listing memory listing = getListing(id); 129 | if (msg.sender != listing.seller && !hasRole(msg.sender, OPERATOR_ROLE)) { 130 | revert UnathorizedSeller(msg.sender); 131 | } 132 | delete _listings[id]; 133 | delete _listingIds[_listingKey(listing.asset, listing.tokenId)]; 134 | emit Delisted(id, listing.asset); 135 | } 136 | 137 | function unlist(uint256 id) external override whenNotPaused nonReentrant onlyMarketplace { 138 | LSP8Listing memory listing = getListing(id); 139 | if (!isActiveListing(id)) { 140 | revert InactiveListing(id); 141 | } 142 | delete _listings[id]; 143 | delete _listingIds[_listingKey(listing.asset, listing.tokenId)]; 144 | emit Unlisted(id, listing.asset); 145 | } 146 | 147 | function _listingKey(address asset, bytes32 tokenId) private pure returns (bytes32) { 148 | return keccak256(abi.encodePacked(asset, tokenId)); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/marketplace/lsp8/LSP8Offers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ILSP8IdentifiableDigitalAsset} from 5 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/ILSP8IdentifiableDigitalAsset.sol"; 6 | import {Module} from "../common/Module.sol"; 7 | import {ILSP8Listings, LSP8Listing} from "./ILSP8Listings.sol"; 8 | import {ILSP8Offers, LSP8Offer} from "./ILSP8Offers.sol"; 9 | 10 | uint256 constant CANCELATION_COOLDOWN = 1 hours; 11 | 12 | contract LSP8Offers is ILSP8Offers, Module { 13 | error NotPlaced(uint256 listingId, address buyer); 14 | error InvalidOfferDuration(uint256 secondsUntilExpiration); 15 | error InvalidOfferTotalPrice(uint256 totalPrice); 16 | error InactiveListing(uint256 listingId); 17 | error InactiveOffer(uint256 listingId, address buyer); 18 | error Unpaid(uint256 listingId, address buyer, uint256 amount); 19 | error RecentlyCanceled(uint256 listingId, address buyer, uint256 cooldownTimestamp); 20 | 21 | ILSP8Listings public listings; 22 | // listing id -> buyer -> offer 23 | mapping(uint256 => mapping(address => LSP8Offer)) private _offers; 24 | mapping(uint256 listingId => mapping(address buyer => uint256 cooldownTimestamp)) private _cancellationCooldown; 25 | 26 | constructor() { 27 | _disableInitializers(); 28 | } 29 | 30 | function initialize(address newOwner_, ILSP8Listings listings_) external initializer { 31 | Module._initialize(newOwner_); 32 | listings = listings_; 33 | } 34 | 35 | function isPlacedOffer(uint256 listingId, address buyer) public view override returns (bool) { 36 | return _offers[listingId][buyer].expirationTime != 0; 37 | } 38 | 39 | function isActiveOffer(uint256 listingId, address buyer) public view override returns (bool) { 40 | return _offers[listingId][buyer].expirationTime > block.timestamp; 41 | } 42 | 43 | function getOffer(uint256 listingId, address buyer) public view override returns (LSP8Offer memory) { 44 | if (!isPlacedOffer(listingId, buyer)) { 45 | revert NotPlaced(listingId, buyer); 46 | } 47 | return _offers[listingId][buyer]; 48 | } 49 | 50 | function place(uint256 listingId, uint256 totalPrice, uint256 secondsUntilExpiration) 51 | external 52 | payable 53 | override 54 | whenNotPaused 55 | nonReentrant 56 | { 57 | LSP8Listing memory listing = listings.getListing(listingId); 58 | if (!listings.isActiveListing(listingId)) { 59 | revert InactiveListing(listingId); 60 | } 61 | if ((secondsUntilExpiration < 1 hours) || (secondsUntilExpiration > 28 days)) { 62 | revert InvalidOfferDuration(secondsUntilExpiration); 63 | } 64 | LSP8Offer memory lastOffer = _offers[listingId][msg.sender]; 65 | uint256 actualTotalPrice = msg.value + lastOffer.price; 66 | if (actualTotalPrice != totalPrice) { 67 | revert InvalidOfferTotalPrice(totalPrice); 68 | } 69 | uint256 expirationTime = block.timestamp + secondsUntilExpiration; 70 | _offers[listingId][msg.sender] = LSP8Offer({price: actualTotalPrice, expirationTime: expirationTime}); 71 | emit Placed(listingId, msg.sender, listing.tokenId, actualTotalPrice, expirationTime); 72 | } 73 | 74 | function cancel(uint256 listingId) external override whenNotPaused nonReentrant { 75 | address buyer = msg.sender; 76 | LSP8Offer memory offer = getOffer(listingId, buyer); 77 | delete _offers[listingId][buyer]; 78 | _cancellationCooldown[listingId][buyer] = block.timestamp; 79 | (bool success,) = buyer.call{value: offer.price}(""); 80 | if (!success) { 81 | revert Unpaid(listingId, buyer, offer.price); 82 | } 83 | emit Canceled(listingId, buyer, offer.price); 84 | } 85 | 86 | function accept(uint256 listingId, address buyer) external override whenNotPaused nonReentrant onlyMarketplace { 87 | if (!listings.isActiveListing(listingId)) { 88 | revert InactiveListing(listingId); 89 | } 90 | LSP8Offer memory offer = getOffer(listingId, buyer); 91 | if (!isActiveOffer(listingId, buyer)) { 92 | revert InactiveOffer(listingId, buyer); 93 | } 94 | delete _offers[listingId][buyer]; 95 | // verify cooldown period if the offer was recently canceled 96 | uint256 cooldownTimestamp = _cancellationCooldown[listingId][buyer]; 97 | if ((cooldownTimestamp > 0) && (cooldownTimestamp + CANCELATION_COOLDOWN > block.timestamp)) { 98 | revert RecentlyCanceled(listingId, buyer, cooldownTimestamp + CANCELATION_COOLDOWN); 99 | } 100 | (bool success,) = msg.sender.call{value: offer.price}(""); 101 | if (!success) { 102 | revert Unpaid(listingId, msg.sender, offer.price); 103 | } 104 | emit Accepted(listingId, buyer, offer.price); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/page/PageName.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; 5 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 6 | import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; 7 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 8 | import {_LSP8_TOKENID_FORMAT_STRING} from 9 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; 10 | import {LSP8EnumerableInitAbstract} from 11 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8EnumerableInitAbstract.sol"; 12 | import {LSP8IdentifiableDigitalAssetInitAbstract} from 13 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetInitAbstract.sol"; 14 | import {Withdrawable} from "../common/Withdrawable.sol"; 15 | 16 | contract PageName is LSP8EnumerableInitAbstract, ReentrancyGuardUpgradeable, PausableUpgradeable, Withdrawable { 17 | uint256 private constant _PROFILE_CLAIMS_CLAIMED = 1 << 255; 18 | 19 | error InvalidController(); 20 | 21 | error UnauthorizedRelease(address account, bytes32 tokenId); 22 | 23 | error IncorrectReservationName(address recipient, string name); 24 | error UnauthorizedReservation(address recipient, string name, uint256 price); 25 | 26 | event ControllerChanged(address indexed oldController, address indexed newController); 27 | 28 | event ReservedName(address indexed account, bytes32 indexed tokenId, uint256 price); 29 | event ReleasedName(address indexed account, bytes32 indexed tokenId); 30 | 31 | mapping(address => uint256) private _profileClaims; 32 | uint256 public _unused_storage_slot_1; 33 | uint8 public minimumLength; 34 | uint16 public _unused_storage_slot_2; 35 | address public controller; 36 | uint160 private _unused_storage_slot_3; 37 | // hash => used 38 | mapping(bytes32 => bool) private _usedReservations; 39 | 40 | constructor() { 41 | _disableInitializers(); 42 | } 43 | 44 | function initialize( 45 | string memory name_, 46 | string memory symbol_, 47 | address newOwner_, 48 | address beneficiary_, 49 | address controller_, 50 | uint8 minimumLength_ 51 | ) external initializer { 52 | super._initialize(name_, symbol_, newOwner_, _LSP4_TOKEN_TYPE_NFT, _LSP8_TOKENID_FORMAT_STRING); 53 | __ReentrancyGuard_init(); 54 | __Pausable_init(); 55 | _setBeneficiary(beneficiary_); 56 | _setController(controller_); 57 | minimumLength = minimumLength_; 58 | } 59 | 60 | receive() external payable override(LSP8IdentifiableDigitalAssetInitAbstract, Withdrawable) { 61 | _doReceive(); 62 | } 63 | 64 | function setMinimumLength(uint8 newLength) external onlyOwner { 65 | minimumLength = newLength; 66 | } 67 | 68 | function claimsOf(address account) public view returns (bool claimed) { 69 | uint256 claims = _profileClaims[account]; 70 | claimed = (claims & _PROFILE_CLAIMS_CLAIMED) == _PROFILE_CLAIMS_CLAIMED; 71 | } 72 | 73 | function setClaimed(address account, bool claimed) private { 74 | uint256 claims = _profileClaims[account]; 75 | if (claimed) { 76 | claims |= _PROFILE_CLAIMS_CLAIMED; 77 | } else { 78 | claims &= ~_PROFILE_CLAIMS_CLAIMED; 79 | } 80 | _profileClaims[account] = claims; 81 | } 82 | 83 | function setController(address newController) external onlyOwner { 84 | _setController(newController); 85 | } 86 | 87 | function _setController(address newController) private { 88 | if (newController == address(0)) { 89 | revert InvalidController(); 90 | } 91 | address oldController = controller; 92 | if (oldController == newController) { 93 | revert InvalidController(); 94 | } 95 | controller = newController; 96 | emit ControllerChanged(oldController, newController); 97 | } 98 | 99 | function pause() external onlyOwner { 100 | _pause(); 101 | } 102 | 103 | function unpause() external onlyOwner { 104 | _unpause(); 105 | } 106 | 107 | function reserve( 108 | address recipient, 109 | string calldata name, 110 | bool force, 111 | bytes calldata salt, 112 | uint8 v, 113 | bytes32 r, 114 | bytes32 s 115 | ) external payable nonReentrant whenNotPaused { 116 | if (!_isValidName(name)) { 117 | revert IncorrectReservationName(recipient, name); 118 | } 119 | bytes32 hash = 120 | keccak256(abi.encodePacked(address(this), block.chainid, recipient, name, force, salt, msg.value)); 121 | if (_usedReservations[hash] || (ECDSA.recover(hash, v, r, s) != controller)) { 122 | revert UnauthorizedReservation(recipient, name, msg.value); 123 | } 124 | if (!force && (msg.value == 0)) { 125 | (bool claimed) = claimsOf(recipient); 126 | if (claimed) { 127 | revert UnauthorizedReservation(recipient, name, msg.value); 128 | } 129 | setClaimed(recipient, true); 130 | } 131 | _usedReservations[hash] = true; 132 | bytes32 tokenId = bytes32(bytes(name)); 133 | _mint(recipient, tokenId, false, ""); 134 | emit ReservedName(recipient, tokenId, msg.value); 135 | } 136 | 137 | function release(bytes32 tokenId) external nonReentrant whenNotPaused { 138 | if (!isOperatorFor(msg.sender, tokenId)) { 139 | revert UnauthorizedRelease(msg.sender, tokenId); 140 | } 141 | address tokenOwner = tokenOwnerOf(tokenId); 142 | _burn(tokenId, ""); 143 | emit ReleasedName(tokenOwner, tokenId); 144 | } 145 | 146 | function _isValidName(string memory name) private view returns (bool) { 147 | bytes memory chars = bytes(name); 148 | uint256 length = chars.length; 149 | if (length < minimumLength || length > 32) { 150 | return false; 151 | } 152 | for (uint256 i = 0; i < length; i++) { 153 | bytes1 char = chars[i]; 154 | if (!(char >= 0x61 && char <= 0x7a) && !(char >= 0x30 && char <= 0x39) && char != 0x2d && char != 0x5f) { 155 | return false; 156 | } 157 | } 158 | return true; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/pool/IDepositContract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | uint256 constant DEPOSIT_AMOUNT = 32 ether; 5 | 6 | IDepositContract constant DepositContract = IDepositContract(0xCAfe00000000000000000000000000000000CAfe); 7 | 8 | interface IDepositContract { 9 | /// @notice A processed deposit event. 10 | event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index); 11 | 12 | /// @notice Submit a Phase 0 DepositData object. 13 | /// @param pubkey A BLS12-381 public key. 14 | /// @param withdrawal_credentials Commitment to a public key for withdrawals. 15 | /// @param signature A BLS12-381 signature. 16 | /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. 17 | /// Used as a protection against malformed input. 18 | function deposit( 19 | bytes calldata pubkey, 20 | bytes calldata withdrawal_credentials, 21 | bytes calldata signature, 22 | bytes32 deposit_data_root 23 | ) external payable; 24 | 25 | /// @notice Query the current deposit root hash. 26 | /// @return The deposit root hash. 27 | function get_deposit_root() external view returns (bytes32); 28 | 29 | /// @notice Query the current deposit count. 30 | /// @return The deposit count encoded as a little endian 64-bit number. 31 | function get_deposit_count() external view returns (bytes memory); 32 | } 33 | -------------------------------------------------------------------------------- /src/profiles/ProfilesOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {ERC725YInitAbstract} from "@erc725/smart-contracts/contracts/ERC725YInitAbstract.sol"; 5 | import {ERC725YCore} from "@erc725/smart-contracts/contracts/ERC725YCore.sol"; 6 | import { 7 | ERC725Y_DataKeysValuesLengthMismatch, 8 | ERC725Y_DataKeysValuesEmptyArray 9 | } from "@erc725/smart-contracts/contracts/errors.sol"; 10 | import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; 11 | 12 | bytes32 constant ORACLE_ROLE = keccak256("ORACLE_ROLE"); 13 | bytes32 constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); 14 | 15 | contract ProfilesOracle is ERC725YInitAbstract, AccessControlUpgradeable { 16 | constructor() { 17 | _disableInitializers(); 18 | } 19 | 20 | function initialize(address initialOwner, address operator) external initializer { 21 | ERC725YInitAbstract._initialize(initialOwner); 22 | _setRoleAdmin(ORACLE_ROLE, OPERATOR_ROLE); 23 | _setRoleAdmin(OPERATOR_ROLE, OPERATOR_ROLE); 24 | _grantRole(OPERATOR_ROLE, operator); 25 | } 26 | 27 | function supportsInterface(bytes4 interfaceId) 28 | public 29 | view 30 | override(ERC725YCore, AccessControlUpgradeable) 31 | returns (bool) 32 | { 33 | return super.supportsInterface(interfaceId); 34 | } 35 | 36 | function submitData(bytes32[] calldata dataKeys, bytes[] calldata dataValues) external onlyRole(ORACLE_ROLE) { 37 | if (dataKeys.length != dataValues.length) { 38 | revert ERC725Y_DataKeysValuesLengthMismatch(); 39 | } 40 | if (dataKeys.length == 0) { 41 | revert ERC725Y_DataKeysValuesEmptyArray(); 42 | } 43 | for (uint256 i = 0; i < dataKeys.length; i++) { 44 | _setData(dataKeys[i], dataValues[i]); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/profiles/ProfilesReverseLookup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {IERC725Y} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; 5 | import {LSP6Utils} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Utils.sol"; 6 | import {_PERMISSION_SIGN} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Constants.sol"; 7 | 8 | contract ProfilesReverseLookup { 9 | error UnathorizedController(address controller, address profile); 10 | error AlreadyRegistered(address controller, address profile); 11 | error NotRegistered(address controller, address profile); 12 | error Unathorized(); 13 | 14 | event ProfileRegistered(address indexed controller, address indexed profile, bytes data); 15 | event ProfileUnregistered(address indexed controller, address indexed profile, bytes data); 16 | 17 | mapping(bytes32 key => uint16 index) private _profileIndices; 18 | mapping(address controller => address[] profiles) private _profiles; 19 | 20 | function profilesOf(address controller) external view returns (address[] memory) { 21 | return _profiles[controller]; 22 | } 23 | 24 | function registered(address controller, address profile) external view returns (bool) { 25 | bytes32 indexKey = _profileIndexKey(controller, profile); 26 | address[] memory profiles = _profiles[controller]; 27 | return profiles.length > 0 && profiles[_profileIndices[indexKey]] == profile; 28 | } 29 | 30 | function _profileIndexKey(address controller, address profile) private pure returns (bytes32) { 31 | return keccak256(abi.encodePacked(controller, profile)); 32 | } 33 | 34 | function register(address controller, address profile, bytes calldata data) external { 35 | if (msg.sender != controller && msg.sender != profile) { 36 | revert Unathorized(); 37 | } 38 | 39 | bytes32 permissions = LSP6Utils.getPermissionsFor(IERC725Y(profile), controller); 40 | bool granted = LSP6Utils.hasPermission(permissions, _PERMISSION_SIGN); 41 | if (!granted) { 42 | revert UnathorizedController(controller, profile); 43 | } 44 | 45 | bytes32 indexKey = _profileIndexKey(controller, profile); 46 | uint16 index = _profileIndices[indexKey]; 47 | address[] memory profiles = _profiles[controller]; 48 | 49 | if (profiles.length > 0 && profiles[index] == profile) { 50 | revert AlreadyRegistered(controller, profile); 51 | } 52 | 53 | _profileIndices[indexKey] = uint16(profiles.length); 54 | _profiles[controller].push(profile); 55 | emit ProfileRegistered(controller, profile, data); 56 | } 57 | 58 | function unregister(address controller, address profile, bytes calldata data) external { 59 | if (msg.sender != controller && msg.sender != profile) { 60 | revert Unathorized(); 61 | } 62 | 63 | bytes32 indexKey = _profileIndexKey(controller, profile); 64 | uint16 index = _profileIndices[indexKey]; 65 | address[] memory profiles = _profiles[controller]; 66 | 67 | if (profiles.length <= index || profiles[index] != profile) { 68 | revert NotRegistered(controller, profile); 69 | } 70 | 71 | uint16 lastIndex = uint16(profiles.length - 1); 72 | address lastProfile = profiles[lastIndex]; 73 | 74 | _profileIndices[_profileIndexKey(controller, lastProfile)] = index; 75 | _profiles[controller][index] = lastProfile; 76 | _profiles[controller].pop(); 77 | emit ProfileUnregistered(controller, profile, data); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/assets/lsp7/DigitalAssetDrop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {Merkle} from "murky/Merkle.sol"; 6 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 7 | import {_LSP4_TOKEN_TYPE_NFT} from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 8 | import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; 9 | import {DigitalAssetDrop} from "../../../src/assets/lsp7/DigitalAssetDrop.sol"; 10 | import {deployProfile} from "../../utils/profile.sol"; 11 | import {DigitalAssetMock} from "./DigitalAssetMock.sol"; 12 | 13 | contract DigitalAssetDropTest is Test { 14 | event Claimed(uint256 indexed index, address indexed recipient, uint256 amount); 15 | event Disposed(address indexed beneficiary, uint256 amount); 16 | 17 | Merkle merkle; 18 | address assetOwner; 19 | address profileOwner; 20 | address dropOwner; 21 | DigitalAssetMock asset; 22 | 23 | function setUp() public { 24 | assetOwner = vm.addr(1); 25 | profileOwner = vm.addr(2); 26 | dropOwner = vm.addr(3); 27 | 28 | merkle = new Merkle(); 29 | asset = new DigitalAssetMock("Mock", "MCK", assetOwner, _LSP4_TOKEN_TYPE_NFT, true); 30 | } 31 | 32 | function test_Claim() public { 33 | (UniversalProfile alice,) = deployProfile(); 34 | (UniversalProfile bob,) = deployProfile(); 35 | 36 | bytes32[] memory data = new bytes32[](2); 37 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, uint256(3)))))); 38 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(bob, uint256(5)))))); 39 | 40 | DigitalAssetDrop drop = new DigitalAssetDrop(asset, merkle.getRoot(data), dropOwner); 41 | asset.mint(address(drop), 8, true, ""); 42 | assertEq(asset.balanceOf(address(drop)), 8); 43 | 44 | vm.startPrank(address(alice)); 45 | vm.expectEmit(address(drop)); 46 | emit Claimed(0, address(alice), 3); 47 | drop.claim(merkle.getProof(data, 0), 0, address(alice), 3); 48 | vm.stopPrank(); 49 | 50 | vm.startPrank(address(bob)); 51 | vm.expectEmit(address(drop)); 52 | emit Claimed(1, address(bob), 5); 53 | drop.claim(merkle.getProof(data, 1), 1, address(bob), 5); 54 | vm.stopPrank(); 55 | 56 | assertEq(asset.balanceOf(address(drop)), 0); 57 | assertEq(asset.balanceOf(address(alice)), 3); 58 | assertEq(asset.balanceOf(address(bob)), 5); 59 | } 60 | 61 | function test_ClaimToRecipient() public { 62 | (UniversalProfile alice,) = deployProfile(); 63 | (UniversalProfile bob,) = deployProfile(); 64 | 65 | bytes32[] memory data = new bytes32[](2); 66 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, uint256(3)))))); 67 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(alice, uint256(5)))))); 68 | 69 | DigitalAssetDrop drop = new DigitalAssetDrop(asset, merkle.getRoot(data), dropOwner); 70 | asset.mint(address(drop), 8, true, ""); 71 | assertEq(asset.balanceOf(address(drop)), 8); 72 | 73 | vm.startPrank(address(alice)); 74 | 75 | vm.expectEmit(address(drop)); 76 | emit Claimed(0, address(bob), 3); 77 | drop.claim(merkle.getProof(data, 0), 0, address(bob), 3); 78 | 79 | vm.expectEmit(address(drop)); 80 | emit Claimed(1, address(bob), 5); 81 | drop.claim(merkle.getProof(data, 1), 1, address(bob), 5); 82 | 83 | vm.stopPrank(); 84 | 85 | assertEq(asset.balanceOf(address(drop)), 0); 86 | assertEq(asset.balanceOf(address(alice)), 0); 87 | assertEq(asset.balanceOf(address(bob)), 8); 88 | } 89 | 90 | function test_Dispose() public { 91 | address beneficiary = vm.addr(100); 92 | (UniversalProfile alice,) = deployProfile(); 93 | (UniversalProfile bob,) = deployProfile(); 94 | 95 | bytes32[] memory data = new bytes32[](2); 96 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, uint256(3)))))); 97 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(bob, uint256(5)))))); 98 | 99 | DigitalAssetDrop drop = new DigitalAssetDrop(asset, merkle.getRoot(data), dropOwner); 100 | asset.mint(address(drop), 8, true, ""); 101 | assertEq(asset.balanceOf(address(drop)), 8); 102 | 103 | assertEq(asset.balanceOf(beneficiary), 0); 104 | vm.prank(dropOwner); 105 | vm.expectEmit(address(drop)); 106 | emit Disposed(beneficiary, 8); 107 | drop.dispose(beneficiary); 108 | assertEq(asset.balanceOf(address(drop)), 0); 109 | assertEq(asset.balanceOf(beneficiary), 8); 110 | } 111 | 112 | function test_Revert_DisposeIfNotOwner() public { 113 | bytes32[] memory data = new bytes32[](2); 114 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(address(1), uint256(3)))))); 115 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(address(1), uint256(5)))))); 116 | 117 | DigitalAssetDrop drop = new DigitalAssetDrop(asset, merkle.getRoot(data), dropOwner); 118 | 119 | vm.prank(address(1)); 120 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 121 | drop.dispose(address(1)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /test/assets/lsp7/DigitalAssetMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol"; 5 | 6 | contract DigitalAssetMock is LSP7DigitalAsset { 7 | constructor( 8 | string memory name_, 9 | string memory symbol_, 10 | address newOwner_, 11 | uint256 lsp4TokenType_, 12 | bool isNonDivisible_ 13 | ) LSP7DigitalAsset(name_, symbol_, newOwner_, lsp4TokenType_, isNonDivisible_) {} 14 | 15 | function mint(address to, uint256 amount, bool allowNonLSP1Recipient, bytes memory data) external { 16 | _mint(to, amount, allowNonLSP1Recipient, data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/assets/lsp7/GenesisDigitalAsset.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 6 | import {GenesisDigitalAsset} from "../../../src/assets/lsp7/GenesisDigitalAsset.sol"; 7 | 8 | contract GenesisDigitalAssetTest is Test { 9 | GenesisDigitalAsset asset; 10 | address owner; 11 | address beneficiary; 12 | 13 | function setUp() public { 14 | owner = vm.addr(1); 15 | beneficiary = vm.addr(2); 16 | asset = new GenesisDigitalAsset("Universal Page Genesis", "UPG", owner, beneficiary); 17 | } 18 | 19 | function testFuzz_Reserve(uint256 amount) public { 20 | assertEq(0, asset.balanceOf(beneficiary)); 21 | vm.prank(owner); 22 | asset.reserve(amount); 23 | assertEq(amount, asset.balanceOf(beneficiary)); 24 | } 25 | 26 | function testFuzz_Release(uint256 reserved, uint256 released) public { 27 | vm.assume(reserved >= released); 28 | vm.prank(owner); 29 | asset.reserve(reserved); 30 | assertEq(reserved, asset.balanceOf(beneficiary)); 31 | 32 | vm.prank(beneficiary); 33 | asset.release(released); 34 | assertEq(reserved - released, asset.balanceOf(beneficiary)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/assets/lsp7/MintableDigitalAsset.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 6 | import { 7 | _LSP4_TOKEN_TYPE_TOKEN, 8 | _LSP4_TOKEN_TYPE_NFT 9 | } from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 10 | import {MintableDigitalAsset} from "../../../src/assets/lsp7/MintableDigitalAsset.sol"; 11 | 12 | contract MintableDigitalAssetTest is Test { 13 | address owner; 14 | 15 | function setUp() public { 16 | owner = vm.addr(1); 17 | } 18 | 19 | function test_NonDivisble() public { 20 | MintableDigitalAsset asset = new MintableDigitalAsset("Test", "TST", owner, _LSP4_TOKEN_TYPE_NFT, true, 100); 21 | assertEq(0, asset.totalSupply()); 22 | assertEq(100, asset.tokenSupplyCap()); 23 | assertEq(owner, asset.owner()); 24 | assertEq(0, asset.decimals()); 25 | } 26 | 27 | function test_Divisible() public { 28 | MintableDigitalAsset asset = new MintableDigitalAsset("Test", "TST", owner, _LSP4_TOKEN_TYPE_TOKEN, false, 100); 29 | assertEq(0, asset.totalSupply()); 30 | assertEq(100, asset.tokenSupplyCap()); 31 | assertEq(owner, asset.owner()); 32 | assertEq(18, asset.decimals()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/assets/lsp8/CollectorIdentifiableDigitalAsset.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 6 | import { 7 | _LSP4_TOKEN_NAME_KEY, 8 | _LSP4_TOKEN_SYMBOL_KEY, 9 | _LSP4_TOKEN_TYPE_KEY, 10 | _LSP4_TOKEN_TYPE_NFT 11 | } from "@lukso/lsp-smart-contracts/contracts/LSP4DigitalAssetMetadata/LSP4Constants.sol"; 12 | import { 13 | _LSP8_TOKENID_FORMAT_KEY, 14 | _LSP8_TOKENID_FORMAT_UNIQUE_ID 15 | } from "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; 16 | import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; 17 | import {CollectorIdentifiableDigitalAsset} from "../../../src/assets/lsp8/CollectorIdentifiableDigitalAsset.sol"; 18 | import {deployProfile} from "../../utils/profile.sol"; 19 | 20 | contract CollectorIdentifiableDigitalAssetTest is Test { 21 | event TokensPurchased(address indexed recipient, bytes32[] tokenIds, uint256 totalPaid); 22 | event TokensReserved(address indexed recipient, bytes32[] tokenIds); 23 | event ControllerChanged(address indexed oldController, address indexed newController); 24 | 25 | address owner; 26 | uint256 controllerKey; 27 | address controller; 28 | CollectorIdentifiableDigitalAsset asset; 29 | 30 | function setUp() public { 31 | owner = vm.addr(1); 32 | controllerKey = 2; 33 | controller = vm.addr(controllerKey); 34 | 35 | asset = new CollectorIdentifiableDigitalAsset("Universal Page Collector", "UPC", owner, controller, 100); 36 | } 37 | 38 | function test_Initialize() public { 39 | assertEq("Universal Page Collector", asset.getData(_LSP4_TOKEN_NAME_KEY)); 40 | assertEq("UPC", asset.getData(_LSP4_TOKEN_SYMBOL_KEY)); 41 | assertEq(_LSP4_TOKEN_TYPE_NFT, uint256(bytes32(asset.getData(_LSP4_TOKEN_TYPE_KEY)))); 42 | assertEq(_LSP8_TOKENID_FORMAT_UNIQUE_ID, uint256(bytes32(asset.getData(_LSP8_TOKENID_FORMAT_KEY)))); 43 | assertEq(0, asset.totalSupply()); 44 | assertEq(100, asset.tokenSupplyCap()); 45 | assertEq(owner, asset.owner()); 46 | assertEq(controller, asset.controller()); 47 | } 48 | 49 | function test_ConfigureIfOwner() public { 50 | vm.startPrank(owner); 51 | asset.setController(address(10)); 52 | vm.stopPrank(); 53 | } 54 | 55 | function test_Revert_IfNotOwner() public { 56 | vm.prank(address(1)); 57 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 58 | asset.setController(address(100)); 59 | 60 | vm.prank(address(1)); 61 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 62 | asset.withdraw(0 ether); 63 | } 64 | 65 | function test_Revert_PurchaseIfUnathorized() public { 66 | (UniversalProfile profile,) = deployProfile(); 67 | 68 | bytes32[] memory tokenIds = new bytes32[](3); 69 | tokenIds[0] = bytes32(uint256(1)); 70 | tokenIds[1] = bytes32(uint256(2)); 71 | tokenIds[2] = bytes32(uint256(3)); 72 | 73 | bytes32 hash = 74 | keccak256(abi.encodePacked(address(asset), block.chainid, address(profile), tokenIds, uint256(3 ether))); 75 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(100, hash); 76 | 77 | vm.deal(address(profile), 3 ether); 78 | vm.prank(address(profile)); 79 | vm.expectRevert( 80 | abi.encodeWithSelector( 81 | CollectorIdentifiableDigitalAsset.UnauthorizedPurchase.selector, address(profile), tokenIds, 3 ether 82 | ) 83 | ); 84 | asset.purchase{value: 3 ether}(address(profile), tokenIds, v, r, s); 85 | } 86 | 87 | function test_Revert_PurchaseIfTokenIndexAlreadyMinted() public { 88 | (UniversalProfile profile,) = deployProfile(); 89 | 90 | for (uint256 tier = 1; tier <= 3; tier++) { 91 | bytes32[] memory tokenIds = new bytes32[](2); 92 | tokenIds[0] = bytes32(uint256(0x10)); 93 | tokenIds[1] = bytes32(uint256(0x10 + tier)); 94 | 95 | bytes32 hash = 96 | keccak256(abi.encodePacked(address(asset), block.chainid, address(profile), tokenIds, uint256(0 ether))); 97 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); 98 | 99 | vm.prank(address(profile)); 100 | vm.expectRevert( 101 | abi.encodeWithSelector(CollectorIdentifiableDigitalAsset.InvalidTokenId.selector, tokenIds[1]) 102 | ); 103 | asset.purchase(address(profile), tokenIds, v, r, s); 104 | } 105 | } 106 | 107 | function testFuzz_Purchase(bytes32 tokenId0, bytes32 tokenId1, bytes32 tokenId2, uint256 price) public { 108 | vm.assume( 109 | uint16((uint256(tokenId0) >> 4) & 0xFFFF) != uint16((uint256(tokenId1) >> 4) & 0xFFFF) 110 | && uint16((uint256(tokenId0) >> 4) & 0xFFFF) != uint16((uint256(tokenId2) >> 4) & 0xFFFF) 111 | && uint16((uint256(tokenId1) >> 4) & 0xFFFF) != uint16((uint256(tokenId2) >> 4) & 0xFFFF) 112 | ); 113 | vm.assume(price <= 10 ether); 114 | 115 | bytes32[] memory tokenIds = new bytes32[](3); 116 | tokenIds[0] = tokenId0; 117 | tokenIds[1] = tokenId1; 118 | tokenIds[2] = tokenId2; 119 | 120 | (UniversalProfile profile,) = deployProfile(); 121 | 122 | bytes32 hash = keccak256( 123 | abi.encodePacked(address(asset), block.chainid, address(profile), tokenIds, price * tokenIds.length) 124 | ); 125 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); 126 | 127 | vm.deal(address(profile), price * tokenIds.length); 128 | vm.prank(address(profile)); 129 | vm.expectEmit(address(asset)); 130 | emit TokensPurchased(address(profile), tokenIds, price * tokenIds.length); 131 | asset.purchase{value: price * tokenIds.length}(address(profile), tokenIds, v, r, s); 132 | 133 | assertEq(tokenIds.length, asset.totalSupply()); 134 | assertEq(tokenIds.length, asset.balanceOf(address(profile))); 135 | assertEq(address(profile), asset.tokenOwnerOf(tokenId0)); 136 | assertEq(address(profile), asset.tokenOwnerOf(tokenId1)); 137 | assertEq(address(profile), asset.tokenOwnerOf(tokenId2)); 138 | } 139 | 140 | function test_TokenIdentifiers() public { 141 | bytes32[] memory tokenIds = new bytes32[](4); 142 | tokenIds[0] = bytes32(uint256((1 << 4) | 0)); 143 | tokenIds[1] = bytes32(uint256((2 << 4) | 1)); 144 | tokenIds[2] = bytes32(uint256((3 << 4) | 2)); 145 | tokenIds[3] = bytes32(uint256((4 << 4) | 3)); 146 | 147 | address recipient = vm.addr(1000); 148 | 149 | bytes32 hash = keccak256(abi.encodePacked(address(asset), block.chainid, recipient, tokenIds, uint256(0 ether))); 150 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); 151 | 152 | vm.prank(recipient); 153 | asset.purchase(recipient, tokenIds, v, r, s); 154 | 155 | assertEq(4, asset.totalSupply()); 156 | assertEq(0, asset.tokenTierOf(tokenIds[0])); 157 | assertEq(1, asset.tokenIndexOf(tokenIds[0])); 158 | assertEq(1, asset.tokenTierOf(tokenIds[1])); 159 | assertEq(2, asset.tokenIndexOf(tokenIds[1])); 160 | assertEq(2, asset.tokenTierOf(tokenIds[2])); 161 | assertEq(3, asset.tokenIndexOf(tokenIds[2])); 162 | assertEq(3, asset.tokenTierOf(tokenIds[3])); 163 | assertEq(4, asset.tokenIndexOf(tokenIds[3])); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /test/assets/lsp8/MintableIdentifiableDigitalAsset.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 6 | import {MintableIdentifiableDigitalAsset} from "../../../src/assets/lsp8/MintableIdentifiableDigitalAsset.sol"; 7 | 8 | contract MintableIdentifiableDigitalAssetTest is Test { 9 | address owner; 10 | 11 | function setUp() public { 12 | owner = vm.addr(1); 13 | } 14 | 15 | function test() public { 16 | MintableIdentifiableDigitalAsset asset = new MintableIdentifiableDigitalAsset("Test", "TST", owner, 1, 1, 100); 17 | assertEq(0, asset.totalSupply()); 18 | assertEq(100, asset.tokenSupplyCap()); 19 | assertEq(owner, asset.owner()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/common/AssetDropMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {IndexedDrop} from "../../src/common/IndexedDrop.sol"; 5 | 6 | contract AssetDropMock is IndexedDrop { 7 | function isSetup() external view returns (bool) { 8 | return _hasRoot(); 9 | } 10 | 11 | function setup(bytes32 root) external { 12 | _setRoot(root); 13 | } 14 | 15 | function isClaimed(uint256 index) external view returns (bool) { 16 | return _isClaimed(index); 17 | } 18 | 19 | function claim(bytes32[] memory proof, uint256 index, address recipient, uint256 amount) external { 20 | _claim(proof, index, abi.encode(recipient, amount)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/common/IndexedDrop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {Merkle} from "murky/Merkle.sol"; 6 | import {AssetDropMock} from "./AssetDropMock.sol"; 7 | import {IndexedDrop} from "../../src/common/IndexedDrop.sol"; 8 | 9 | contract IndexedDropTest is Test { 10 | AssetDropMock drop; 11 | Merkle merkle; 12 | 13 | function setUp() public { 14 | drop = new AssetDropMock(); 15 | merkle = new Merkle(); 16 | } 17 | 18 | function testFuzz_Claim(address alice, address bob, uint256 amount1, uint256 amount2, uint256 amount3) public { 19 | bytes32[] memory data = new bytes32[](3); 20 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, amount1))))); 21 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(alice, amount2))))); 22 | data[2] = keccak256(bytes.concat(keccak256(abi.encode(uint256(2), abi.encode(bob, amount3))))); 23 | drop.setup(merkle.getRoot(data)); 24 | 25 | drop.claim(merkle.getProof(data, 0), 0, alice, amount1); 26 | assertTrue(drop.isClaimed(0)); 27 | drop.claim(merkle.getProof(data, 1), 1, alice, amount2); 28 | assertTrue(drop.isClaimed(1)); 29 | drop.claim(merkle.getProof(data, 2), 2, bob, amount3); 30 | assertTrue(drop.isClaimed(2)); 31 | } 32 | 33 | function test_Revert_IfClaimed() public { 34 | address alice = vm.addr(1); 35 | 36 | bytes32[] memory data = new bytes32[](2); 37 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, uint256(1 ether)))))); 38 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(alice, uint256(1.5 ether)))))); 39 | drop.setup(merkle.getRoot(data)); 40 | 41 | drop.claim(merkle.getProof(data, 0), 0, alice, 1 ether); 42 | assertTrue(drop.isClaimed(0)); 43 | 44 | bytes32[] memory proof = merkle.getProof(data, 0); 45 | vm.expectRevert( 46 | abi.encodeWithSelector(IndexedDrop.AlreadyClaimed.selector, 0, abi.encode(alice, uint256(1 ether))) 47 | ); 48 | drop.claim(proof, 0, alice, 1 ether); 49 | } 50 | 51 | function test_Revert_IfInvalidClaim() public { 52 | address alice = vm.addr(1); 53 | 54 | bytes32[] memory data = new bytes32[](2); 55 | data[0] = keccak256(bytes.concat(keccak256(abi.encode(uint256(0), abi.encode(alice, uint256(1 ether)))))); 56 | data[1] = keccak256(bytes.concat(keccak256(abi.encode(uint256(1), abi.encode(alice, uint256(1.5 ether)))))); 57 | drop.setup(merkle.getRoot(data)); 58 | 59 | bytes32[] memory proof = merkle.getProof(data, 0); 60 | vm.expectRevert( 61 | abi.encodeWithSelector(IndexedDrop.InvalidClaim.selector, 0, abi.encode(alice, uint256(2 ether))) 62 | ); 63 | drop.claim(proof, 0, alice, 2 ether); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/marketplace/Participant.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; 6 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 7 | import {ILSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/ILSP7DigitalAsset.sol"; 8 | import {OwnableCallerNotTheOwner} from "@erc725/smart-contracts/contracts/errors.sol"; 9 | import { 10 | Participant, 11 | GENESIS_DISCOUNT, 12 | COLLECTOR_TIER_0_DISCOUNT, 13 | COLLECTOR_TIER_1_DISCOUNT, 14 | COLLECTOR_TIER_2_DISCOUNT, 15 | COLLECTOR_TIER_3_DISCOUNT 16 | } from "../../src/marketplace/Participant.sol"; 17 | import {deployProfile} from "../utils/profile.sol"; 18 | import {LSP7DigitalAssetMock} from "./lsp7/LSP7DigitalAssetMock.sol"; 19 | import {ICollectorIdentifiableDigitalAsset} from "../../src/assets/lsp8/ICollectorIdentifiableDigitalAsset.sol"; 20 | import {CollectorIdentifiableDigitalAsset} from "../../src/assets/lsp8/CollectorIdentifiableDigitalAsset.sol"; 21 | 22 | contract ParticipantTest is Test { 23 | event AssetFeeDiscountChanged(address indexed asset, uint32 previousDiscountPoints, uint32 newDiscountPoints); 24 | 25 | Participant participant; 26 | address admin; 27 | address owner; 28 | address assetOwner; 29 | uint256 controllerKey; 30 | address controller; 31 | LSP7DigitalAssetMock genesisAsset; 32 | CollectorIdentifiableDigitalAsset collectorAsset; 33 | 34 | function setUp() public { 35 | admin = vm.addr(1); 36 | owner = vm.addr(2); 37 | assetOwner = vm.addr(3); 38 | controllerKey = 4; 39 | controller = vm.addr(controllerKey); 40 | 41 | genesisAsset = new LSP7DigitalAssetMock("Mock", "MCK", assetOwner, 0, true); 42 | collectorAsset = 43 | new CollectorIdentifiableDigitalAsset("Universal Page Collector", "UPC", assetOwner, controller, 1000); 44 | 45 | participant = Participant( 46 | payable( 47 | address( 48 | new TransparentUpgradeableProxy( 49 | address(new Participant()), 50 | admin, 51 | abi.encodeWithSelector(Participant.initialize.selector, owner) 52 | ) 53 | ) 54 | ) 55 | ); 56 | } 57 | 58 | function test_Initialize() public { 59 | assertTrue(!participant.paused()); 60 | assertEq(owner, participant.owner()); 61 | } 62 | 63 | function test_ConfigureIfOwner() public { 64 | vm.startPrank(owner); 65 | participant.setCollectorAsset(ICollectorIdentifiableDigitalAsset(vm.addr(1))); 66 | participant.setGenesisAsset(ILSP7DigitalAsset(vm.addr(1))); 67 | participant.pause(); 68 | participant.unpause(); 69 | vm.stopPrank(); 70 | } 71 | 72 | function test_Revert_IfConfigureNotOwner() public { 73 | vm.prank(address(1)); 74 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 75 | participant.setCollectorAsset(ICollectorIdentifiableDigitalAsset(vm.addr(1))); 76 | 77 | vm.prank(address(1)); 78 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 79 | participant.setGenesisAsset(ILSP7DigitalAsset(vm.addr(1))); 80 | 81 | vm.prank(address(1)); 82 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 83 | participant.pause(); 84 | 85 | vm.prank(address(1)); 86 | vm.expectRevert(abi.encodeWithSelector(OwnableCallerNotTheOwner.selector, address(1))); 87 | participant.unpause(); 88 | } 89 | 90 | function test_Revert_WhenPaused() public { 91 | vm.prank(owner); 92 | participant.pause(); 93 | vm.expectRevert("Pausable: paused"); 94 | participant.feeDiscountFor(vm.addr(1)); 95 | } 96 | 97 | function testFuzz_GenesisFeeDiscount(uint256 tokenCount) public { 98 | vm.assume(tokenCount > 0); 99 | 100 | (UniversalProfile profile,) = deployProfile(); 101 | 102 | genesisAsset.mint(address(profile), tokenCount, false, ""); 103 | assertEq(genesisAsset.balanceOf(address(profile)), tokenCount); 104 | 105 | assertEq(participant.feeDiscountFor(address(profile)), 0); 106 | 107 | vm.prank(owner); 108 | participant.setGenesisAsset(genesisAsset); 109 | 110 | assertEq(participant.feeDiscountFor(address(profile)), GENESIS_DISCOUNT); 111 | } 112 | 113 | function test_CollectorTierFeeDiscount() public { 114 | (UniversalProfile profile,) = deployProfile(); 115 | 116 | vm.prank(owner); 117 | participant.setCollectorAsset(collectorAsset); 118 | 119 | for (uint256 i = 0; i < 4; i++) { 120 | bytes32[] memory tokenIds = new bytes32[](1); 121 | tokenIds[0] = bytes32(uint256(((i + 1) << 4) | i)); 122 | 123 | bytes32 hash = keccak256( 124 | abi.encodePacked(address(collectorAsset), block.chainid, address(profile), tokenIds, uint256(0 ether)) 125 | ); 126 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(controllerKey, hash); 127 | 128 | vm.prank(address(profile)); 129 | collectorAsset.purchase(address(profile), tokenIds, v, r, s); 130 | assertEq(collectorAsset.balanceOf(address(profile)), i + 1); 131 | 132 | uint32 discount = participant.feeDiscountFor(address(profile)); 133 | if (i == 0) { 134 | assertEq(discount, COLLECTOR_TIER_0_DISCOUNT); 135 | } else if (i == 1) { 136 | assertEq(discount, COLLECTOR_TIER_1_DISCOUNT); 137 | } else if (i == 2) { 138 | assertEq(discount, COLLECTOR_TIER_2_DISCOUNT); 139 | } else if (i == 3) { 140 | assertEq(discount, COLLECTOR_TIER_3_DISCOUNT); 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /test/marketplace/lsp7/LSP7DigitalAssetMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP7DigitalAsset} from "@lukso/lsp-smart-contracts/contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol"; 5 | 6 | contract LSP7DigitalAssetMock is LSP7DigitalAsset { 7 | constructor( 8 | string memory name_, 9 | string memory symbol_, 10 | address newOwner_, 11 | uint256 lsp4TokenType_, 12 | bool isNonDivisible_ 13 | ) LSP7DigitalAsset(name_, symbol_, newOwner_, lsp4TokenType_, isNonDivisible_) {} 14 | 15 | function mint(address to, uint256 amount, bool allowNonLSP1Recipient, bytes memory data) external { 16 | _mint(to, amount, allowNonLSP1Recipient, data); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/marketplace/lsp8/LSP8DigitalAssetMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {LSP8IdentifiableDigitalAsset} from 5 | "@lukso/lsp-smart-contracts/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAsset.sol"; 6 | 7 | contract LSP8DigitalAssetMock is LSP8IdentifiableDigitalAsset { 8 | constructor( 9 | string memory name_, 10 | string memory symbol_, 11 | address newOwner_, 12 | uint256 tokenIdType_, 13 | uint256 lsp8TokenIdSchema_ 14 | ) LSP8IdentifiableDigitalAsset(name_, symbol_, newOwner_, tokenIdType_, lsp8TokenIdSchema_) { 15 | // noop 16 | } 17 | 18 | function mint(address to, bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data) external { 19 | _mint(to, tokenId, allowNonLSP1Recipient, data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/profiles/ProfilesReverseLookup.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {LSP6KeyManager} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol"; 6 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 7 | import {LSP6Utils} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Utils.sol"; 8 | import {ALL_REGULAR_PERMISSIONS} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Constants.sol"; 9 | import {deployProfile} from "../utils/profile.sol"; 10 | import {ProfilesReverseLookup} from "../../src/profiles/ProfilesReverseLookup.sol"; 11 | 12 | contract ProfilesReverseLookupTest is Test { 13 | event ProfileRegistered(address indexed controller, address indexed profile, bytes data); 14 | event ProfileUnregistered(address indexed controller, address indexed profile, bytes data); 15 | 16 | ProfilesReverseLookup lookup; 17 | address controller; 18 | 19 | function setUp() public { 20 | controller = vm.addr(1); 21 | 22 | lookup = new ProfilesReverseLookup(); 23 | } 24 | 25 | function test_Register() public { 26 | (UniversalProfile alice, LSP6KeyManager keyManager) = deployProfile(); 27 | 28 | (bytes32[] memory keys, bytes[] memory values) = 29 | LSP6Utils.generateNewPermissionsKeys(alice, controller, ALL_REGULAR_PERMISSIONS); 30 | vm.prank(address(keyManager)); 31 | alice.setDataBatch(keys, values); 32 | 33 | assertEq(new address[](0), lookup.profilesOf(controller)); 34 | assertFalse(lookup.registered(controller, address(alice))); 35 | 36 | vm.prank(controller); 37 | vm.expectEmit(); 38 | emit ProfileRegistered(controller, address(alice), "0x"); 39 | lookup.register(controller, address(alice), "0x"); 40 | 41 | assertEq(lookup.profilesOf(controller).length, 1); 42 | assertEq(lookup.profilesOf(controller)[0], address(alice)); 43 | assertTrue(lookup.registered(controller, address(alice))); 44 | } 45 | 46 | function test_Revert_NotController() public { 47 | (UniversalProfile alice,) = deployProfile(); 48 | 49 | vm.prank(controller); 50 | vm.expectRevert( 51 | abi.encodeWithSelector(ProfilesReverseLookup.UnathorizedController.selector, controller, address(alice)) 52 | ); 53 | lookup.register(controller, address(alice), "0x"); 54 | } 55 | 56 | function test_Revert_AlreadyRegistered() public { 57 | (UniversalProfile alice, LSP6KeyManager keyManager) = deployProfile(); 58 | 59 | (bytes32[] memory keys, bytes[] memory values) = 60 | LSP6Utils.generateNewPermissionsKeys(alice, controller, ALL_REGULAR_PERMISSIONS); 61 | vm.prank(address(keyManager)); 62 | alice.setDataBatch(keys, values); 63 | 64 | vm.prank(controller); 65 | lookup.register(controller, address(alice), "0x"); 66 | 67 | vm.prank(controller); 68 | vm.expectRevert( 69 | abi.encodeWithSelector(ProfilesReverseLookup.AlreadyRegistered.selector, controller, address(alice)) 70 | ); 71 | lookup.register(controller, address(alice), "0x"); 72 | } 73 | 74 | function test_UnregisterAsController() public { 75 | (UniversalProfile alice, LSP6KeyManager keyManager) = deployProfile(); 76 | 77 | (bytes32[] memory keys, bytes[] memory values) = 78 | LSP6Utils.generateNewPermissionsKeys(alice, controller, ALL_REGULAR_PERMISSIONS); 79 | vm.prank(address(keyManager)); 80 | alice.setDataBatch(keys, values); 81 | 82 | vm.prank(controller); 83 | vm.expectEmit(); 84 | emit ProfileRegistered(controller, address(alice), "0x"); 85 | lookup.register(controller, address(alice), "0x"); 86 | 87 | assertEq(lookup.profilesOf(controller).length, 1); 88 | assertEq(lookup.profilesOf(controller)[0], address(alice)); 89 | assertTrue(lookup.registered(controller, address(alice))); 90 | 91 | vm.prank(controller); 92 | vm.expectEmit(); 93 | emit ProfileUnregistered(controller, address(alice), "0x"); 94 | lookup.unregister(controller, address(alice), "0x"); 95 | 96 | assertEq(lookup.profilesOf(controller).length, 0); 97 | assertFalse(lookup.registered(controller, address(alice))); 98 | } 99 | 100 | function test_UnregisterAsProfile() public { 101 | (UniversalProfile alice, LSP6KeyManager keyManager) = deployProfile(); 102 | 103 | (bytes32[] memory keys, bytes[] memory values) = 104 | LSP6Utils.generateNewPermissionsKeys(alice, controller, ALL_REGULAR_PERMISSIONS); 105 | vm.prank(address(keyManager)); 106 | alice.setDataBatch(keys, values); 107 | 108 | vm.prank(controller); 109 | vm.expectEmit(); 110 | emit ProfileRegistered(controller, address(alice), "0x"); 111 | lookup.register(controller, address(alice), "0x"); 112 | 113 | assertEq(lookup.profilesOf(controller).length, 1); 114 | assertEq(lookup.profilesOf(controller)[0], address(alice)); 115 | assertTrue(lookup.registered(controller, address(alice))); 116 | 117 | vm.prank(address(alice)); 118 | vm.expectEmit(); 119 | emit ProfileUnregistered(controller, address(alice), "0x"); 120 | lookup.unregister(controller, address(alice), "0x"); 121 | 122 | assertEq(lookup.profilesOf(controller).length, 0); 123 | assertFalse(lookup.registered(controller, address(alice))); 124 | } 125 | 126 | function test_Revert_UnregisterNotController() public { 127 | (UniversalProfile alice,) = deployProfile(); 128 | 129 | address pranker = vm.addr(2); 130 | 131 | vm.prank(pranker); 132 | vm.expectRevert(abi.encodeWithSelector(ProfilesReverseLookup.Unathorized.selector)); 133 | lookup.unregister(controller, address(alice), "0x"); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/utils/profile.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity =0.8.22; 3 | 4 | import {Vm} from "forge-std/Vm.sol"; 5 | import {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY} from 6 | "@lukso/lsp-smart-contracts/contracts/LSP1UniversalReceiver/LSP1Constants.sol"; 7 | import {LSP1UniversalReceiverDelegateUP} from 8 | "@lukso/lsp-smart-contracts/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol"; 9 | import { 10 | _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, 11 | _PERMISSION_REENTRANCY, 12 | _PERMISSION_SUPER_SETDATA 13 | } from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Constants.sol"; 14 | import {LSP6KeyManager} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol"; 15 | import {LSP6Utils} from "@lukso/lsp-smart-contracts/contracts/LSP6KeyManager/LSP6Utils.sol"; 16 | import {LSP2Utils} from "@lukso/lsp-smart-contracts/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol"; 17 | import {UniversalProfile} from "@lukso/lsp-smart-contracts/contracts/UniversalProfile.sol"; 18 | 19 | address constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); 20 | Vm constant vm = Vm(VM_ADDRESS); 21 | 22 | function deployProfile() returns (UniversalProfile profile, LSP6KeyManager keyManager) { 23 | address deployer = vm.addr(100); 24 | profile = new UniversalProfile(deployer); 25 | keyManager = new LSP6KeyManager(address(profile)); 26 | 27 | vm.prank(deployer); 28 | profile.transferOwnership(address(keyManager)); 29 | vm.prank(address(keyManager)); 30 | profile.acceptOwnership(); 31 | 32 | LSP1UniversalReceiverDelegateUP delegate = new LSP1UniversalReceiverDelegateUP(); 33 | 34 | vm.startPrank(address(keyManager)); 35 | profile.setData(_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY, bytes.concat(bytes20(address(delegate)))); 36 | { 37 | bytes32 key = LSP2Utils.generateMappingWithGroupingKey( 38 | _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX, bytes20(address(delegate)) 39 | ); 40 | bytes32[] memory permissions = new bytes32[](2); 41 | permissions[0] = _PERMISSION_REENTRANCY; 42 | permissions[1] = _PERMISSION_SUPER_SETDATA; 43 | profile.setData(key, abi.encodePacked(LSP6Utils.combinePermissions(permissions))); 44 | } 45 | vm.stopPrank(); 46 | } 47 | --------------------------------------------------------------------------------