diff --git a/abi/IERC20Extended.json b/abi/IERC20Extended.json new file mode 100644 index 00000000..253d8006 --- /dev/null +++ b/abi/IERC20Extended.json @@ -0,0 +1,198 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/IParanetIncentivesPool.json b/abi/IParanetIncentivesPool.json new file mode 100644 index 00000000..0ec9841b --- /dev/null +++ b/abi/IParanetIncentivesPool.json @@ -0,0 +1,15 @@ +[ + { + "inputs": [], + "name": "getParanetIncentivesPoolStorage", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abi/KnowledgeCollection.json b/abi/KnowledgeCollection.json index 5af35289..0b17b30a 100644 --- a/abi/KnowledgeCollection.json +++ b/abi/KnowledgeCollection.json @@ -189,11 +189,6 @@ "name": "ZeroAddressHub", "type": "error" }, - { - "inputs": [], - "name": "ZeroTokenAmount", - "type": "error" - }, { "inputs": [], "name": "askStorage", @@ -371,29 +366,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - }, - { - "internalType": "uint96", - "name": "tokenAmount", - "type": "uint96" - }, - { - "internalType": "address", - "name": "paymaster", - "type": "address" - } - ], - "name": "increaseKnowledgeCollectionTokenAmount", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "initialize", @@ -440,6 +412,45 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "paranetKnowledgeCollectionsRegistry", + "outputs": [ + { + "internalType": "contract ParanetKnowledgeCollectionsRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetKnowledgeMinersRegistry", + "outputs": [ + { + "internalType": "contract ParanetKnowledgeMinersRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetsRegistry", + "outputs": [ + { + "internalType": "contract ParanetsRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "paymasterManager", diff --git a/abi/Paranet.json b/abi/Paranet.json index 04ce8992..3cc4597f 100644 --- a/abi/Paranet.json +++ b/abi/Paranet.json @@ -12,200 +12,77 @@ }, { "inputs": [ - { - "internalType": "enum ParanetLib.MinersAccessPolicy[]", - "name": "expectedAccessPolicies", - "type": "uint8[]" - }, - { - "internalType": "enum ParanetLib.MinersAccessPolicy", - "name": "actualAccessPolicy", - "type": "uint8" - } - ], - "name": "InvalidParanetMinersAccessPolicy", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "enum ParanetLib.NodesAccessPolicy[]", - "name": "expectedAccessPolicies", - "type": "uint8[]" - }, - { - "internalType": "enum ParanetLib.NodesAccessPolicy", - "name": "actualAccessPolicy", - "type": "uint8" - } - ], - "name": "InvalidParanetNodesAccessPolicy", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, { "internalType": "address", - "name": "miner", + "name": "knowledgeCollectionStorageContract", "type": "address" - } - ], - "name": "ParanetCuratedMinerAccessRequestDoesntExist", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" }, { - "internalType": "address", - "name": "miner", - "type": "address" + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" }, - { - "internalType": "enum ParanetLib.RequestStatus", - "name": "status", - "type": "uint8" - } - ], - "name": "ParanetCuratedMinerAccessRequestInvalidStatus", - "type": "error" - }, - { - "inputs": [ { "internalType": "bytes32", "name": "paranetId", "type": "bytes32" - }, - { - "internalType": "address", - "name": "miner", - "type": "address" } ], - "name": "ParanetCuratedMinerDoesntExist", + "name": "KnowledgeCollectionIsAPartOfOtherParanet", "type": "error" }, { "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, { "internalType": "address", - "name": "miner", + "name": "knowledgeCollectionStorageContract", "type": "address" - } - ], - "name": "ParanetCuratedMinerHasAlreadyBeenAdded", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" - } - ], - "name": "ParanetCuratedNodeDoesntExist", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" - } - ], - "name": "ParanetCuratedNodeHasAlreadyBeenAdded", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" } ], - "name": "ParanetCuratedNodeJoinRequestDoesntExist", + "name": "KnowledgeCollectionNotInFirstEpoch", "type": "error" }, { "inputs": [ { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" + "internalType": "address", + "name": "knowledgeCollectionStorageAddress", + "type": "address" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" }, { - "internalType": "enum ParanetLib.RequestStatus", - "name": "status", - "type": "uint8" + "internalType": "uint256", + "name": "knowledgeAssetTokenId", + "type": "uint256" } ], - "name": "ParanetCuratedNodeJoinRequestInvalidStatus", + "name": "ParanetDoesntExist", "type": "error" }, { "inputs": [ { "internalType": "address", - "name": "knowledgeAssetStorageAddress", + "name": "knowledgeCollectionStorageAddress", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" - } - ], - "name": "ParanetDoesntExist", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "knowledgeAssetStorageAddress", - "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeAssetTokenId", "type": "uint256" } ], @@ -216,12 +93,17 @@ "inputs": [ { "internalType": "address", - "name": "knowledgeAssetStorageAddress", + "name": "knowledgeCollectionStorageAddress", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "knowledgeAssetTokenId", "type": "uint256" } ], @@ -248,27 +130,21 @@ "inputs": [ { "internalType": "address", - "name": "knowledgeAssetStorageAddress", + "name": "knowledgeCollectionStorageAddress", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" - } - ], - "name": "ParanetServiceHasAlreadyBeenRegistered", - "type": "error" - }, - { - "inputs": [ + }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "uint256", + "name": "knowledgeAssetTokenId", + "type": "uint256" } ], - "name": "ProfileDoesntExist", + "name": "ParanetServiceHasAlreadyBeenRegistered", "type": "error" }, { @@ -293,29 +169,35 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKCTokenId", "type": "uint256" }, { "indexed": true, + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" + }, + { + "indexed": false, "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "indexed": false, "internalType": "uint256", - "name": "knowledgeAssetTokenId", + "name": "knowledgeCollectionId", "type": "uint256" } ], - "name": "KnowledgeAssetSubmittedToParanet", + "name": "KnowledgeCollectionSubmittedToParanet", "type": "event" }, { @@ -324,9 +206,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -334,13 +222,30 @@ "type": "uint256" }, { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "storageAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardTokenAddress", + "type": "address" + } + ], "indexed": false, - "internalType": "address", - "name": "minerAddress", - "type": "address" + "internalType": "struct ParanetLib.IncentivesPool", + "name": "incentivesPool", + "type": "tuple" } ], - "name": "ParanetCuratedMinerAccessRequestAccepted", + "name": "ParanetIncetivesPoolDeployed", "type": "event" }, { @@ -349,9 +254,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -360,12 +271,18 @@ }, { "indexed": false, - "internalType": "address", - "name": "minerAddress", - "type": "address" + "internalType": "string", + "name": "newParanetName", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "newParanetDescription", + "type": "string" } ], - "name": "ParanetCuratedMinerAccessRequestCreated", + "name": "ParanetMetadataUpdated", "type": "event" }, { @@ -374,9 +291,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -390,7 +313,7 @@ "type": "address" } ], - "name": "ParanetCuratedMinerAccessRequestRejected", + "name": "ParanetPermissionedMinerAccessRequestAccepted", "type": "event" }, { @@ -399,9 +322,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -415,7 +344,7 @@ "type": "address" } ], - "name": "ParanetCuratedMinerAdded", + "name": "ParanetPermissionedMinerAccessRequestCreated", "type": "event" }, { @@ -424,9 +353,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -440,7 +375,7 @@ "type": "address" } ], - "name": "ParanetCuratedMinerRemoved", + "name": "ParanetPermissionedMinerAccessRequestRejected", "type": "event" }, { @@ -449,9 +384,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -460,12 +401,12 @@ }, { "indexed": false, - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "address", + "name": "minerAddress", + "type": "address" } ], - "name": "ParanetCuratedNodeAdded", + "name": "ParanetPermissionedMinerAdded", "type": "event" }, { @@ -474,9 +415,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -485,12 +432,12 @@ }, { "indexed": false, - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "address", + "name": "minerAddress", + "type": "address" } ], - "name": "ParanetCuratedNodeJoinRequestAccepted", + "name": "ParanetPermissionedMinerRemoved", "type": "event" }, { @@ -499,9 +446,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -515,7 +468,7 @@ "type": "uint72" } ], - "name": "ParanetCuratedNodeJoinRequestCreated", + "name": "ParanetPermissionedNodeAdded", "type": "event" }, { @@ -524,9 +477,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -540,7 +499,7 @@ "type": "uint72" } ], - "name": "ParanetCuratedNodeJoinRequestRejected", + "name": "ParanetPermissionedNodeJoinRequestAccepted", "type": "event" }, { @@ -549,9 +508,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -565,7 +530,7 @@ "type": "uint72" } ], - "name": "ParanetCuratedNodeRemoved", + "name": "ParanetPermissionedNodeJoinRequestCreated", "type": "event" }, { @@ -574,35 +539,29 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKCTokenId", "type": "uint256" - }, - { - "components": [ - { - "internalType": "string", - "name": "poolType", - "type": "string" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], + }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" + }, + { "indexed": false, - "internalType": "struct ParanetLib.IncentivesPool", - "name": "incentivesPool", - "type": "tuple" + "internalType": "uint72", + "name": "identityId", + "type": "uint72" } ], - "name": "ParanetIncetivesPoolDeployed", + "name": "ParanetPermissionedNodeJoinRequestRejected", "type": "event" }, { @@ -611,29 +570,29 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKCTokenId", "type": "uint256" }, { - "indexed": false, - "internalType": "string", - "name": "newParanetName", - "type": "string" + "indexed": true, + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" }, { "indexed": false, - "internalType": "string", - "name": "newParanetDescription", - "type": "string" + "internalType": "uint72", + "name": "identityId", + "type": "uint72" } ], - "name": "ParanetMetadataUpdated", + "name": "ParanetPermissionedNodeRemoved", "type": "event" }, { @@ -642,13 +601,19 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKCTokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "parnetKATokenId", "type": "uint256" }, { @@ -665,20 +630,20 @@ }, { "indexed": false, - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "nodesAccessPolicy", "type": "uint8" }, { "indexed": false, - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "minersAccessPolicy", "type": "uint8" }, { "indexed": false, - "internalType": "enum ParanetLib.KnowledgeAssetsAccessPolicy", - "name": "knowledgeAssetsAccessPolicy", + "internalType": "uint8", + "name": "knowledgeCollectionsSubmissionPolicy", "type": "uint8" } ], @@ -691,21 +656,33 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "indexed": true, "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKCTokenId", "type": "uint256" }, { "indexed": true, + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" + }, + { + "indexed": false, "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "indexed": false, + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "indexed": false, "internalType": "uint256", @@ -722,9 +699,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -759,9 +742,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -794,44 +783,26 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKnowledgeCollectionTokenId", "type": "uint256" }, - { - "internalType": "address[]", - "name": "minerAddresses", - "type": "address[]" - } - ], - "name": "addParanetCuratedMiners", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "paranetKAStorageContract", - "type": "address" - }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKnowledgeAssetTokenId", "type": "uint256" }, { - "internalType": "uint72[]", - "name": "identityIds", - "type": "uint72[]" + "internalType": "address", + "name": "curator", + "type": "address" } ], - "name": "addParanetCuratedNodes", + "name": "addCurator", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -840,9 +811,14 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetKATokenId", @@ -852,12 +828,17 @@ "components": [ { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "knowledgeAssetTokenId", "type": "uint256" } ], @@ -872,49 +853,57 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "paranetKAStorageContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "paranetKATokenId", - "type": "uint256" - }, + "inputs": [], + "name": "chronos", + "outputs": [ { - "internalType": "address", - "name": "minerAddress", + "internalType": "contract Chronos", + "name": "", "type": "address" } ], - "name": "approveCuratedMiner", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [ { - "internalType": "address", - "name": "paranetKAStorageContract", - "type": "address" + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "offset", "type": "uint256" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "uint256", + "name": "limit", + "type": "uint256" } ], - "name": "approveCuratedNode", - "outputs": [], - "stateMutability": "nonpayable", + "name": "getKnowledgeCollectionLocatorsWithPagination", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.UniversalAssetCollectionLocator[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", "type": "function" }, { @@ -965,10 +954,10 @@ }, { "inputs": [], - "name": "paranetKnowledgeAssetsRegistry", + "name": "paranetKnowledgeCollectionsRegistry", "outputs": [ { - "internalType": "contract ParanetKnowledgeAssetsRegistry", + "internalType": "contract ParanetKnowledgeCollectionsRegistry", "name": "", "type": "address" } @@ -1002,6 +991,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "paranetStagingRegistry", + "outputs": [ + { + "internalType": "contract ParanetStagingRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "paranetsRegistry", @@ -1032,9 +1034,14 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetKATokenId", @@ -1051,14 +1058,19 @@ "type": "string" }, { - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "nodesAccessPolicy", "type": "uint8" }, { - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "minersAccessPolicy", "type": "uint8" + }, + { + "internalType": "uint8", + "name": "knowledgeCollectionsSubmissionPolicy", + "type": "uint8" } ], "name": "registerParanet", @@ -1076,9 +1088,14 @@ "inputs": [ { "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetServiceKATokenId", @@ -1115,21 +1132,26 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKnowledgeCollectionTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "paranetKnowledgeAssetTokenId", "type": "uint256" }, { "internalType": "address", - "name": "minerAddress", + "name": "curator", "type": "address" } ], - "name": "rejectCuratedMiner", + "name": "removeCurator", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1138,44 +1160,36 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKnowledgeCollectionTokenId", "type": "uint256" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" - } - ], - "name": "rejectCuratedNode", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + "internalType": "uint256", + "name": "paranetKnowledgeAssetTokenId", + "type": "uint256" + }, { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" }, { - "internalType": "address[]", - "name": "minerAddresses", - "type": "address[]" + "internalType": "bool", + "name": "accepted", + "type": "bool" } ], - "name": "removeParanetCuratedMiners", + "name": "reviewKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1183,22 +1197,12 @@ { "inputs": [ { - "internalType": "address", - "name": "paranetKAStorageContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "paranetKATokenId", - "type": "uint256" - }, - { - "internalType": "uint72[]", - "name": "identityIds", - "type": "uint72[]" + "internalType": "bool", + "name": "_status", + "type": "bool" } ], - "name": "removeParanetCuratedNodes", + "name": "setStatus", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1207,71 +1211,93 @@ "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "paranetKnowledgeCollectionTokenId", "type": "uint256" - } - ], - "name": "requestParanetCuratedMinerAccess", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ + }, + { + "internalType": "uint256", + "name": "paranetKnowledgeAssetTokenId", + "type": "uint256" + }, { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "paranetKATokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" } ], - "name": "requestParanetCuratedNodeAccess", + "name": "stageKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "status", + "outputs": [ { "internalType": "bool", - "name": "_status", + "name": "", "type": "bool" } ], - "name": "setStatus", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { - "inputs": [], - "name": "status", - "outputs": [ + "inputs": [ { - "internalType": "bool", - "name": "", - "type": "bool" + "internalType": "address", + "name": "paranetKCStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "paranetKnowledgeCollectionTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "paranetKnowledgeAssetTokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" } ], - "stateMutability": "view", + "name": "submitKnowledgeCollection", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetKATokenId", @@ -1297,9 +1323,14 @@ "inputs": [ { "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetServiceKATokenId", diff --git a/abi/ParanetIncentivesPool.json b/abi/ParanetIncentivesPool.json new file mode 100644 index 00000000..64fd11d0 --- /dev/null +++ b/abi/ParanetIncentivesPool.json @@ -0,0 +1,562 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "knowledgeMinersRegistryAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "paranetIncentivesPoolStorageAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "paranetsRegistryAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tracToTokenEmissionMultiplier", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "uint96", + "name": "currentCumulativeWeight", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "targetCumulativeWeight", + "type": "uint96" + } + ], + "name": "InvalidCumulativeVotersWeight", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + } + ], + "name": "NoRewardAvailable", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldMultiplier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newMultiplier", + "type": "uint256" + } + ], + "name": "TokenEmissionMultiplierUpdateFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldMultiplier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newMultiplier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "TokenEmissionMultiplierUpdateInitiated", + "type": "event" + }, + { + "inputs": [], + "name": "claimIncentivizationProposalVoterReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claimKnowledgeMinerReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimParanetOperatorReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeTokenEmissionMultiplierUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimableAllKnowledgeMinersRewardAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimableAllProposalVotersRewardAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimableKnowledgeMinerRewardAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimableParanetOperatorRewardAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getClaimableProposalVoterRewardAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "getEffectiveTokenEmissionMultiplier", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getParanetIncentivesPoolStorage", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalAllKnowledgeMinersIncentiveEstimation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalAllProposalVotersIncentiveEstimation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalKnowledgeMinerIncentiveEstimation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalParanetOperatorIncentiveEstimation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalProposalVoterIncentiveEstimation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gettokenEmissionMultipliers", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "multiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + } + ], + "internalType": "struct ParanetLib.TokenEmissionMultiplier[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMultiplier", + "type": "uint256" + } + ], + "name": "initiateTokenEmissionMultiplierUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isKnowledgeMiner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isParanetOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isProposalVoter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "paranetIncentivesPoolStorage", + "outputs": [ + { + "internalType": "contract ParanetIncentivesPoolStorage", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetKnowledgeMinersRegistry", + "outputs": [ + { + "internalType": "contract ParanetKnowledgeMinersRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetsRegistry", + "outputs": [ + { + "internalType": "contract ParanetsRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenEmissionMultiplierUpdateDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenEmissionMultipliers", + "outputs": [ + { + "internalType": "uint256", + "name": "multiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "finalized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newDelay", + "type": "uint256" + } + ], + "name": "updatetokenEmissionMultiplierUpdateDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "voterclaimedToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abi/ParanetIncentivesPoolFactory.json b/abi/ParanetIncentivesPoolFactory.json index 774b2299..87a2d9c4 100644 --- a/abi/ParanetIncentivesPoolFactory.json +++ b/abi/ParanetIncentivesPoolFactory.json @@ -13,44 +13,61 @@ { "inputs": [ { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, "internalType": "address", - "name": "knowledgeAssetStorageAddress", + "name": "paranetKCStorageContract", "type": "address" }, { + "indexed": true, "internalType": "uint256", - "name": "tokenId", + "name": "paranetKCTokenId", "type": "uint256" }, { - "internalType": "string", - "name": "poolType", - "type": "string" + "indexed": true, + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" }, { + "indexed": false, + "internalType": "address", + "name": "storageAddress", + "type": "address" + }, + { + "indexed": false, "internalType": "address", "name": "poolAddress", "type": "address" - } - ], - "name": "ParanetIncentivesPoolAlreadyExists", - "type": "error" - }, - { - "inputs": [ + }, { - "internalType": "string", - "name": "msg", - "type": "string" + "indexed": false, + "internalType": "address", + "name": "rewardTokenAddress", + "type": "address" } ], - "name": "UnauthorizedAccess", - "type": "error" - }, - { - "inputs": [], - "name": "ZeroAddressHub", - "type": "error" + "name": "ParanetIncentivesPoolDeployed", + "type": "event" }, { "anonymous": false, @@ -58,9 +75,15 @@ { "indexed": true, "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "indexed": true, + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "indexed": true, "internalType": "uint256", @@ -68,34 +91,33 @@ "type": "uint256" }, { - "components": [ - { - "internalType": "string", - "name": "poolType", - "type": "string" - }, - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], "indexed": false, - "internalType": "struct ParanetLib.IncentivesPool", - "name": "incentivesPool", - "type": "tuple" + "internalType": "address", + "name": "storageAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newPoolAddress", + "type": "address" } ], - "name": "ParanetIncetivesPoolDeployed", + "name": "ParanetIncentivesPoolRedeployed", "type": "event" }, { "inputs": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetKATokenId", @@ -103,7 +125,7 @@ }, { "internalType": "uint256", - "name": "tracToNeuroEmissionMultiplier", + "name": "tracToTokenEmissionMultiplier", "type": "uint256" }, { @@ -115,16 +137,20 @@ "internalType": "uint16", "name": "paranetIncentivizationProposalVotersRewardPercentage", "type": "uint16" - } - ], - "name": "deployNeuroIncentivesPool", - "outputs": [ + }, + { + "internalType": "string", + "name": "incentivesPoolName", + "type": "string" + }, { "internalType": "address", - "name": "", + "name": "rewardTokenAddress", "type": "address" } ], + "name": "deployIncentivesPool", + "outputs": [], "stateMutability": "nonpayable", "type": "function" }, @@ -161,6 +187,19 @@ "stateMutability": "pure", "type": "function" }, + { + "inputs": [], + "name": "paranetIncentivesPoolFactoryHelper", + "outputs": [ + { + "internalType": "contract ParanetIncentivesPoolFactoryHelper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "paranetsRegistry", @@ -174,6 +213,34 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "paranetKCStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "paranetKATokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "storageAddress", + "type": "address" + } + ], + "name": "redeployIncentivesPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/abi/ParanetIncentivesPoolFactoryHelper.json b/abi/ParanetIncentivesPoolFactoryHelper.json new file mode 100644 index 00000000..6c139382 --- /dev/null +++ b/abi/ParanetIncentivesPoolFactoryHelper.json @@ -0,0 +1,123 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "storageAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tracToTokenEmissionMultiplier", + "type": "uint256" + }, + { + "internalType": "address", + "name": "poolStorageAddress", + "type": "address" + } + ], + "name": "deployIncentivesPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "status", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/ParanetNeuroIncentivesPool.json b/abi/ParanetIncentivesPoolStorage.json similarity index 56% rename from abi/ParanetNeuroIncentivesPool.json rename to abi/ParanetIncentivesPoolStorage.json index 1677d836..798c2524 100644 --- a/abi/ParanetNeuroIncentivesPool.json +++ b/abi/ParanetIncentivesPoolStorage.json @@ -8,24 +8,14 @@ }, { "internalType": "address", - "name": "paranetsRegistryAddress", - "type": "address" - }, - { - "internalType": "address", - "name": "knowledgeMinersRegistryAddress", + "name": "rewardTokenAddress", "type": "address" }, { "internalType": "bytes32", - "name": "paranetId", + "name": "paranetId_", "type": "bytes32" }, - { - "internalType": "uint256", - "name": "tracToNeuroEmissionMultiplier", - "type": "uint256" - }, { "internalType": "uint16", "name": "paranetOperatorRewardPercentage_", @@ -43,159 +33,539 @@ { "inputs": [ { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "name": "IncentivesPoolAddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "miner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "additionalAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotal", + "type": "uint256" + } + ], + "name": "MinerRewardIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "miner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MinerRewardProfileAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "additionalAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotal", + "type": "uint256" + } + ], + "name": "OperatorRewardIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "OperatorRewardProfileAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RewardTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOrigin", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOrigin", + "type": "address" + } + ], + "name": "TokenOriginSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TokenRewardDeposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotal", + "type": "uint256" + } + ], + "name": "TotalMinersclaimedTokenDecremented", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldAmount", + "type": "uint256" }, { + "indexed": false, + "internalType": "uint256", + "name": "newAmount", + "type": "uint256" + } + ], + "name": "TotalMinersclaimedTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotal", + "type": "uint256" + } + ], + "name": "TotalOperatorsclaimedTokenDecremented", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newAmount", + "type": "uint256" + } + ], + "name": "TotalOperatorsclaimedTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newTotal", + "type": "uint256" + } + ], + "name": "TotalVotersclaimedTokenDecremented", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newAmount", + "type": "uint256" + } + ], + "name": "TotalVotersclaimedTokenSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "weight", + "type": "uint16" + } + ], + "name": "VoterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "weight", + "type": "uint96" + } + ], + "name": "VoterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VoterRewardClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, "internalType": "uint96", - "name": "currentCumulativeWeight", + "name": "oldWeight", "type": "uint96" }, { + "indexed": false, "internalType": "uint96", - "name": "targetCumulativeWeight", + "name": "newWeight", "type": "uint96" } ], - "name": "InvalidCumulativeVotersWeight", - "type": "error" + "name": "VoterWeightUpdated", + "type": "event" }, { + "anonymous": false, "inputs": [ { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" + "indexed": true, + "internalType": "address", + "name": "previousRegistrar", + "type": "address" }, { + "indexed": true, "internalType": "address", - "name": "claimer", + "name": "newRegistrar", "type": "address" } ], - "name": "NoRewardAvailable", - "type": "error" + "name": "VotersRegistrarTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "name": "VotersRemoved", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "claimableTokenReward", + "type": "uint256" + } + ], + "name": "addClaimedOperatorReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "claimableTokenReward", + "type": "uint256" + } + ], + "name": "addMinerClaimedReward", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": false, - "internalType": "uint256", - "name": "oldMultiplier", - "type": "uint256" + "internalType": "address", + "name": "addr", + "type": "address" }, { - "indexed": false, "internalType": "uint256", - "name": "newMultiplier", + "name": "claimableTokenReward", "type": "uint256" } ], - "name": "NeuroEmissionMultiplierUpdateFinalized", - "type": "event" + "name": "addMinerClaimedRewardProfile", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": false, - "internalType": "uint256", - "name": "oldMultiplier", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "newMultiplier", - "type": "uint256" + "internalType": "address", + "name": "addr", + "type": "address" }, { - "indexed": false, "internalType": "uint256", - "name": "timestamp", + "name": "claimableTokenReward", "type": "uint256" } ], - "name": "NeuroEmissionMultiplierUpdateInitiated", - "type": "event" + "name": "addOperatorClaimedRewardsProfile", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "NeuroRewardDeposit", - "type": "event" + "name": "addTotalMinersclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "voter", - "type": "address" - }, - { - "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "ParanetIncentivizationProposalVoterRewardClaimed", - "type": "event" + "name": "addTotalOperatorsclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "miner", - "type": "address" - }, - { - "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "ParanetKnowledgeMinerRewardClaimed", - "type": "event" + "name": "addTotalVotersclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "anonymous": false, "inputs": [ { - "indexed": true, "internalType": "address", - "name": "operator", + "name": "voter", "type": "address" }, { - "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" } ], - "name": "ParanetOperatorRewardClaimed", - "type": "event" + "name": "addVoterClaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { "inputs": [ @@ -222,27 +592,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [], - "name": "claimIncentivizationProposalVoterReward", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "claimKnowledgeMinerReward", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "claimParanetOperatorReward", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -260,7 +609,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -303,7 +652,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -334,17 +683,49 @@ "name": "cumulativeVotersWeight", "outputs": [ { - "internalType": "uint16", + "internalType": "uint96", "name": "", - "type": "uint16" + "type": "uint96" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [], - "name": "finalizeNeuroEmissionMultiplierUpdate", + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decrementTotalMinersclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decrementTotalOperatorsclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "decrementTotalVotersclaimedToken", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -362,7 +743,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -387,7 +768,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -401,7 +782,7 @@ }, { "inputs": [], - "name": "getClaimableAllKnowledgeMinersRewardAmount", + "name": "getBalance", "outputs": [ { "internalType": "uint256", @@ -413,21 +794,39 @@ "type": "function" }, { - "inputs": [], - "name": "getClaimableAllProposalVotersRewardAmount", - "outputs": [ + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "index", "type": "uint256" } ], + "name": "getClaimedMinerRewardsAtIndex", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "claimedToken", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile", + "name": "", + "type": "tuple" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "getClaimableKnowledgeMinerRewardAmount", + "name": "getClaimedMinerRewardsLength", "outputs": [ { "internalType": "uint256", @@ -439,21 +838,39 @@ "type": "function" }, { - "inputs": [], - "name": "getClaimableParanetOperatorRewardAmount", - "outputs": [ + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "index", "type": "uint256" } ], + "name": "getClaimedOperatorRewardsAtIndex", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "claimedToken", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile", + "name": "", + "type": "tuple" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "getClaimableProposalVoterRewardAmount", + "name": "getClaimedOperatorRewardsLength", "outputs": [ { "internalType": "uint256", @@ -468,15 +885,37 @@ "inputs": [ { "internalType": "uint256", - "name": "timestamp", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", "type": "uint256" } ], - "name": "getEffectiveNeuroEmissionMultiplier", + "name": "getPaginatedClaimedMinerRewards", "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "claimedToken", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[]", + "name": "rewards", + "type": "tuple[]" + }, { "internalType": "uint256", - "name": "", + "name": "total", "type": "uint256" } ], @@ -484,55 +923,40 @@ "type": "function" }, { - "inputs": [], - "name": "getNeuroBalance", - "outputs": [ + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", "type": "uint256" } ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getNeuroEmissionMultipliers", + "name": "getPaginatedClaimedOperatorRewards", "outputs": [ { "components": [ { - "internalType": "uint256", - "name": "multiplier", - "type": "uint256" + "internalType": "address", + "name": "addr", + "type": "address" }, { "internalType": "uint256", - "name": "timestamp", + "name": "claimedToken", "type": "uint256" - }, - { - "internalType": "bool", - "name": "finalized", - "type": "bool" } ], - "internalType": "struct ParanetLib.NeuroEmissionMultiplier[]", - "name": "", + "internalType": "struct ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[]", + "name": "rewards", "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalAllKnowledgeMinersIncentiveEstimation", - "outputs": [ + }, { "internalType": "uint256", - "name": "", + "name": "total", "type": "uint256" } ], @@ -540,25 +964,45 @@ "type": "function" }, { - "inputs": [], - "name": "getTotalAllProposalVotersIncentiveEstimation", - "outputs": [ + "inputs": [ { "internalType": "uint256", - "name": "", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", "type": "uint256" } ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalKnowledgeMinerIncentiveEstimation", + "name": "getPaginatedVoters", "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint96", + "name": "weight", + "type": "uint96" + }, + { + "internalType": "uint256", + "name": "claimedToken", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.ParanetIncentivizationProposalVoter[]", + "name": "votersList", + "type": "tuple[]" + }, { "internalType": "uint256", - "name": "", + "name": "total", "type": "uint256" } ], @@ -566,26 +1010,36 @@ "type": "function" }, { - "inputs": [], - "name": "getTotalParanetOperatorIncentiveEstimation", - "outputs": [ + "inputs": [ { - "internalType": "uint256", - "name": "", - "type": "uint256" + "internalType": "address", + "name": "voterAddress", + "type": "address" } ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getTotalProposalVoterIncentiveEstimation", + "name": "getVoter", "outputs": [ { - "internalType": "uint256", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint96", + "name": "weight", + "type": "uint96" + }, + { + "internalType": "uint256", + "name": "claimedToken", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.ParanetIncentivizationProposalVoter", "name": "", - "type": "uint256" + "type": "tuple" } ], "stateMutability": "view", @@ -594,12 +1048,12 @@ { "inputs": [ { - "internalType": "address", - "name": "voterAddress", - "type": "address" + "internalType": "uint256", + "name": "index", + "type": "uint256" } ], - "name": "getVoter", + "name": "getVoterAtIndex", "outputs": [ { "components": [ @@ -615,7 +1069,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -645,7 +1099,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], @@ -684,14 +1138,8 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "uint256", - "name": "newMultiplier", - "type": "uint256" - } - ], - "name": "initiateNeuroEmissionMultiplierUpdate", + "inputs": [], + "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -704,7 +1152,7 @@ "type": "address" } ], - "name": "isKnowledgeMiner", + "name": "isProposalVoter", "outputs": [ { "internalType": "bool", @@ -719,49 +1167,43 @@ "inputs": [ { "internalType": "address", - "name": "addr", + "name": "minerAddress", "type": "address" } ], - "name": "isParanetOperator", + "name": "minerclaimedToken", "outputs": [ { - "internalType": "bool", + "internalType": "uint256", "name": "", - "type": "bool" + "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "isProposalVoter", + "inputs": [], + "name": "name", "outputs": [ { - "internalType": "bool", + "internalType": "string", "name": "", - "type": "bool" + "type": "string" } ], - "stateMutability": "view", + "stateMutability": "pure", "type": "function" }, { "inputs": [ { "internalType": "address", - "name": "minerAddress", + "name": "operatorAddress", "type": "address" } ], - "name": "minerClaimedNeuro", + "name": "operatorclaimedToken", "outputs": [ { "internalType": "uint256", @@ -774,54 +1216,64 @@ }, { "inputs": [], - "name": "name", + "name": "paranetId", "outputs": [ { - "internalType": "string", + "internalType": "bytes32", "name": "", - "type": "string" + "type": "bytes32" } ], - "stateMutability": "pure", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "neuroEmissionMultiplierUpdateDelay", + "name": "paranetIncentivesPoolAddress", "outputs": [ { - "internalType": "uint256", + "internalType": "address", "name": "", - "type": "uint256" + "type": "address" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "paranetIncentivizationProposalVotersRewardPercentage", + "outputs": [ { - "internalType": "uint256", + "internalType": "uint16", "name": "", - "type": "uint256" + "type": "uint16" } ], - "name": "neuroEmissionMultipliers", + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetOperatorRewardPercentage", "outputs": [ { - "internalType": "uint256", - "name": "multiplier", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - }, + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paranetsRegistry", + "outputs": [ { - "internalType": "bool", - "name": "finalized", - "type": "bool" + "internalType": "contract ParanetsRegistry", + "name": "", + "type": "address" } ], "stateMutability": "view", @@ -831,66 +1283,99 @@ "inputs": [ { "internalType": "address", - "name": "operatorAddress", + "name": "voterAddress", "type": "address" } ], - "name": "operatorClaimedNeuro", - "outputs": [ + "name": "removeVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { - "internalType": "uint256", - "name": "", - "type": "uint256" + "internalType": "address[]", + "name": "votersToRemove", + "type": "address[]" } ], - "stateMutability": "view", + "name": "removeVoters", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "paranetIncentivizationProposalVotersRewardPercentage", - "outputs": [ + "inputs": [ { - "internalType": "uint16", - "name": "", - "type": "uint16" + "internalType": "address", + "name": "_paranetIncentivesPoolAddress", + "type": "address" } ], - "stateMutability": "view", + "name": "setParanetIncentivesPool", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "paranetKnowledgeMinersRegistry", - "outputs": [ + "inputs": [ { - "internalType": "contract ParanetKnowledgeMinersRegistry", - "name": "", + "internalType": "address", + "name": "newOrigin", "type": "address" } ], - "stateMutability": "view", + "name": "setTokenOrigin", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [], - "name": "paranetOperatorRewardPercentage", - "outputs": [ + "inputs": [ { - "internalType": "uint16", - "name": "", - "type": "uint16" + "internalType": "uint256", + "name": "amount", + "type": "uint256" } ], - "stateMutability": "view", + "name": "setTotalMinersclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setTotalOperatorsclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "setTotalVotersclaimedToken", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], - "name": "paranetsRegistry", + "name": "token", "outputs": [ { - "internalType": "contract ParanetsRegistry", + "internalType": "contract IERC20", "name": "", "type": "address" } @@ -900,33 +1385,33 @@ }, { "inputs": [], - "name": "parentParanetId", + "name": "tokenOrigin", "outputs": [ { - "internalType": "bytes32", + "internalType": "address", "name": "", - "type": "bytes32" + "type": "address" } ], "stateMutability": "view", "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "totalMinersclaimedToken", + "outputs": [ { "internalType": "uint256", - "name": "limit", + "name": "", "type": "uint256" } ], - "name": "removeVoters", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "totalMinersClaimedNeuro", + "name": "totalOperatorsclaimedToken", "outputs": [ { "internalType": "uint256", @@ -939,7 +1424,7 @@ }, { "inputs": [], - "name": "totalNeuroReceived", + "name": "totalReceived", "outputs": [ { "internalType": "uint256", @@ -952,7 +1437,7 @@ }, { "inputs": [], - "name": "totalOperatorsClaimedNeuro", + "name": "totalVotersclaimedToken", "outputs": [ { "internalType": "uint256", @@ -964,16 +1449,21 @@ "type": "function" }, { - "inputs": [], - "name": "totalVotersClaimedNeuro", - "outputs": [ + "inputs": [ + { + "internalType": "address", + "name": "rewardAddress", + "type": "address" + }, { "internalType": "uint256", - "name": "", + "name": "amount", "type": "uint256" } ], - "stateMutability": "view", + "name": "transferReward", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { @@ -992,12 +1482,17 @@ { "inputs": [ { - "internalType": "uint256", - "name": "newDelay", - "type": "uint256" + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "uint96", + "name": "newWeight", + "type": "uint96" } ], - "name": "updateNeuroEmissionMultiplierUpdateDelay", + "name": "updateVoterWeight", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1015,25 +1510,6 @@ "stateMutability": "pure", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "voterAddress", - "type": "address" - } - ], - "name": "voterClaimedNeuro", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -1056,7 +1532,7 @@ }, { "internalType": "uint256", - "name": "claimedNeuro", + "name": "claimedToken", "type": "uint256" } ], diff --git a/abi/ParanetKnowledgeAssetsRegistry.json b/abi/ParanetKnowledgeCollectionsRegistry.json similarity index 74% rename from abi/ParanetKnowledgeAssetsRegistry.json rename to abi/ParanetKnowledgeCollectionsRegistry.json index 817138b6..dea39e71 100644 --- a/abi/ParanetKnowledgeAssetsRegistry.json +++ b/abi/ParanetKnowledgeCollectionsRegistry.json @@ -35,12 +35,12 @@ }, { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" }, { @@ -49,7 +49,7 @@ "type": "address" } ], - "name": "addKnowledgeAsset", + "name": "addKnowledgeCollection", "outputs": [ { "internalType": "bytes32", @@ -64,11 +64,11 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "getKnowledgeAssetLocator", + "name": "getKnowledgeCollectionLocator", "outputs": [ { "internalType": "address", @@ -84,26 +84,57 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "knowledgeCollectionIds", + "type": "bytes32[]" + } + ], + "name": "getKnowledgeCollectionLocators", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "knowledgeCollectionStorageContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "knowledgeCollectionTokenId", + "type": "uint256" + } + ], + "internalType": "struct ParanetLib.UniversalAssetCollectionLocator[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "getKnowledgeAssetObject", + "name": "getKnowledgeCollectionObject", "outputs": [ { "components": [ { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", "type": "uint256" }, { @@ -117,7 +148,7 @@ "type": "bytes32" } ], - "internalType": "struct ParanetLib.KnowledgeAsset", + "internalType": "struct ParanetLib.KnowledgeCollection", "name": "", "type": "tuple" } @@ -129,7 +160,7 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], @@ -148,7 +179,7 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], @@ -180,11 +211,11 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "isParanetKnowledgeAsset", + "name": "isParanetKnowledgeCollection", "outputs": [ { "internalType": "bool", @@ -212,11 +243,11 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "removeKnowledgeAsset", + "name": "removeKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -225,7 +256,7 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" }, { @@ -243,7 +274,7 @@ "inputs": [ { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" }, { diff --git a/abi/ParanetKnowledgeMinersRegistry.json b/abi/ParanetKnowledgeMinersRegistry.json index f78c7e52..5aa6397f 100644 --- a/abi/ParanetKnowledgeMinersRegistry.json +++ b/abi/ParanetKnowledgeMinersRegistry.json @@ -26,29 +26,6 @@ "name": "ZeroAddressHub", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "miner", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "addedCumulativeAwardedNeuro", - "type": "uint256" - } - ], - "name": "addCumulativeAwardedNeuro", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -86,11 +63,11 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "addSubmittedKnowledgeAsset", + "name": "addSubmittedKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -150,17 +127,17 @@ }, { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionId", "type": "uint256" }, { "internalType": "bytes32", - "name": "assertionId", + "name": "merkleRoot", "type": "bytes32" }, { @@ -169,7 +146,7 @@ "type": "uint96" } ], - "name": "addUpdatingKnowledgeAssetState", + "name": "addUpdatingKnowledgeCollectionState", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -188,7 +165,7 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetStateId", + "name": "knowledgeCollectionStateId", "type": "bytes32" }, { @@ -197,7 +174,7 @@ "type": "uint96" } ], - "name": "addUpdatingKnowledgeAssetUpdateTokenAmount", + "name": "addUpdatingKnowledgeCollectionUpdateTokenAmount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -208,9 +185,19 @@ "internalType": "address", "name": "miner", "type": "address" + }, + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "addedcumulativeAwardedToken", + "type": "uint256" } ], - "name": "decrementTotalSubmittedKnowledgeAssetsCount", + "name": "addcumulativeAwardedToken", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -223,7 +210,7 @@ "type": "address" } ], - "name": "deleteKnowledgeMiner", + "name": "decrementTotalSubmittedKnowledgeCollectionsCount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -234,22 +221,11 @@ "internalType": "address", "name": "miner", "type": "address" - }, - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" } ], - "name": "getCumulativeAwardedNeuro", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", + "name": "deleteKnowledgeMiner", + "outputs": [], + "stateMutability": "nonpayable", "type": "function" }, { @@ -300,7 +276,7 @@ }, { "internalType": "uint256", - "name": "totalSubmittedKnowledgeAssetsCount", + "name": "totalSubmittedKnowledgeCollectionsCount", "type": "uint256" } ], @@ -325,7 +301,7 @@ "type": "bytes32" } ], - "name": "getSubmittedKnowledgeAssets", + "name": "getSubmittedKnowledgeCollections", "outputs": [ { "internalType": "bytes32[]", @@ -359,7 +335,7 @@ "type": "uint256" } ], - "name": "getSubmittedKnowledgeAssets", + "name": "getSubmittedKnowledgeCollections", "outputs": [ { "internalType": "bytes32[]", @@ -378,7 +354,7 @@ "type": "address" } ], - "name": "getTotalSubmittedKnowledgeAssetsCount", + "name": "getTotalSubmittedKnowledgeCollectionsCount", "outputs": [ { "internalType": "uint256", @@ -443,35 +419,25 @@ "internalType": "bytes32", "name": "paranetId", "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "start", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "end", - "type": "uint256" } ], - "name": "getUpdatingKnowledgeAssetStates", + "name": "getUpdatingKnowledgeCollectionStates", "outputs": [ { "components": [ { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionId", "type": "uint256" }, { "internalType": "bytes32", - "name": "assertionId", + "name": "merkleRoot", "type": "bytes32" }, { @@ -480,7 +446,7 @@ "type": "uint96" } ], - "internalType": "struct ParanetLib.UpdatingKnowledgeAssetState[]", + "internalType": "struct ParanetLib.UpdatingKnowledgeCollectionState[]", "name": "", "type": "tuple[]" } @@ -499,25 +465,35 @@ "internalType": "bytes32", "name": "paranetId", "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "start", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "end", + "type": "uint256" } ], - "name": "getUpdatingKnowledgeAssetStates", + "name": "getUpdatingKnowledgeCollectionStates", "outputs": [ { "components": [ { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionId", "type": "uint256" }, { "internalType": "bytes32", - "name": "assertionId", + "name": "merkleRoot", "type": "bytes32" }, { @@ -526,7 +502,7 @@ "type": "uint96" } ], - "internalType": "struct ParanetLib.UpdatingKnowledgeAssetState[]", + "internalType": "struct ParanetLib.UpdatingKnowledgeCollectionState[]", "name": "", "type": "tuple[]" } @@ -534,6 +510,30 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "miner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getcumulativeAwardedToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "hub", @@ -555,7 +555,7 @@ "type": "address" } ], - "name": "incrementTotalSubmittedKnowledgeAssetsCount", + "name": "incrementTotalSubmittedKnowledgeCollectionsCount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -639,57 +639,11 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetId", - "type": "bytes32" - } - ], - "name": "removeSubmittedKnowledgeAsset", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "miner", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "knowledgeAssetStateId", - "type": "bytes32" - } - ], - "name": "removeUpdatingKnowledgeAssetState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "miner", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "paranetId", + "name": "knowledgeCollectionId", "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "cumulativeAwardedNeuro", - "type": "uint256" } ], - "name": "setCumulativeAwardedNeuro", + "name": "removeSubmittedKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -726,11 +680,11 @@ }, { "internalType": "uint256", - "name": "totalSubmittedKnowledgeAssetsCount", + "name": "totalSubmittedKnowledgeCollectionsCount", "type": "uint256" } ], - "name": "setTotalSubmittedKnowledgeAssetsCount", + "name": "setTotalSubmittedKnowledgeCollectionsCount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -790,7 +744,7 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetStateId", + "name": "knowledgeCollectionStateId", "type": "bytes32" }, { @@ -799,7 +753,7 @@ "type": "uint96" } ], - "name": "setUpdatingKnowledgeAssetUpdateTokenAmount", + "name": "setUpdatingKnowledgeCollectionUpdateTokenAmount", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -818,11 +772,11 @@ }, { "internalType": "uint256", - "name": "subtractedCumulativeAwardedNeuro", + "name": "cumulativeAwardedToken", "type": "uint256" } ], - "name": "subCumulativeAwardedNeuro", + "name": "setcumulativeAwardedToken", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -905,7 +859,7 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetStateId", + "name": "knowledgeCollectionStateId", "type": "bytes32" }, { @@ -914,7 +868,30 @@ "type": "uint96" } ], - "name": "subUpdatingKnowledgeAssetUpdateTokenAmount", + "name": "subUpdatingKnowledgeCollectionUpdateTokenAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "miner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "subtractedcumulativeAwardedToken", + "type": "uint256" + } + ], + "name": "subcumulativeAwardedToken", "outputs": [], "stateMutability": "nonpayable", "type": "function" diff --git a/abi/ParanetServicesRegistry.json b/abi/ParanetServicesRegistry.json index 7bdde7e6..3501f264 100644 --- a/abi/ParanetServicesRegistry.json +++ b/abi/ParanetServicesRegistry.json @@ -104,13 +104,18 @@ "type": "bytes32" } ], - "name": "getParanetServiceKnowledgeAssetLocator", + "name": "getParanetServiceKnowledgeCollectionLocator", "outputs": [ { "internalType": "address", "name": "", "type": "address" }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, { "internalType": "uint256", "name": "", @@ -134,9 +139,14 @@ "components": [ { "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetServiceKATokenId", @@ -239,9 +249,14 @@ "inputs": [ { "internalType": "address", - "name": "paranetServiceKAStorageContract", + "name": "paranetServiceKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetServiceKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetServiceKATokenId", diff --git a/abi/ParanetStagingRegistry.json b/abi/ParanetStagingRegistry.json new file mode 100644 index 00000000..3fa71f09 --- /dev/null +++ b/abi/ParanetStagingRegistry.json @@ -0,0 +1,548 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "hubAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "msg", + "type": "string" + } + ], + "name": "UnauthorizedAccess", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddressHub", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "curator", + "type": "address" + } + ], + "name": "CuratorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "curator", + "type": "address" + } + ], + "name": "CuratorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bool", + "name": "accepted", + "type": "bool" + } + ], + "name": "KnowledgeCollectionReviewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "submitter", + "type": "address" + } + ], + "name": "KnowledgeCollectionStaged", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "curator", + "type": "address" + } + ], + "name": "addCurator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "collectionSubmitters", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "curators", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getAllParanetCurators", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + } + ], + "name": "getKnowledgeCollectionStatus", + "outputs": [ + { + "internalType": "enum ParanetLib.RequestStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + } + ], + "name": "getKnowledgeCollectionSubmitter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "getPendingCollections", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "submitter", + "type": "address" + }, + { + "internalType": "enum ParanetLib.RequestStatus", + "name": "status", + "type": "uint8" + } + ], + "internalType": "struct ParanetStagingRegistry.StagedCollection[]", + "name": "collections", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hub", + "outputs": [ + { + "internalType": "contract Hub", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "isCurator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + } + ], + "name": "isKnowledgeCollectionApproved", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + } + ], + "name": "isKnowledgeCollectionStaged", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "paranetCuratorIndexes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "paranetCurators", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "curator", + "type": "address" + } + ], + "name": "removeCurator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + }, + { + "internalType": "bool", + "name": "accepted", + "type": "bool" + } + ], + "name": "reviewKnowledgeCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "submitter", + "type": "address" + } + ], + "name": "stageKnowledgeCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "stagedCollections", + "outputs": [ + { + "internalType": "enum ParanetLib.RequestStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/abi/ParanetsRegistry.json b/abi/ParanetsRegistry.json index 438a9fbc..b3abdb9e 100644 --- a/abi/ParanetsRegistry.json +++ b/abi/ParanetsRegistry.json @@ -10,55 +10,6 @@ "stateMutability": "nonpayable", "type": "constructor" }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "AddressAlreadyInSet", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "AddressDoesNotExist", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "name": "ContractAlreadyExists", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - } - ], - "name": "ContractDoesNotExist", - "type": "error" - }, - { - "inputs": [], - "name": "EmptyName", - "type": "error" - }, { "inputs": [ { @@ -70,11 +21,6 @@ "name": "UnauthorizedAccess", "type": "error" }, - { - "inputs": [], - "name": "ZeroAddress", - "type": "error" - }, { "inputs": [], "name": "ZeroAddressHub", @@ -106,17 +52,22 @@ "type": "bytes32" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "string", + "name": "incentivesPoolName", + "type": "string" }, { - "internalType": "bytes", - "name": "nodeId", - "type": "bytes" + "internalType": "address", + "name": "storageAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardTokenAddress", + "type": "address" } ], - "name": "addCuratedNode", + "name": "addIncentivesPool", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -130,11 +81,11 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "addKnowledgeAsset", + "name": "addKnowledgeCollecton", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -203,6 +154,29 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + }, + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes", + "name": "nodeId", + "type": "bytes" + } + ], + "name": "addPermissionedNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -253,11 +227,16 @@ }, { "internalType": "address", - "name": "addr", + "name": "storageAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardTokenAddress", "type": "address" } ], - "internalType": "struct UnorderedNamedContractDynamicSet.Contract[]", + "internalType": "struct ParanetLib.IncentivesPool[]", "name": "", "type": "tuple[]" } @@ -266,50 +245,13 @@ "type": "function" }, { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - } - ], - "name": "getCumulativeKnowledgeValue", - "outputs": [ - { - "internalType": "uint96", - "name": "", - "type": "uint96" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - } - ], - "name": "getCuratedNodes", + "inputs": [], + "name": "getAllParanetIds", "outputs": [ { - "components": [ - { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" - }, - { - "internalType": "bytes", - "name": "nodeId", - "type": "bytes" - } - ], - "internalType": "struct ParanetLib.Node[]", + "internalType": "bytes32[]", "name": "", - "type": "tuple[]" + "type": "bytes32[]" } ], "stateMutability": "view", @@ -323,12 +265,12 @@ "type": "bytes32" } ], - "name": "getCuratedNodesCount", + "name": "getCumulativeKnowledgeValue", "outputs": [ { - "internalType": "uint256", + "internalType": "uint96", "name": "", - "type": "uint256" + "type": "uint96" } ], "stateMutability": "view", @@ -362,16 +304,33 @@ }, { "internalType": "string", - "name": "incentivesPoolType", + "name": "poolName", "type": "string" } ], - "name": "getIncentivesPoolAddress", + "name": "getIncentivesPoolByPoolName", "outputs": [ { - "internalType": "address", + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "storageAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardTokenAddress", + "type": "address" + } + ], + "internalType": "struct ParanetLib.IncentivesPool", "name": "", - "type": "address" + "type": "tuple" } ], "stateMutability": "view", @@ -383,14 +342,36 @@ "internalType": "bytes32", "name": "paranetId", "type": "bytes32" + }, + { + "internalType": "address", + "name": "storageAddr", + "type": "address" } ], - "name": "getKnowledgeAssets", + "name": "getIncentivesPoolByStorageAddress", "outputs": [ { - "internalType": "bytes32[]", + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "storageAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "rewardTokenAddress", + "type": "address" + } + ], + "internalType": "struct ParanetLib.IncentivesPool", "name": "", - "type": "bytes32[]" + "type": "tuple" } ], "stateMutability": "view", @@ -404,12 +385,12 @@ "type": "bytes32" } ], - "name": "getKnowledgeAssetsAccessPolicy", + "name": "getKnowledgeCollections", "outputs": [ { - "internalType": "enum ParanetLib.KnowledgeAssetsAccessPolicy", + "internalType": "bytes32[]", "name": "", - "type": "uint8" + "type": "bytes32[]" } ], "stateMutability": "view", @@ -423,7 +404,7 @@ "type": "bytes32" } ], - "name": "getKnowledgeAssetsCount", + "name": "getKnowledgeCollectionsCount", "outputs": [ { "internalType": "uint256", @@ -443,7 +424,7 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" }, { @@ -452,7 +433,7 @@ "type": "uint256" } ], - "name": "getKnowledgeAssetsStartingFromKnowledgeAssetId", + "name": "getKnowledgeCollectionsStartingFromKnowlCollectionId", "outputs": [ { "internalType": "bytes32[]", @@ -463,6 +444,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getKnowledgeCollectionsSubmissionPolicy", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -481,7 +481,7 @@ "type": "uint256" } ], - "name": "getKnowledgeAssetsWithPagination", + "name": "getKnowledgeCollectionsWithPagination", "outputs": [ { "internalType": "bytes32[]", @@ -754,7 +754,7 @@ "name": "getMinersAccessPolicy", "outputs": [ { - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "", "type": "uint8" } @@ -913,7 +913,7 @@ "name": "getNodesAccessPolicy", "outputs": [ { - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "", "type": "uint8" } @@ -921,6 +921,68 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getParanetIdAtIndex", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "getParanetIds", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getParanetIdsMapping", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -936,6 +998,11 @@ "name": "", "type": "address" }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, { "internalType": "uint256", "name": "", @@ -959,9 +1026,14 @@ "components": [ { "internalType": "address", - "name": "paranetKAStorageContract", + "name": "paranetKCStorageContract", "type": "address" }, + { + "internalType": "uint256", + "name": "paranetKCTokenId", + "type": "uint256" + }, { "internalType": "uint256", "name": "paranetKATokenId", @@ -978,18 +1050,18 @@ "type": "string" }, { - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "nodesAccessPolicy", "type": "uint8" }, { - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "minersAccessPolicy", "type": "uint8" }, { - "internalType": "enum ParanetLib.KnowledgeAssetsAccessPolicy", - "name": "knowledgeAssetsAccessPolicy", + "internalType": "uint8", + "name": "knowledgeCollectionsSubmissionPolicy", "type": "uint8" }, { @@ -1006,6 +1078,69 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getParanetsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getPermissionedNodes", + "outputs": [ + { + "components": [ + { + "internalType": "uint72", + "name": "identityId", + "type": "uint72" + }, + { + "internalType": "bytes", + "name": "nodeId", + "type": "bytes" + } + ], + "internalType": "struct ParanetLib.Node[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paranetId", + "type": "bytes32" + } + ], + "name": "getPermissionedNodesCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -1052,12 +1187,12 @@ "type": "bytes32" }, { - "internalType": "address", - "name": "incentivesPoolAddress", - "type": "address" + "internalType": "string", + "name": "poolName", + "type": "string" } ], - "name": "hasIncentivesPoolByAddress", + "name": "hasIncentivesPoolByName", "outputs": [ { "internalType": "bool", @@ -1076,12 +1211,12 @@ "type": "bytes32" }, { - "internalType": "string", - "name": "incentivesPoolType", - "type": "string" + "internalType": "address", + "name": "storageAddr", + "type": "address" } ], - "name": "hasIncentivesPoolByType", + "name": "hasIncentivesPoolByStorageAddress", "outputs": [ { "internalType": "bool", @@ -1113,12 +1248,12 @@ "type": "bytes32" }, { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" + "internalType": "bytes32", + "name": "knowledgeCollectionId", + "type": "bytes32" } ], - "name": "isCuratedNode", + "name": "isKnowledgeCollectionRegistered", "outputs": [ { "internalType": "bool", @@ -1137,12 +1272,12 @@ "type": "bytes32" }, { - "internalType": "bytes32", - "name": "knowledgeAssetId", - "type": "bytes32" + "internalType": "address", + "name": "knowledgeMinerAddress", + "type": "address" } ], - "name": "isKnowledgeAssetRegistered", + "name": "isKnowledgeMinerRegistered", "outputs": [ { "internalType": "bool", @@ -1161,12 +1296,12 @@ "type": "bytes32" }, { - "internalType": "address", - "name": "knowledgeMinerAddress", - "type": "address" + "internalType": "uint72", + "name": "identityId", + "type": "uint72" } ], - "name": "isKnowledgeMinerRegistered", + "name": "isPermissionedNode", "outputs": [ { "internalType": "bool", @@ -1237,12 +1372,17 @@ "inputs": [ { "internalType": "address", - "name": "knowledgeAssetStorageContract", + "name": "knowledgeCollectionStorageContract", "type": "address" }, { "internalType": "uint256", - "name": "tokenId", + "name": "knowledgeCollectionTokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "knowledgeAssetTokenId", "type": "uint256" }, { @@ -1256,18 +1396,18 @@ "type": "string" }, { - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "nodesAccessPolicy", "type": "uint8" }, { - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "minersAccessPolicy", "type": "uint8" }, { - "internalType": "enum ParanetLib.KnowledgeAssetsAccessPolicy", - "name": "knowledgeAssetsAccessPolicy", + "internalType": "uint8", + "name": "knowledgeCollectionsSubmissionPolicy", "type": "uint8" } ], @@ -1282,60 +1422,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "uint72", - "name": "identityId", - "type": "uint72" - } - ], - "name": "removeCuratedNode", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "incentivesPoolAddress", - "type": "address" - } - ], - "name": "removeIncentivesPool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "incentivesPoolType", - "type": "string" - } - ], - "name": "removeIncentivesPool", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1345,11 +1431,11 @@ }, { "internalType": "bytes32", - "name": "knowledgeAssetId", + "name": "knowledgeCollectionId", "type": "bytes32" } ], - "name": "removeKnowledgeAsset", + "name": "removeKnowledgeCollection", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1426,12 +1512,12 @@ "type": "bytes32" }, { - "internalType": "bytes32", - "name": "serviceId", - "type": "bytes32" + "internalType": "uint72", + "name": "identityId", + "type": "uint72" } ], - "name": "removeService", + "name": "removePermissionedNode", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1444,12 +1530,12 @@ "type": "bytes32" }, { - "internalType": "uint96", - "name": "cumulativeKnowledgeValue", - "type": "uint96" + "internalType": "bytes32", + "name": "serviceId", + "type": "bytes32" } ], - "name": "setCumulativeKnowledgeValue", + "name": "removeService", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1462,12 +1548,12 @@ "type": "bytes32" }, { - "internalType": "string", - "name": "description", - "type": "string" + "internalType": "uint96", + "name": "cumulativeKnowledgeValue", + "type": "uint96" } ], - "name": "setDescription", + "name": "setCumulativeKnowledgeValue", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1481,16 +1567,11 @@ }, { "internalType": "string", - "name": "incentivesPoolType", + "name": "description", "type": "string" - }, - { - "internalType": "address", - "name": "incentivesPoolAddress", - "type": "address" } ], - "name": "setIncentivesPoolAddress", + "name": "setDescription", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1503,12 +1584,12 @@ "type": "bytes32" }, { - "internalType": "enum ParanetLib.KnowledgeAssetsAccessPolicy", - "name": "knowledgeAssetsAccessPolicy", + "internalType": "uint8", + "name": "knowledgeCollectionsSubmissionPolicy", "type": "uint8" } ], - "name": "setKnowledgeAssetsAccessPolicy", + "name": "setKnowledgeCollectionsSubmissionPolicy", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1521,7 +1602,7 @@ "type": "bytes32" }, { - "internalType": "enum ParanetLib.MinersAccessPolicy", + "internalType": "uint8", "name": "minersAccessPolicy", "type": "uint8" } @@ -1557,7 +1638,7 @@ "type": "bytes32" }, { - "internalType": "enum ParanetLib.NodesAccessPolicy", + "internalType": "uint8", "name": "nodesAccessPolicy", "type": "uint8" } @@ -1585,29 +1666,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "paranetId", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "incentivesPoolType", - "type": "string" - }, - { - "internalType": "address", - "name": "incentivesPoolAddress", - "type": "address" - } - ], - "name": "updateIncentivesPoolAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { diff --git a/contracts/KnowledgeCollection.sol b/contracts/KnowledgeCollection.sol index 3304b454..a6546b51 100644 --- a/contracts/KnowledgeCollection.sol +++ b/contracts/KnowledgeCollection.sol @@ -10,6 +10,9 @@ import {KnowledgeCollectionStorage} from "./storage/KnowledgeCollectionStorage.s import {ShardingTableStorage} from "./storage/ShardingTableStorage.sol"; import {IdentityStorage} from "./storage/IdentityStorage.sol"; import {ParametersStorage} from "./storage/ParametersStorage.sol"; +import {ParanetKnowledgeCollectionsRegistry} from "./storage/paranets/ParanetKnowledgeCollectionsRegistry.sol"; +import {ParanetKnowledgeMinersRegistry} from "./storage/paranets/ParanetKnowledgeMinersRegistry.sol"; +import {ParanetsRegistry} from "./storage/paranets/ParanetsRegistry.sol"; import {KnowledgeCollectionLib} from "./libraries/KnowledgeCollectionLib.sol"; import {TokenLib} from "./libraries/TokenLib.sol"; import {IdentityLib} from "./libraries/IdentityLib.sol"; @@ -28,6 +31,9 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab AskStorage public askStorage; EpochStorage public epochStorage; PaymasterManager public paymasterManager; + ParanetKnowledgeCollectionsRegistry public paranetKnowledgeCollectionsRegistry; + ParanetKnowledgeMinersRegistry public paranetKnowledgeMinersRegistry; + ParanetsRegistry public paranetsRegistry; KnowledgeCollectionStorage public knowledgeCollectionStorage; Chronos public chronos; ShardingTableStorage public shardingTableStorage; @@ -41,6 +47,13 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab askStorage = AskStorage(hub.getContractAddress("AskStorage")); epochStorage = EpochStorage(hub.getContractAddress("EpochStorageV8")); paymasterManager = PaymasterManager(hub.getContractAddress("PaymasterManager")); + paranetKnowledgeCollectionsRegistry = ParanetKnowledgeCollectionsRegistry( + hub.getContractAddress("ParanetKnowledgeCollectionsRegistry") + ); + paranetKnowledgeMinersRegistry = ParanetKnowledgeMinersRegistry( + hub.getContractAddress("ParanetKnowledgeMinersRegistry") + ); + paranetsRegistry = ParanetsRegistry(hub.getContractAddress("ParanetsRegistry")); knowledgeCollectionStorage = KnowledgeCollectionStorage( hub.getAssetStorageAddress("KnowledgeCollectionStorage") ); @@ -166,6 +179,25 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab // es.addTokensToEpochRange(1, currentEpoch, endEpoch, tokenAmount); // es.addEpochProducedKnowledgeValue(publisherNodeIdentityId, currentEpoch, tokenAmount); + // _addTokens(tokenAmount, paymaster); + + // ParanetKnowledgeCollectionsRegistry pkar = paranetKnowledgeCollectionsRegistry; + + // bytes32 knowledgeCollectionId = pkar.getParanetId(keccak256(abi.encodePacked(address(kcs), id))); + // if (pkar.isParanetKnowledgeCollection(knowledgeCollectionId)) { + // ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + // bytes32 paranetId = paranetKnowledgeCollectionsRegistry.getParanetId(knowledgeCollectionId); + + // // Add Knowledge Asset Token Amount Metadata to the ParanetsRegistry + // paranetsRegistry.addCumulativeKnowledgeValue(paranetId, tokenAmount); + + // // Add Knowledge Asset Token Amount Metadata to the KnowledgeMinersRegistry + // pkmr.addCumulativeTracSpent(msg.sender, paranetId, tokenAmount); + // pkmr.addUnrewardedTracSpent(msg.sender, paranetId, tokenAmount); + // pkmr.addTotalTracSpent(msg.sender, tokenAmount); + // pkmr.addUpdatingKnowledgeCollectionState(msg.sender, paranetId, address(kcs), id, merkleRoot, tokenAmount); + // } + // } // _addTokens(tokenAmount, paymaster); // } @@ -192,27 +224,22 @@ contract KnowledgeCollection is INamed, IVersioned, ContractStatus, IInitializab epochStorage.addTokensToEpochRange(1, endEpoch, endEpoch + epochs, tokenAmount); _addTokens(tokenAmount, paymaster); - } - function increaseKnowledgeCollectionTokenAmount(uint256 id, uint96 tokenAmount, address paymaster) external { - if (tokenAmount == 0) { - revert TokenLib.ZeroTokenAmount(); - } + ParanetKnowledgeCollectionsRegistry pkar = paranetKnowledgeCollectionsRegistry; - KnowledgeCollectionStorage kcs = knowledgeCollectionStorage; + bytes32 knowledgeCollectionId = pkar.getParanetId(keccak256(abi.encodePacked(address(kcs), id))); + if (pkar.isParanetKnowledgeCollection(knowledgeCollectionId)) { + ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + bytes32 paranetId = paranetKnowledgeCollectionsRegistry.getParanetId(knowledgeCollectionId); - (, , , , , uint40 endEpoch, uint96 oldTokenAmount, ) = kcs.getKnowledgeCollectionMetadata(id); + // Add Knowledge Asset Token Amount Metadata to the ParanetsRegistry + paranetsRegistry.addCumulativeKnowledgeValue(paranetId, tokenAmount); - uint256 currentEpoch = chronos.getCurrentEpoch(); - if (currentEpoch > endEpoch) { - revert KnowledgeCollectionLib.KnowledgeCollectionExpired(id, currentEpoch, endEpoch); + // Add Knowledge Asset Token Amount Metadata to the KnowledgeMinersRegistry + pkmr.addCumulativeTracSpent(msg.sender, paranetId, tokenAmount); + pkmr.addUnrewardedTracSpent(msg.sender, paranetId, tokenAmount); + pkmr.addTotalTracSpent(msg.sender, tokenAmount); } - - kcs.setTokenAmount(id, oldTokenAmount + tokenAmount); - - epochStorage.addTokensToEpochRange(1, currentEpoch, endEpoch, tokenAmount); - - _addTokens(tokenAmount, paymaster); } function _verifySignatures( diff --git a/contracts/interfaces/IERC20Extended.sol b/contracts/interfaces/IERC20Extended.sol new file mode 100644 index 00000000..4d7f5af7 --- /dev/null +++ b/contracts/interfaces/IERC20Extended.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IERC20Extended is IERC20 { + function decimals() external view returns (uint8); +} diff --git a/contracts/interfaces/IParanetIncentivesPool.sol b/contracts/interfaces/IParanetIncentivesPool.sol new file mode 100644 index 00000000..50b24209 --- /dev/null +++ b/contracts/interfaces/IParanetIncentivesPool.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +interface IParanetIncentivesPool { + function getParanetIncentivesPoolStorage() external view returns (address); +} diff --git a/contracts/libraries/ParanetLib.sol b/contracts/libraries/ParanetLib.sol index 8c5c4c18..94031323 100644 --- a/contracts/libraries/ParanetLib.sol +++ b/contracts/libraries/ParanetLib.sol @@ -10,23 +10,15 @@ library ParanetLib { uint16 constant PERCENTAGE_SCALING_FACTOR = 10 ** 4; uint16 constant MAX_CUMULATIVE_VOTERS_WEIGHT = 10 ** 4; - struct UniversalAssetLocator { - address knowledgeAssetStorageContract; - uint256 tokenId; - } - - enum NodesAccessPolicy { - OPEN, - CURATED + struct UniversalAssetCollectionLocator { + address knowledgeCollectionStorageContract; + uint256 knowledgeCollectionTokenId; } - enum MinersAccessPolicy { - OPEN, - CURATED - } - - enum KnowledgeAssetsAccessPolicy { - OPEN + struct UniversalAssetLocator { + address knowledgeCollectionStorageContract; + uint256 knowledgeCollectionTokenId; + uint256 knowledgeAssetTokenId; } struct Node { @@ -55,19 +47,30 @@ library ParanetLib { RequestStatus status; } + struct IncentivesPool { + string name; + address storageAddr; + address rewardTokenAddress; + } + struct Paranet { - address paranetKAStorageContract; + address paranetKCStorageContract; + uint256 paranetKCTokenId; uint256 paranetKATokenId; string name; string description; - NodesAccessPolicy nodesAccessPolicy; - MinersAccessPolicy minersAccessPolicy; - KnowledgeAssetsAccessPolicy knowledgeAssetsAccessPolicy; + uint8 nodesAccessPolicy; + uint8 minersAccessPolicy; + uint8 knowledgeCollectionsSubmissionPolicy; uint96 cumulativeKnowledgeValue; - UnorderedNamedContractDynamicSet.Set incentivesPools; - Node[] curatedNodes; + IncentivesPool[] incentivesPools; + // Incentives Pool Name => Index in the array + mapping(string => uint256) incentivesPoolsByNameIndexes; + // Incentives Pool Storage Address => Index in the array + mapping(address => uint256) incentivesPoolsByStorageAddressIndexes; + Node[] permissionedNodes; // Identity ID => Index in the array - mapping(uint72 => uint256) curatedNodesIndexes; + mapping(uint72 => uint256) permissionedNodesIndexes; // Identity ID => Requests Array mapping(uint72 => ParanetNodeJoinRequest[]) paranetNodeJoinRequests; bytes32[] services; @@ -78,29 +81,26 @@ library ParanetLib { mapping(address => uint256) registeredKnowledgeMinersIndexes; // Knowledge Miner address => Requests Array mapping(address => ParanetKnowledgeMinerAccessRequest[]) paranetKnowledgeMinerAccessRequests; - bytes32[] knowledgeAssets; - // Knowledge Asset ID => Index in the array - mapping(bytes32 => uint256) registeredKnowledgeAssetsIndexes; + bytes32[] knowledgeCollections; + // Knowledge Collection ID => Index in the array + mapping(bytes32 => uint256) registeredKnowledgeCollectionsIndexes; } struct ParanetMetadata { - address paranetKAStorageContract; + address paranetKCStorageContract; + uint256 paranetKCTokenId; uint256 paranetKATokenId; string name; string description; - NodesAccessPolicy nodesAccessPolicy; - MinersAccessPolicy minersAccessPolicy; - KnowledgeAssetsAccessPolicy knowledgeAssetsAccessPolicy; + uint8 nodesAccessPolicy; + uint8 minersAccessPolicy; + uint8 knowledgeCollectionsSubmissionPolicy; uint96 cumulativeKnowledgeValue; } - struct IncentivesPool { - string poolType; - address addr; - } - struct ParanetService { - address paranetServiceKAStorageContract; + address paranetServiceKCStorageContract; + uint256 paranetServiceKCTokenId; uint256 paranetServiceKATokenId; string name; string description; @@ -109,7 +109,8 @@ library ParanetLib { } struct ParanetServiceMetadata { - address paranetServiceKAStorageContract; + address paranetServiceKCStorageContract; + uint256 paranetServiceKCTokenId; uint256 paranetServiceKATokenId; string name; string description; @@ -119,37 +120,37 @@ library ParanetLib { struct KnowledgeMiner { address addr; uint96 totalTracSpent; - uint256 totalSubmittedKnowledgeAssetsCount; - mapping(bytes32 => bytes32[]) submittedKnowledgeAssets; - mapping(bytes32 => mapping(bytes32 => uint256)) submittedKnowledgeAssetsIndexes; - mapping(bytes32 => UpdatingKnowledgeAssetState[]) updatingKnowledgeAssetStates; - mapping(bytes32 => mapping(bytes32 => uint256)) updatingKnowledgeAssetStateIndexes; + uint256 totalSubmittedKnowledgeCollectionsCount; + mapping(bytes32 => bytes32[]) submittedKnowledgeCollections; + mapping(bytes32 => mapping(bytes32 => uint256)) submittedKnowledgeCollectionsIndexes; + mapping(bytes32 => UpdatingKnowledgeCollectionState[]) updatingKnowledgeCollectionsStates; + mapping(bytes32 => mapping(bytes32 => uint256)) updatingKnowledgeCollectionsStateIndexes; mapping(bytes32 => uint96) cumulativeTracSpent; mapping(bytes32 => uint96) unrewardedTracSpent; - mapping(bytes32 => uint256) cumulativeAwardedNeuro; + mapping(bytes32 => uint256) cumulativeAwardedToken; } struct KnowledgeMinerMetadata { address addr; uint96 totalTracSpent; - uint256 totalSubmittedKnowledgeAssetsCount; + uint256 totalSubmittedKnowledgeCollectionsCount; } - struct KnowledgeAsset { - address knowledgeAssetStorageContract; - uint256 tokenId; + struct KnowledgeCollection { + address knowledgeCollectionStorageContract; + uint256 knowledgeCollectionTokenId; address minerAddress; bytes32 paranetId; } - struct UpdatingKnowledgeAssetState { - address knowledgeAssetStorageContract; - uint256 tokenId; - bytes32 assertionId; + struct UpdatingKnowledgeCollectionState { + address knowledgeCollectionStorageContract; + uint256 knowledgeCollectionId; + bytes32 merkleRoot; uint96 updateTokenAmount; } - struct NeuroEmissionMultiplier { + struct TokenEmissionMultiplier { uint256 multiplier; uint256 timestamp; bool finalized; @@ -157,7 +158,7 @@ library ParanetLib { struct ParanetIncentivesPoolClaimedRewardsProfile { address addr; - uint256 claimedNeuro; + uint256 claimedToken; } struct ParanetIncentivizationProposalVoterInput { @@ -168,46 +169,57 @@ library ParanetLib { struct ParanetIncentivizationProposalVoter { address addr; uint96 weight; - uint256 claimedNeuro; + uint256 claimedToken; } - error ParanetHasAlreadyBeenRegistered(address knowledgeAssetStorageAddress, uint256 tokenId); - error InvalidParanetNodesAccessPolicy( - ParanetLib.NodesAccessPolicy[] expectedAccessPolicies, - ParanetLib.NodesAccessPolicy actualAccessPolicy + error ParanetHasAlreadyBeenRegistered( + address knowledgeCollectionStorageAddress, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId ); - error ParanetCuratedNodeHasAlreadyBeenAdded(bytes32 paranetId, uint72 identityId); - error ParanetCuratedNodeDoesntExist(bytes32 paranetId, uint72 identityId); - error ParanetCuratedNodeJoinRequestInvalidStatus( + error InvalidParanetNodesAccessPolicy(uint8[] expectedAccessPolicies, uint8 actualAccessPolicy); + error ParanetPermissionedNodeHasAlreadyBeenAdded(bytes32 paranetId, uint72 identityId); + error ParanetPermissionedNodeDoesntExist(bytes32 paranetId, uint72 identityId); + error ParanetPermissionedNodeJoinRequestInvalidStatus( bytes32 paranetId, uint72 identityId, ParanetLib.RequestStatus status ); - error ParanetCuratedNodeJoinRequestDoesntExist(bytes32 paranetId, uint72 identityId); - error InvalidParanetMinersAccessPolicy( - ParanetLib.MinersAccessPolicy[] expectedAccessPolicies, - ParanetLib.MinersAccessPolicy actualAccessPolicy - ); - error ParanetCuratedMinerHasAlreadyBeenAdded(bytes32 paranetId, address miner); - error ParanetCuratedMinerDoesntExist(bytes32 paranetId, address miner); - error ParanetCuratedMinerAccessRequestInvalidStatus( + error ParanetPermissionedNodeJoinRequestDoesntExist(bytes32 paranetId, uint72 identityId); + error InvalidParanetMinersAccessPolicy(uint8[] expectedAccessPolicies, uint8 actualAccessPolicy); + error ParanetPermissionedMinerHasAlreadyBeenAdded(bytes32 paranetId, address miner); + error ParanetPermissionedMinerDoesntExist(bytes32 paranetId, address miner); + error ParanetPermissionedMinerAccessRequestInvalidStatus( bytes32 paranetId, address miner, ParanetLib.RequestStatus status ); - error ParanetCuratedMinerAccessRequestDoesntExist(bytes32 paranetId, address miner); + error ParanetPermissionedMinerAccessRequestDoesntExist(bytes32 paranetId, address miner); error ParanetIncentivesPoolAlreadyExists( - address knowledgeAssetStorageAddress, - uint256 tokenId, + address knowledgeCollectionStorageAddress, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId, string poolType, address poolAddress ); - error ParanetDoesntExist(address knowledgeAssetStorageAddress, uint256 tokenId); - error ParanetServiceHasAlreadyBeenRegistered(address knowledgeAssetStorageAddress, uint256 tokenId); - error ParanetServiceDoesntExist(address knowledgeAssetStorageAddress, uint256 tokenId); - error KnowledgeAssetIsAPartOfOtherParanet( - address paranetKnowledgeAssetStorageContract, - uint256 paranetTokenId, + error ParanetDoesntExist( + address knowledgeCollectionStorageAddress, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ); + error ParanetServiceHasAlreadyBeenRegistered( + address knowledgeCollectionStorageAddress, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ); + error ParanetServiceDoesntExist( + address knowledgeCollectionStorageAddress, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ); + error KnowledgeCollectionIsAPartOfAParanet( + address paranetKnowledgeCollectionStorageAddress, + uint256 paranetKnowledgeCollectionTokenId, bytes32 paranetId ); error NoRewardAvailable(bytes32 paranetId, address claimer); @@ -217,4 +229,14 @@ library ParanetLib { uint96 currentCumulativeWeight, uint96 targetCumulativeWeight ); + error KnowledgeCollectionNotInFirstEpoch( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ); + + error KnowledgeCollectionIsAPartOfOtherParanet( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + bytes32 paranetId + ); } diff --git a/contracts/paranets/Paranet.sol b/contracts/paranets/Paranet.sol index 884e6257..124fa45e 100644 --- a/contracts/paranets/Paranet.sol +++ b/contracts/paranets/Paranet.sol @@ -2,114 +2,145 @@ pragma solidity ^0.8.20; -import {ParanetKnowledgeAssetsRegistry} from "../storage/paranets/ParanetKnowledgeAssetsRegistry.sol"; +import {ParanetKnowledgeCollectionsRegistry} from "../storage/paranets/ParanetKnowledgeCollectionsRegistry.sol"; import {ParanetKnowledgeMinersRegistry} from "../storage/paranets/ParanetKnowledgeMinersRegistry.sol"; import {ParanetsRegistry} from "../storage/paranets/ParanetsRegistry.sol"; import {ParanetServicesRegistry} from "../storage/paranets/ParanetServicesRegistry.sol"; +import {ParanetStagingRegistry} from "../storage/paranets/ParanetStagingRegistry.sol"; import {ProfileStorage} from "../storage/ProfileStorage.sol"; import {IdentityStorage} from "../storage/IdentityStorage.sol"; +import {KnowledgeCollectionStorage} from "../storage/KnowledgeCollectionStorage.sol"; +import {Chronos} from "../storage/Chronos.sol"; import {ContractStatus} from "../abstract/ContractStatus.sol"; import {IInitializable} from "../interfaces/IInitializable.sol"; import {INamed} from "../interfaces/INamed.sol"; import {IVersioned} from "../interfaces/IVersioned.sol"; import {ParanetLib} from "../libraries/ParanetLib.sol"; import {ProfileLib} from "../libraries/ProfileLib.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {KnowledgeCollectionLib} from "../libraries/KnowledgeCollectionLib.sol"; contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { + // Access Policy Constants + uint8 private constant NODES_ACCESS_POLICY_OPEN = 0; + uint8 private constant NODES_ACCESS_POLICY_PERMISSIONED = 1; + + uint8 private constant MINERS_ACCESS_POLICY_OPEN = 0; + uint8 private constant MINERS_ACCESS_POLICY_PERMISSIONED = 1; + + uint8 private constant KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_OPEN = 0; + uint8 private constant KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING = 1; + event ParanetRegistered( - address indexed paranetKAStorageContract, - uint256 indexed paranetKATokenId, + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, + uint256 indexed parnetKATokenId, string paranetName, string paranetDescription, - ParanetLib.NodesAccessPolicy nodesAccessPolicy, - ParanetLib.MinersAccessPolicy minersAccessPolicy, - ParanetLib.KnowledgeAssetsAccessPolicy knowledgeAssetsAccessPolicy + uint8 nodesAccessPolicy, + uint8 minersAccessPolicy, + uint8 knowledgeCollectionsSubmissionPolicy ); - event ParanetCuratedNodeAdded( - address indexed paranetKAStorageContract, + event ParanetPermissionedNodeAdded( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, uint72 identityId ); - event ParanetCuratedNodeRemoved( - address indexed paranetKAStorageContract, + event ParanetPermissionedNodeRemoved( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, uint72 identityId ); - event ParanetCuratedNodeJoinRequestCreated( - address indexed paranetKAStorageContract, + event ParanetPermissionedNodeJoinRequestCreated( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, uint72 identityId ); - event ParanetCuratedNodeJoinRequestAccepted( - address indexed paranetKAStorageContract, + event ParanetPermissionedNodeJoinRequestAccepted( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, uint72 identityId ); - event ParanetCuratedNodeJoinRequestRejected( - address indexed paranetKAStorageContract, + event ParanetPermissionedNodeJoinRequestRejected( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, uint72 identityId ); event ParanetIncetivesPoolDeployed( - address indexed paranetKAStorageContract, + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, ParanetLib.IncentivesPool incentivesPool ); event ParanetMetadataUpdated( - address indexed paranetKAStorageContract, + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, string newParanetName, string newParanetDescription ); event ParanetServiceAdded( - address indexed paranetKAStorageContract, + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, - address indexed paranetServiceKAStorageContract, + address paranetServiceKCStorageContract, + uint256 paranetServiceKCTokenId, uint256 paranetServiceKATokenId ); event ParanetServiceRegistered( - address indexed paranetServiceKAStorageContract, + address indexed paranetServiceKCStorageContract, + uint256 indexed paranetServiceKCTokenId, uint256 indexed paranetServiceKATokenId, string paranetServiceName, string paranetServiceDescription, address[] paranetServiceAddresses ); event ParanetServiceMetadataUpdated( - address indexed paranetServiceKAStorageContract, + address indexed paranetServiceKCStorageContract, + uint256 indexed paranetServiceKCTokenId, uint256 indexed paranetServiceKATokenId, string newParanetServiceName, string newParanetServiceDescription, address[] newParanetServiceAddresses ); - event KnowledgeAssetSubmittedToParanet( - address indexed paranetKAStorageContract, + event KnowledgeCollectionSubmittedToParanet( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, - address indexed knowledgeAssetStorageContract, - uint256 knowledgeAssetTokenId + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionId ); - event ParanetCuratedMinerAdded( - address indexed paranetKAStorageContract, + event ParanetPermissionedMinerAdded( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, address minerAddress ); - event ParanetCuratedMinerRemoved( - address indexed paranetKAStorageContract, + event ParanetPermissionedMinerRemoved( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, address minerAddress ); - event ParanetCuratedMinerAccessRequestCreated( - address indexed paranetKAStorageContract, + event ParanetPermissionedMinerAccessRequestCreated( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, address minerAddress ); - event ParanetCuratedMinerAccessRequestAccepted( - address indexed paranetKAStorageContract, + event ParanetPermissionedMinerAccessRequestAccepted( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, address minerAddress ); - event ParanetCuratedMinerAccessRequestRejected( - address indexed paranetKAStorageContract, + event ParanetPermissionedMinerAccessRequestRejected( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, address minerAddress ); @@ -120,15 +151,42 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { ParanetsRegistry public paranetsRegistry; ParanetServicesRegistry public paranetServicesRegistry; ParanetKnowledgeMinersRegistry public paranetKnowledgeMinersRegistry; - ParanetKnowledgeAssetsRegistry public paranetKnowledgeAssetsRegistry; + ParanetKnowledgeCollectionsRegistry public paranetKnowledgeCollectionsRegistry; + ParanetStagingRegistry public paranetStagingRegistry; ProfileStorage public profileStorage; IdentityStorage public identityStorage; + Chronos public chronos; // solhint-disable-next-line no-empty-blocks constructor(address hubAddress) ContractStatus(hubAddress) {} - modifier onlyKnowledgeAssetOwner(address knowledgeAssetStorageContract, uint256 knowledgeAssetTokenId) { - _checkKnowledgeAssetOwner(knowledgeAssetStorageContract, knowledgeAssetTokenId); + modifier onlyKnowledgeAssetOwner( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ) { + _checkKnowledgeAssetOwner( + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + knowledgeAssetTokenId + ); + _; + } + + modifier onlyKnowledgeCollectionOwner( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ) { + _checkKnowledgeCollectionOwner(knowledgeCollectionStorageContract, knowledgeCollectionTokenId); + _; + } + + modifier onlyCurator( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId + ) { + _checkCurator(paranetKCStorageContract, paranetKnowledgeCollectionTokenId, paranetKnowledgeAssetTokenId); _; } @@ -140,9 +198,11 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { paranetKnowledgeMinersRegistry = ParanetKnowledgeMinersRegistry( hub.getContractAddress("ParanetKnowledgeMinersRegistry") ); - paranetKnowledgeAssetsRegistry = ParanetKnowledgeAssetsRegistry( - hub.getContractAddress("ParanetKnowledgeAssetsRegistry") + paranetKnowledgeCollectionsRegistry = ParanetKnowledgeCollectionsRegistry( + hub.getContractAddress("ParanetKnowledgeCollectionsRegistry") ); + paranetStagingRegistry = ParanetStagingRegistry(hub.getContractAddress("ParanetStagingRegistry")); + chronos = Chronos(hub.getContractAddress("Chronos")); } function name() external pure virtual override returns (string memory) { @@ -154,342 +214,422 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { } function registerParanet( - address paranetKAStorageContract, + address paranetKCStorageContract, + uint256 paranetKCTokenId, uint256 paranetKATokenId, string calldata paranetName, string calldata paranetDescription, - ParanetLib.NodesAccessPolicy nodesAccessPolicy, - ParanetLib.MinersAccessPolicy minersAccessPolicy - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) returns (bytes32) { + uint8 nodesAccessPolicy, + uint8 minersAccessPolicy, + uint8 knowledgeCollectionsSubmissionPolicy + ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) returns (bytes32) { + require( + nodesAccessPolicy < 1 && minersAccessPolicy < 1 && knowledgeCollectionsSubmissionPolicy < 2, + "Invalid policy" + ); ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); if (pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetHasAlreadyBeenRegistered(paranetKAStorageContract, paranetKATokenId); + revert ParanetLib.ParanetHasAlreadyBeenRegistered( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId + ); } emit ParanetRegistered( - paranetKAStorageContract, + paranetKCStorageContract, + paranetKCTokenId, paranetKATokenId, paranetName, paranetDescription, nodesAccessPolicy, minersAccessPolicy, - ParanetLib.KnowledgeAssetsAccessPolicy.OPEN + knowledgeCollectionsSubmissionPolicy ); return pr.registerParanet( - paranetKAStorageContract, + paranetKCStorageContract, + paranetKCTokenId, paranetKATokenId, paranetName, paranetDescription, nodesAccessPolicy, minersAccessPolicy, - ParanetLib.KnowledgeAssetsAccessPolicy.OPEN + knowledgeCollectionsSubmissionPolicy ); } function updateParanetMetadata( - address paranetKAStorageContract, + address paranetKCStorageContract, + uint256 paranetKCTokenId, uint256 paranetKATokenId, string calldata paranetName, string calldata paranetDescription - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { + ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); + revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); } pr.setName(paranetId, paranetName); pr.setDescription(paranetId, paranetDescription); - emit ParanetMetadataUpdated(paranetKAStorageContract, paranetKATokenId, paranetName, paranetDescription); + emit ParanetMetadataUpdated( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetName, + paranetDescription + ); } - function addParanetCuratedNodes( - address paranetKAStorageContract, - uint256 paranetKATokenId, - uint72[] calldata identityIds - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; - ProfileStorage ps = profileStorage; + // function addParanetPermissionedNodes( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // uint72[] calldata identityIds + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; + // ProfileStorage ps = profileStorage; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getNodesAccessPolicy(paranetId) != ParanetLib.NodesAccessPolicy.CURATED) { - ParanetLib.NodesAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.NodesAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.NodesAccessPolicy.CURATED; + // if (pr.getNodesAccessPolicy(paranetId) != NODES_ACCESS_POLICY_PERMISSIONED) { + // // TODO: Why is this 1 element array + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = NODES_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetNodesAccessPolicy( - expectedAccessPolicies, - pr.getNodesAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetNodesAccessPolicy( + // expectedAccessPolicies, + // pr.getNodesAccessPolicy(paranetId) + // ); + // } - for (uint256 i; i < identityIds.length; ) { - if (!ps.profileExists(identityIds[i])) { - revert ProfileLib.ProfileDoesntExist(identityIds[i]); - } + // for (uint256 i; i < identityIds.length; ) { + // if (!ps.profileExists(identityIds[i])) { + // revert ProfileLib.ProfileDoesntExist(identityIds[i]); + // } - if (pr.isCuratedNode(paranetId, identityIds[i])) { - revert ParanetLib.ParanetCuratedNodeHasAlreadyBeenAdded(paranetId, identityIds[i]); - } + // if (pr.isPermissionedNode(paranetId, identityIds[i])) { + // revert ParanetLib.ParanetPermissionedNodeHasAlreadyBeenAdded(paranetId, identityIds[i]); + // } - pr.addCuratedNode(paranetId, identityIds[i], ps.getNodeId(identityIds[i])); + // pr.addPermissionedNode(paranetId, identityIds[i], ps.getNodeId(identityIds[i])); - emit ParanetCuratedNodeAdded(paranetKAStorageContract, paranetKATokenId, identityIds[i]); + // emit ParanetPermissionedNodeAdded(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId, identityIds[i]); - unchecked { - i++; - } - } - } + // unchecked { + // i++; + // } + // } + // } - function removeParanetCuratedNodes( - address paranetKAStorageContract, - uint256 paranetKATokenId, - uint72[] calldata identityIds - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function removeParanetPermissionedNodes( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // uint72[] calldata identityIds + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getNodesAccessPolicy(paranetId) != ParanetLib.NodesAccessPolicy.CURATED) { - ParanetLib.NodesAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.NodesAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.NodesAccessPolicy.CURATED; + // if (pr.getNodesAccessPolicy(paranetId) != NODES_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = NODES_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetNodesAccessPolicy( - expectedAccessPolicies, - pr.getNodesAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetNodesAccessPolicy( + // expectedAccessPolicies, + // pr.getNodesAccessPolicy(paranetId) + // ); + // } - for (uint256 i; i < identityIds.length; ) { - if (!pr.isCuratedNode(paranetId, identityIds[i])) { - revert ParanetLib.ParanetCuratedNodeDoesntExist(paranetId, identityIds[i]); - } + // for (uint256 i; i < identityIds.length; ) { + // if (!pr.isPermissionedNode(paranetId, identityIds[i])) { + // revert ParanetLib.ParanetPermissionedNodeDoesntExist(paranetId, identityIds[i]); + // } - pr.removeCuratedNode(paranetId, identityIds[i]); + // pr.removePermissionedNode(paranetId, identityIds[i]); - emit ParanetCuratedNodeRemoved(paranetKAStorageContract, paranetKATokenId, identityIds[i]); + // emit ParanetPermissionedNodeRemoved( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // identityIds[i] + // ); - unchecked { - i++; - } - } - } + // unchecked { + // i++; + // } + // } + // } - function requestParanetCuratedNodeAccess(address paranetKAStorageContract, uint256 paranetKATokenId) external { - ParanetsRegistry pr = paranetsRegistry; + // function requestParanetPermissionedNodeAccess( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId + // ) external { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getNodesAccessPolicy(paranetId) != ParanetLib.NodesAccessPolicy.CURATED) { - ParanetLib.NodesAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.NodesAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.NodesAccessPolicy.CURATED; + // if (pr.getNodesAccessPolicy(paranetId) != NODES_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = NODES_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetNodesAccessPolicy( - expectedAccessPolicies, - pr.getNodesAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetNodesAccessPolicy( + // expectedAccessPolicies, + // pr.getNodesAccessPolicy(paranetId) + // ); + // } - uint72 identityId = identityStorage.getIdentityId(msg.sender); + // uint72 identityId = identityStorage.getIdentityId(msg.sender); - if (!profileStorage.profileExists(identityId)) { - revert ProfileLib.ProfileDoesntExist(identityId); - } + // if (!profileStorage.profileExists(identityId)) { + // revert ProfileLib.ProfileDoesntExist(identityId); + // } - ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( - paranetId, - identityId - ); + // ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( + // paranetId, + // identityId + // ); - if ( - paranetNodeJoinRequests.length > 0 && - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status == ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedNodeJoinRequestInvalidStatus( - paranetId, - identityId, - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status - ); - } + // if ( + // paranetNodeJoinRequests.length > 0 && + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status == ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedNodeJoinRequestInvalidStatus( + // paranetId, + // identityId, + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status + // ); + // } - pr.addNodeJoinRequest(paranetId, identityId, ParanetLib.RequestStatus.PENDING); + // pr.addNodeJoinRequest(paranetId, identityId, ParanetLib.RequestStatus.PENDING); - emit ParanetCuratedNodeJoinRequestCreated(paranetKAStorageContract, paranetKATokenId, identityId); - } + // emit ParanetPermissionedNodeJoinRequestCreated( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // identityId + // ); + // } - function approveCuratedNode( - address paranetKAStorageContract, - uint256 paranetKATokenId, - uint72 identityId - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function approvePermissionedNode( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // uint72 identityId + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getNodesAccessPolicy(paranetId) != ParanetLib.NodesAccessPolicy.CURATED) { - ParanetLib.NodesAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.NodesAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.NodesAccessPolicy.CURATED; + // if (pr.getNodesAccessPolicy(paranetId) != NODES_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = NODES_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetNodesAccessPolicy( - expectedAccessPolicies, - pr.getNodesAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetNodesAccessPolicy( + // expectedAccessPolicies, + // pr.getNodesAccessPolicy(paranetId) + // ); + // } - ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( - paranetId, - identityId - ); + // ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( + // paranetId, + // identityId + // ); - if (paranetNodeJoinRequests.length == 0) { - revert ParanetLib.ParanetCuratedNodeJoinRequestDoesntExist(paranetId, identityId); - } else if ( - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status != ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedNodeJoinRequestInvalidStatus( - paranetId, - identityId, - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status - ); - } + // if (paranetNodeJoinRequests.length == 0) { + // revert ParanetLib.ParanetPermissionedNodeJoinRequestDoesntExist(paranetId, identityId); + // } else if ( + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status != ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedNodeJoinRequestInvalidStatus( + // paranetId, + // identityId, + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status + // ); + // } - pr.updateNodeJoinRequestStatus( - paranetId, - identityId, - paranetNodeJoinRequests.length - 1, - ParanetLib.RequestStatus.APPROVED - ); - pr.addCuratedNode(paranetId, identityId, profileStorage.getNodeId(identityId)); + // pr.updateNodeJoinRequestStatus( + // paranetId, + // identityId, + // paranetNodeJoinRequests.length - 1, + // ParanetLib.RequestStatus.APPROVED + // ); + // pr.addPermissionedNode(paranetId, identityId, profileStorage.getNodeId(identityId)); - emit ParanetCuratedNodeJoinRequestAccepted(paranetKAStorageContract, paranetKATokenId, identityId); - emit ParanetCuratedNodeAdded(paranetKAStorageContract, paranetKATokenId, identityId); - } + // emit ParanetPermissionedNodeJoinRequestAccepted( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // identityId + // ); + // emit ParanetPermissionedNodeAdded(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId, identityId); + // } - function rejectCuratedNode( - address paranetKAStorageContract, - uint256 paranetKATokenId, - uint72 identityId - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function rejectPermissionedNode( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // uint72 identityId + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getNodesAccessPolicy(paranetId) != ParanetLib.NodesAccessPolicy.CURATED) { - ParanetLib.NodesAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.NodesAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.NodesAccessPolicy.CURATED; + // if (pr.getNodesAccessPolicy(paranetId) != NODES_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = NODES_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetNodesAccessPolicy( - expectedAccessPolicies, - pr.getNodesAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetNodesAccessPolicy( + // expectedAccessPolicies, + // pr.getNodesAccessPolicy(paranetId) + // ); + // } - ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( - paranetId, - identityId - ); + // ParanetLib.ParanetNodeJoinRequest[] memory paranetNodeJoinRequests = pr.getNodeJoinRequests( + // paranetId, + // identityId + // ); - if (paranetNodeJoinRequests.length == 0) { - revert ParanetLib.ParanetCuratedNodeJoinRequestDoesntExist(paranetId, identityId); - } else if ( - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status != ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedNodeJoinRequestInvalidStatus( - paranetId, - identityId, - paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status - ); - } + // if (paranetNodeJoinRequests.length == 0) { + // revert ParanetLib.ParanetPermissionedNodeJoinRequestDoesntExist(paranetId, identityId); + // } else if ( + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status != ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedNodeJoinRequestInvalidStatus( + // paranetId, + // identityId, + // paranetNodeJoinRequests[paranetNodeJoinRequests.length - 1].status + // ); + // } - pr.updateNodeJoinRequestStatus( - paranetId, - identityId, - paranetNodeJoinRequests.length - 1, - ParanetLib.RequestStatus.REJECTED - ); + // pr.updateNodeJoinRequestStatus( + // paranetId, + // identityId, + // paranetNodeJoinRequests.length - 1, + // ParanetLib.RequestStatus.REJECTED + // ); - emit ParanetCuratedNodeJoinRequestRejected(paranetKAStorageContract, paranetKATokenId, identityId); - } + // emit ParanetPermissionedNodeJoinRequestRejected( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // identityId + // ); + // } function addParanetServices( - address paranetKAStorageContract, + address paranetKCStorageContract, + uint256 paranetKCTokenId, uint256 paranetKATokenId, ParanetLib.UniversalAssetLocator[] calldata services - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { + ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { ParanetsRegistry pr = paranetsRegistry; ParanetServicesRegistry psr = paranetServicesRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); + revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); } for (uint256 i; i < services.length; ) { if ( !psr.paranetServiceExists( - keccak256(abi.encodePacked(services[i].knowledgeAssetStorageContract, services[i].tokenId)) + keccak256( + abi.encodePacked( + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId + ) + ) ) ) { revert ParanetLib.ParanetServiceDoesntExist( - services[i].knowledgeAssetStorageContract, - services[i].tokenId + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId ); } - _checkKnowledgeAssetOwner(services[i].knowledgeAssetStorageContract, services[i].tokenId); + _checkKnowledgeAssetOwner( + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId + ); if ( pr.isServiceImplemented( paranetId, - keccak256(abi.encodePacked(services[i].knowledgeAssetStorageContract, services[i].tokenId)) + keccak256( + abi.encodePacked( + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId + ) + ) ) ) { revert ParanetLib.ParanetServiceHasAlreadyBeenAdded( paranetId, - keccak256(abi.encodePacked(services[i].knowledgeAssetStorageContract, services[i].tokenId)) + keccak256( + abi.encodePacked( + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId + ) + ) ); } pr.addService( paranetId, - keccak256(abi.encodePacked(services[i].knowledgeAssetStorageContract, services[i].tokenId)) + keccak256( + abi.encodePacked( + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId + ) + ) ); emit ParanetServiceAdded( - paranetKAStorageContract, + paranetKCStorageContract, + paranetKCTokenId, paranetKATokenId, - services[i].knowledgeAssetStorageContract, - services[i].tokenId + services[i].knowledgeCollectionStorageContract, + services[i].knowledgeCollectionTokenId, + services[i].knowledgeAssetTokenId ); unchecked { @@ -499,27 +639,34 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { } function registerParanetService( - address paranetServiceKAStorageContract, + address paranetServiceKCStorageContract, + uint256 paranetServiceKCTokenId, uint256 paranetServiceKATokenId, string calldata paranetServiceName, string calldata paranetServiceDescription, address[] calldata paranetServiceAddresses - ) external onlyKnowledgeAssetOwner(paranetServiceKAStorageContract, paranetServiceKATokenId) returns (bytes32) { + ) + external + onlyKnowledgeAssetOwner(paranetServiceKCStorageContract, paranetServiceKCTokenId, paranetServiceKATokenId) + returns (bytes32) + { ParanetServicesRegistry psr = paranetServicesRegistry; bytes32 paranetServiceId = keccak256( - abi.encodePacked(paranetServiceKAStorageContract, paranetServiceKATokenId) + abi.encodePacked(paranetServiceKCStorageContract, paranetServiceKCTokenId, paranetServiceKATokenId) ); if (psr.paranetServiceExists(paranetServiceId)) { revert ParanetLib.ParanetServiceHasAlreadyBeenRegistered( - paranetServiceKAStorageContract, + paranetServiceKCStorageContract, + paranetServiceKCTokenId, paranetServiceKATokenId ); } emit ParanetServiceRegistered( - paranetServiceKAStorageContract, + paranetServiceKCStorageContract, + paranetServiceKCTokenId, paranetServiceKATokenId, paranetServiceName, paranetServiceDescription, @@ -528,7 +675,8 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { return psr.registerParanetService( - paranetServiceKAStorageContract, + paranetServiceKCStorageContract, + paranetServiceKCTokenId, paranetServiceKATokenId, paranetServiceName, paranetServiceDescription, @@ -537,20 +685,28 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { } function updateParanetServiceMetadata( - address paranetServiceKAStorageContract, + address paranetServiceKCStorageContract, + uint256 paranetServiceKCTokenId, uint256 paranetServiceKATokenId, string calldata paranetServiceName, string calldata paranetServiceDescription, address[] calldata paranetServiceAddresses - ) external onlyKnowledgeAssetOwner(paranetServiceKAStorageContract, paranetServiceKATokenId) { + ) + external + onlyKnowledgeAssetOwner(paranetServiceKCStorageContract, paranetServiceKCTokenId, paranetServiceKATokenId) + { ParanetServicesRegistry psr = paranetServicesRegistry; bytes32 paranetServiceId = keccak256( - abi.encodePacked(paranetServiceKAStorageContract, paranetServiceKATokenId) + abi.encodePacked(paranetServiceKCStorageContract, paranetServiceKCTokenId, paranetServiceKATokenId) ); if (!psr.paranetServiceExists(paranetServiceId)) { - revert ParanetLib.ParanetServiceDoesntExist(paranetServiceKAStorageContract, paranetServiceKATokenId); + revert ParanetLib.ParanetServiceDoesntExist( + paranetServiceKCStorageContract, + paranetServiceKCTokenId, + paranetServiceKATokenId + ); } psr.setName(paranetServiceId, paranetServiceName); @@ -558,7 +714,8 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { psr.setParanetServiceAddresses(paranetServiceId, paranetServiceAddresses); emit ParanetServiceMetadataUpdated( - paranetServiceKAStorageContract, + paranetServiceKCStorageContract, + paranetServiceKCTokenId, paranetServiceKATokenId, paranetServiceName, paranetServiceDescription, @@ -566,248 +723,294 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { ); } - function addParanetCuratedMiners( - address paranetKAStorageContract, - uint256 paranetKATokenId, - address[] calldata minerAddresses - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; - ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + // function addParanetPermissionedMiners( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // address[] calldata minerAddresses + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; + // ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getMinersAccessPolicy(paranetId) != ParanetLib.MinersAccessPolicy.CURATED) { - ParanetLib.MinersAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.MinersAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.MinersAccessPolicy.CURATED; + // if (pr.getMinersAccessPolicy(paranetId) != MINERS_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = MINERS_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetMinersAccessPolicy( - expectedAccessPolicies, - pr.getMinersAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetMinersAccessPolicy( + // expectedAccessPolicies, + // pr.getMinersAccessPolicy(paranetId) + // ); + // } - for (uint256 i; i < minerAddresses.length; ) { - if (!pkmr.knowledgeMinerExists(minerAddresses[i])) { - pkmr.registerKnowledgeMiner(minerAddresses[i]); - } + // for (uint256 i; i < minerAddresses.length; ) { + // if (!pkmr.knowledgeMinerExists(minerAddresses[i])) { + // pkmr.registerKnowledgeMiner(minerAddresses[i]); + // } - if (pr.isKnowledgeMinerRegistered(paranetId, minerAddresses[i])) { - revert ParanetLib.ParanetCuratedMinerHasAlreadyBeenAdded(paranetId, minerAddresses[i]); - } + // if (pr.isKnowledgeMinerRegistered(paranetId, minerAddresses[i])) { + // revert ParanetLib.ParanetPermissionedMinerHasAlreadyBeenAdded(paranetId, minerAddresses[i]); + // } - pr.addKnowledgeMiner(paranetId, minerAddresses[i]); + // pr.addKnowledgeMiner(paranetId, minerAddresses[i]); - emit ParanetCuratedMinerAdded(paranetKAStorageContract, paranetKATokenId, minerAddresses[i]); + // emit ParanetPermissionedMinerAdded( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // minerAddresses[i] + // ); - unchecked { - i++; - } - } - } + // unchecked { + // i++; + // } + // } + // } - function removeParanetCuratedMiners( - address paranetKAStorageContract, - uint256 paranetKATokenId, - address[] calldata minerAddresses - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function removeParanetPermissionedMiners( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // address[] calldata minerAddresses + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getMinersAccessPolicy(paranetId) != ParanetLib.MinersAccessPolicy.CURATED) { - ParanetLib.MinersAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.MinersAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.MinersAccessPolicy.CURATED; + // if (pr.getMinersAccessPolicy(paranetId) != MINERS_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = MINERS_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetMinersAccessPolicy( - expectedAccessPolicies, - pr.getMinersAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetMinersAccessPolicy( + // expectedAccessPolicies, + // pr.getMinersAccessPolicy(paranetId) + // ); + // } - for (uint256 i; i < minerAddresses.length; ) { - if (!pr.isKnowledgeMinerRegistered(paranetId, minerAddresses[i])) { - revert ParanetLib.ParanetCuratedMinerDoesntExist(paranetId, minerAddresses[i]); - } + // for (uint256 i; i < minerAddresses.length; ) { + // if (!pr.isKnowledgeMinerRegistered(paranetId, minerAddresses[i])) { + // revert ParanetLib.ParanetPermissionedMinerDoesntExist(paranetId, minerAddresses[i]); + // } - pr.removeKnowledgeMiner(paranetId, minerAddresses[i]); + // pr.removeKnowledgeMiner(paranetId, minerAddresses[i]); - emit ParanetCuratedMinerRemoved(paranetKAStorageContract, paranetKATokenId, minerAddresses[i]); + // emit ParanetPermissionedMinerRemoved( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // minerAddresses[i] + // ); - unchecked { - i++; - } - } - } + // unchecked { + // i++; + // } + // } + // } - function requestParanetCuratedMinerAccess(address paranetKAStorageContract, uint256 paranetKATokenId) external { - ParanetsRegistry pr = paranetsRegistry; + // function requestParanetPermissionedMinerAccess( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId + // ) external { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getMinersAccessPolicy(paranetId) != ParanetLib.MinersAccessPolicy.CURATED) { - ParanetLib.MinersAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.MinersAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.MinersAccessPolicy.CURATED; + // if (pr.getMinersAccessPolicy(paranetId) != MINERS_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = MINERS_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetMinersAccessPolicy( - expectedAccessPolicies, - pr.getMinersAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetMinersAccessPolicy( + // expectedAccessPolicies, + // pr.getMinersAccessPolicy(paranetId) + // ); + // } - ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinersAccessRequests = pr - .getKnowledgeMinerAccessRequests(paranetId, msg.sender); + // ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinersAccessRequests = pr + // .getKnowledgeMinerAccessRequests(paranetId, msg.sender); - if ( - paranetKnowledgeMinersAccessRequests.length > 0 && - paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status == - ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedMinerAccessRequestInvalidStatus( - paranetId, - msg.sender, - paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status - ); - } + // if ( + // paranetKnowledgeMinersAccessRequests.length > 0 && + // paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status == + // ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedMinerAccessRequestInvalidStatus( + // paranetId, + // msg.sender, + // paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status + // ); + // } - pr.addKnowledgeMinerAccessRequest(paranetId, msg.sender, ParanetLib.RequestStatus.PENDING); + // pr.addKnowledgeMinerAccessRequest(paranetId, msg.sender, ParanetLib.RequestStatus.PENDING); - emit ParanetCuratedMinerAccessRequestCreated(paranetKAStorageContract, paranetKATokenId, msg.sender); - } + // emit ParanetPermissionedMinerAccessRequestCreated( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // msg.sender + // ); + // } - function approveCuratedMiner( - address paranetKAStorageContract, - uint256 paranetKATokenId, - address minerAddress - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function approvePermissionedMiner( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // address minerAddress + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getMinersAccessPolicy(paranetId) != ParanetLib.MinersAccessPolicy.CURATED) { - ParanetLib.MinersAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.MinersAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.MinersAccessPolicy.CURATED; + // if (pr.getMinersAccessPolicy(paranetId) != MINERS_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = MINERS_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetMinersAccessPolicy( - expectedAccessPolicies, - pr.getMinersAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetMinersAccessPolicy( + // expectedAccessPolicies, + // pr.getMinersAccessPolicy(paranetId) + // ); + // } - ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinersAccessRequests = pr - .getKnowledgeMinerAccessRequests(paranetId, minerAddress); + // ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinersAccessRequests = pr + // .getKnowledgeMinerAccessRequests(paranetId, minerAddress); - if (paranetKnowledgeMinersAccessRequests.length == 0) { - revert ParanetLib.ParanetCuratedMinerAccessRequestDoesntExist(paranetId, minerAddress); - } else if ( - paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status != - ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedMinerAccessRequestInvalidStatus( - paranetId, - minerAddress, - paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status - ); - } + // if (paranetKnowledgeMinersAccessRequests.length == 0) { + // revert ParanetLib.ParanetPermissionedMinerAccessRequestDoesntExist(paranetId, minerAddress); + // } else if ( + // paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status != + // ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedMinerAccessRequestInvalidStatus( + // paranetId, + // minerAddress, + // paranetKnowledgeMinersAccessRequests[paranetKnowledgeMinersAccessRequests.length - 1].status + // ); + // } - pr.updateKnowledgeMinerAccessRequestStatus( - paranetId, - minerAddress, - paranetKnowledgeMinersAccessRequests.length - 1, - ParanetLib.RequestStatus.APPROVED - ); - pr.addKnowledgeMiner(paranetId, minerAddress); + // pr.updateKnowledgeMinerAccessRequestStatus( + // paranetId, + // minerAddress, + // paranetKnowledgeMinersAccessRequests.length - 1, + // ParanetLib.RequestStatus.APPROVED + // ); + // pr.addKnowledgeMiner(paranetId, minerAddress); - emit ParanetCuratedMinerAccessRequestAccepted(paranetKAStorageContract, paranetKATokenId, minerAddress); - emit ParanetCuratedMinerAdded(paranetKAStorageContract, paranetKATokenId, minerAddress); - } + // emit ParanetPermissionedMinerAccessRequestAccepted( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // minerAddress + // ); + // emit ParanetPermissionedMinerAdded(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId, minerAddress); + // } - function rejectCuratedMiner( - address paranetKAStorageContract, - uint256 paranetKATokenId, - address minerAddress - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) { - ParanetsRegistry pr = paranetsRegistry; + // function rejectPermissionedMiner( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // uint256 paranetKATokenId, + // address minerAddress + // ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + // ParanetsRegistry pr = paranetsRegistry; - bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = _getParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - if (!pr.paranetExists(paranetId)) { - revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - } + // if (!pr.paranetExists(paranetId)) { + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); + // } - if (pr.getMinersAccessPolicy(paranetId) != ParanetLib.MinersAccessPolicy.CURATED) { - ParanetLib.MinersAccessPolicy[] memory expectedAccessPolicies = new ParanetLib.MinersAccessPolicy[](1); - expectedAccessPolicies[0] = ParanetLib.MinersAccessPolicy.CURATED; + // if (pr.getMinersAccessPolicy(paranetId) != MINERS_ACCESS_POLICY_PERMISSIONED) { + // uint8[] memory expectedAccessPolicies = new uint8[](1); + // expectedAccessPolicies[0] = MINERS_ACCESS_POLICY_PERMISSIONED; - revert ParanetLib.InvalidParanetMinersAccessPolicy( - expectedAccessPolicies, - pr.getMinersAccessPolicy(paranetId) - ); - } + // revert ParanetLib.InvalidParanetMinersAccessPolicy( + // expectedAccessPolicies, + // pr.getMinersAccessPolicy(paranetId) + // ); + // } - ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinerAccessRequests = pr - .getKnowledgeMinerAccessRequests(paranetId, minerAddress); + // ParanetLib.ParanetKnowledgeMinerAccessRequest[] memory paranetKnowledgeMinerAccessRequests = pr + // .getKnowledgeMinerAccessRequests(paranetId, minerAddress); - if (paranetKnowledgeMinerAccessRequests.length == 0) { - revert ParanetLib.ParanetCuratedMinerAccessRequestDoesntExist(paranetId, minerAddress); - } else if ( - paranetKnowledgeMinerAccessRequests[paranetKnowledgeMinerAccessRequests.length - 1].status != - ParanetLib.RequestStatus.PENDING - ) { - revert ParanetLib.ParanetCuratedMinerAccessRequestInvalidStatus( - paranetId, - minerAddress, - paranetKnowledgeMinerAccessRequests[paranetKnowledgeMinerAccessRequests.length - 1].status - ); - } + // if (paranetKnowledgeMinerAccessRequests.length == 0) { + // revert ParanetLib.ParanetPermissionedMinerAccessRequestDoesntExist(paranetId, minerAddress); + // } else if ( + // paranetKnowledgeMinerAccessRequests[paranetKnowledgeMinerAccessRequests.length - 1].status != + // ParanetLib.RequestStatus.PENDING + // ) { + // revert ParanetLib.ParanetPermissionedMinerAccessRequestInvalidStatus( + // paranetId, + // minerAddress, + // paranetKnowledgeMinerAccessRequests[paranetKnowledgeMinerAccessRequests.length - 1].status + // ); + // } - pr.updateKnowledgeMinerAccessRequestStatus( - paranetId, - minerAddress, - paranetKnowledgeMinerAccessRequests.length - 1, - ParanetLib.RequestStatus.REJECTED - ); + // pr.updateKnowledgeMinerAccessRequestStatus( + // paranetId, + // minerAddress, + // paranetKnowledgeMinerAccessRequests.length - 1, + // ParanetLib.RequestStatus.REJECTED + // ); + + // emit ParanetPermissionedMinerAccessRequestRejected( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // minerAddress + // ); + // } - emit ParanetCuratedMinerAccessRequestRejected(paranetKAStorageContract, paranetKATokenId, minerAddress); + function getKnowledgeCollectionLocatorsWithPagination( + bytes32 paranetId, + uint256 offset, + uint256 limit + ) external view returns (ParanetLib.UniversalAssetCollectionLocator[] memory) { + ParanetsRegistry pr = paranetsRegistry; + bytes32[] memory knowledgeCollections = pr.getKnowledgeCollectionsWithPagination(paranetId, offset, limit); + + ParanetKnowledgeCollectionsRegistry pkcr = paranetKnowledgeCollectionsRegistry; + + return pkcr.getKnowledgeCollectionLocators(knowledgeCollections); } - // function mintKnowledgeAsset( - // address paranetKAStorageContract, - // uint256 paranetKATokenId, - // ContentAssetStructs.AssetInputArgs calldata knowledgeAssetArgs + // function mintKnowledgeCollection( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, + // ContentCollectionStructs.CollectionInputArgs calldata knowledgeCollectionArgs // ) external returns (uint256) { // ParanetsRegistry pr = paranetsRegistry; - // bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = keccak256(abi.encodePacked(paranetKCStorageContract, paranetKCTokenId)); // // Check if Paranet exists // // If not: Throw an error // if (!pr.paranetExists(paranetId)) { - // revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); + // revert ParanetLib.ParanetDoesntExist(paranetKCStorageContract, paranetKCTokenId); // } // ParanetLib.MinersAccessPolicy minersAccessPolicy = pr.getMinersAccessPolicy(paranetId); - // // Check if paranet is curated and if knowledge miner is whitelisted + // // Check if paranet is permissioned and if knowledge miner is whitelisted // if ( - // minersAccessPolicy == ParanetLib.MinersAccessPolicy.CURATED && + // minersAccessPolicy == ParanetLib.MinersAccessPolicy.permissioned && // !pr.isKnowledgeMinerRegistered(paranetId, msg.sender) // ) { - // revert ParanetLib.ParanetCuratedMinerDoesntExist(paranetId, msg.sender); + // revert ParanetLib.ParanetPermissionedMinerDoesntExist(paranetId, msg.sender); // } else if (minersAccessPolicy == ParanetLib.MinersAccessPolicy.OPEN) { // // Check if Knowledge Miner has profile // // If not: Create a profile @@ -821,198 +1024,450 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { // } // } - // // Mint Knowledge Asset - // uint256 knowledgeAssetTokenId = contentAsset.createAssetFromContract(msg.sender, knowledgeAssetArgs); + // // Mint Knowledge Collection + // uint256 knowledgeCollectionTokenId = contentCollection.createCollectionFromContract(msg.sender, knowledgeCollectionArgs); - // _updateSubmittedKnowledgeAssetMetadata( - // paranetKAStorageContract, - // paranetKATokenId, - // address(contentAssetStorage), - // knowledgeAssetTokenId, - // knowledgeAssetArgs.tokenAmount + // _updateSubmittedKnowledgeCollectionMetadata( + // paranetKCStorageContract, + // paranetKCTokenId, + // address(contentCollectionStorage), + // knowledgeCollectionTokenId, + // knowledgeCollectionArgs.tokenAmount // ); - // emit KnowledgeAssetSubmittedToParanet( - // paranetKAStorageContract, - // paranetKATokenId, - // address(contentAssetStorage), - // knowledgeAssetTokenId + // emit KnowledgeCollectionSubmittedToParanet( + // paranetKCStorageContract, + // paranetKCTokenId, + // address(contentCollectionStorage), + // knowledgeCollectionTokenId // ); - // return knowledgeAssetTokenId; + // return knowledgeCollectionTokenId; // } - // function submitKnowledgeAsset( - // address paranetKAStorageContract, - // uint256 paranetKATokenId, - // address knowledgeAssetStorageContract, - // uint256 knowledgeAssetTokenId - // ) external onlyKnowledgeAssetOwner(knowledgeAssetStorageContract, knowledgeAssetTokenId) { - // ParanetsRegistry pr = paranetsRegistry; - // bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // If asset has been updated there should be logic to update paranet kc states metadata with info about previouse state if posible - // if (!pr.paranetExists(paranetId)) { - // revert ParanetLib.ParanetDoesntExist(paranetKAStorageContract, paranetKATokenId); - // } + function submitKnowledgeCollection( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ) external onlyKnowledgeCollectionOwner(knowledgeCollectionStorageContract, knowledgeCollectionTokenId) { + ParanetsRegistry pr = paranetsRegistry; + bytes32 paranetId = _getParanetId( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); - // ParanetLib.MinersAccessPolicy minersAccessPolicy = pr.getMinersAccessPolicy(paranetId); + _validateParanetAndKnowledgeCollection( + paranetId, + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + KnowledgeCollectionStorage kcs = KnowledgeCollectionStorage(knowledgeCollectionStorageContract); + uint256 currentEpoch = chronos.getCurrentEpoch(); + uint40 kcStartEpoch = kcs.getStartEpoch(knowledgeCollectionTokenId); + + if (!(kcStartEpoch == currentEpoch || kcStartEpoch - 1 == currentEpoch)) { + revert ParanetLib.KnowledgeCollectionNotInFirstEpoch( + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + } - // // Check if paranet is curated and if knowledge miner is whitelisted - // if ( - // minersAccessPolicy == ParanetLib.MinersAccessPolicy.CURATED && - // !pr.isKnowledgeMinerRegistered(paranetId, msg.sender) - // ) { - // revert ParanetLib.ParanetCuratedMinerDoesntExist(paranetId, msg.sender); - // } else if (minersAccessPolicy == ParanetLib.MinersAccessPolicy.OPEN) { - // // Check if Knowledge Miner has profile - // // If not: Create a profile - // if (!paranetKnowledgeMinersRegistry.knowledgeMinerExists(msg.sender)) { - // paranetKnowledgeMinersRegistry.registerKnowledgeMiner(msg.sender); - // } + _updateKnowledgeMinerMetadata(paranetId); - // // Check if Knowledge Miner is registered on paranet - // if (!pr.isKnowledgeMinerRegistered(paranetId, msg.sender)) { - // pr.addKnowledgeMiner(paranetId, msg.sender); - // } - // } + require( + pr.getKnowledgeCollectionsSubmissionPolicy(paranetId) != KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + "Staging policy denied" + ); - // if ( - // paranetKnowledgeAssetsRegistry.isParanetKnowledgeAsset( - // keccak256(abi.encodePacked(knowledgeAssetStorageContract, knowledgeAssetTokenId)) - // ) - // ) { - // revert ParanetLib.KnowledgeAssetIsAPartOfOtherParanet( - // knowledgeAssetStorageContract, - // knowledgeAssetTokenId, - // paranetKnowledgeAssetsRegistry.getParanetId( - // keccak256(abi.encodePacked(knowledgeAssetStorageContract, knowledgeAssetTokenId)) - // ) - // ); - // } + // Update KnowledgeMiner metadata + _updateSubmittedKnowledgeCollectionMetadata( + paranetId, + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + } - // uint96 remainingTokenAmount = serviceAgreementStorageProxy.getAgreementTokenAmount( - // hashingProxy.callHashFunction( - // HASH_FUNCTION_ID, - // abi.encodePacked( - // address(contentAssetStorage), - // knowledgeAssetTokenId, - // abi.encodePacked( - // address(contentAssetStorage), - // contentAssetStorage.getAssertionIdByIndex(knowledgeAssetTokenId, 0) - // ) - // ) - // ) - // ); + function stageKnowledgeCollection( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ) external onlyKnowledgeCollectionOwner(knowledgeCollectionStorageContract, knowledgeCollectionTokenId) { + ParanetsRegistry pr = paranetsRegistry; - // _updateSubmittedKnowledgeAssetMetadata( - // paranetKAStorageContract, - // paranetKATokenId, - // knowledgeAssetStorageContract, - // knowledgeAssetTokenId, - // remainingTokenAmount - // ); + bytes32 paranetId = _getParanetId( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); - // emit KnowledgeAssetSubmittedToParanet( - // paranetKAStorageContract, - // paranetKATokenId, - // knowledgeAssetStorageContract, - // knowledgeAssetTokenId - // ); - // } + _validateParanetAndKnowledgeCollection( + paranetId, + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); - // function processUpdatedKnowledgeAssetStatesMetadata( - // address paranetKAStorageContract, - // uint256 paranetKATokenId, + if (pr.getMinersAccessPolicy(paranetId) == MINERS_ACCESS_POLICY_PERMISSIONED) { + require(pr.isKnowledgeMinerRegistered(paranetId, msg.sender), "Knowledge miner is not registered"); + } + + uint8 knowledgeCollectionsSubmissionPolicy = pr.getKnowledgeCollectionsSubmissionPolicy(paranetId); + + require( + knowledgeCollectionsSubmissionPolicy == KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + "Paranet does not allow staging of knowledge collections" + ); + + KnowledgeCollectionStorage kcs = KnowledgeCollectionStorage(knowledgeCollectionStorageContract); + uint256 currentEpoch = chronos.getCurrentEpoch(); + uint40 kcStartEpoch = kcs.getStartEpoch(knowledgeCollectionTokenId); + + if (!(kcStartEpoch == currentEpoch || kcStartEpoch - 1 == currentEpoch)) { + revert ParanetLib.KnowledgeCollectionNotInFirstEpoch( + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + } + + bytes32 knowledgeCollectionId = keccak256( + abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId) + ); + ParanetStagingRegistry pss = paranetStagingRegistry; + require( + !pss.isKnowledgeCollectionStaged(paranetId, knowledgeCollectionId), + "Knowledge collection is already staged" + ); + pss.stageKnowledgeCollection(paranetId, knowledgeCollectionId, msg.sender); + } + + function _validateParanetAndKnowledgeCollection( + bytes32 paranetId, + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ) internal view { + ParanetsRegistry pr = paranetsRegistry; + if (!pr.paranetExists(paranetId)) { + revert ParanetLib.ParanetDoesntExist( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + } + + ParanetKnowledgeCollectionsRegistry pkcr = paranetKnowledgeCollectionsRegistry; + if ( + pkcr.isParanetKnowledgeCollection( + keccak256(abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId)) + ) + ) { + revert ParanetLib.KnowledgeCollectionIsAPartOfOtherParanet( + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + pkcr.getParanetId( + keccak256(abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId)) + ) + ); + } + } + + function addCurator( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address curator + ) + external + onlyKnowledgeAssetOwner( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ) + { + bytes32 paranetId = _getParanetId( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + + ParanetsRegistry pr = paranetsRegistry; + if (!pr.paranetExists(paranetId)) { + revert ParanetLib.ParanetDoesntExist( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + } + + uint8 knowledgeCollectionsSubmissionPolicy = pr.getKnowledgeCollectionsSubmissionPolicy(paranetId); + require( + knowledgeCollectionsSubmissionPolicy == KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + "Paranet does not allow adding curators" + ); + ParanetStagingRegistry pss = paranetStagingRegistry; + require(!pss.isCurator(paranetId, curator), "Existing curator"); + + pss.addCurator(paranetId, curator); + } + + function removeCurator( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address curator + ) + external + onlyKnowledgeAssetOwner( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ) + { + bytes32 paranetId = _getParanetId( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + ParanetsRegistry pr = paranetsRegistry; + if (!pr.paranetExists(paranetId)) { + revert ParanetLib.ParanetDoesntExist( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + } + + uint8 knowledgeCollectionsSubmissionPolicy = pr.getKnowledgeCollectionsSubmissionPolicy(paranetId); + require( + knowledgeCollectionsSubmissionPolicy == KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + "Paranet does not allow adding curators" + ); + + ParanetStagingRegistry pss = paranetStagingRegistry; + require(pss.isCurator(paranetId, curator), "Address is not a curator"); + pss.removeCurator(paranetId, curator); + } + + function reviewKnowledgeCollection( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + bool accepted + ) external onlyCurator(paranetKCStorageContract, paranetKnowledgeCollectionTokenId, paranetKnowledgeAssetTokenId) { + bytes32 paranetId = _getParanetId( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ); + bytes32 knowledgeCollectionId = keccak256( + abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId) + ); + + _validateParanetAndKnowledgeCollection( + paranetId, + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + + ParanetStagingRegistry pss = paranetStagingRegistry; + require( + pss.isKnowledgeCollectionStaged(paranetId, knowledgeCollectionId), + "Knowledge collection is not staged" + ); + pss.reviewKnowledgeCollection(paranetId, knowledgeCollectionId, accepted); + + if (accepted) { + _updateKnowledgeMinerMetadata(paranetId); + + // Update KnowledgeMiner metadata + _updateSubmittedKnowledgeCollectionMetadata( + paranetId, + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + } + } + + function _updateKnowledgeMinerMetadata(bytes32 paranetId) internal { + ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + ParanetsRegistry pr = paranetsRegistry; + uint8 minersAccessPolicy = pr.getMinersAccessPolicy(paranetId); + // Check if paranet is permissioned and if knowledge miner is whitelisted + if (minersAccessPolicy == MINERS_ACCESS_POLICY_PERMISSIONED) { + require(pr.isKnowledgeMinerRegistered(paranetId, msg.sender), "Miner is not registered"); + // Should this be done in both cases why would OPEN have separeted logic ??? + } else if (minersAccessPolicy == MINERS_ACCESS_POLICY_OPEN) { + // Check if Knowledge Miner has profile + // If not: Create a profile + if (!pkmr.knowledgeMinerExists(msg.sender)) { + pkmr.registerKnowledgeMiner(msg.sender); + } + + // Check if Knowledge Miner is registered on paranet + if (!pr.isKnowledgeMinerRegistered(paranetId, msg.sender)) { + pr.addKnowledgeMiner(paranetId, msg.sender); + } + } + } + + // function processUpdatedKnowledgeCollectionStatesMetadata( + // address paranetKCStorageContract, + // uint256 paranetKCTokenId, // uint256 start, // uint256 end // ) external { - // bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); + // bytes32 paranetId = keccak256(abi.encodePacked(paranetKCStorageContract, paranetKCTokenId)); - // _processUpdatedKnowledgeAssetStatesMetadata( + // _processUpdatedKnowledgeCollectionStatesMetadata( // paranetId, - // paranetKnowledgeMinersRegistry.getUpdatingKnowledgeAssetStates(msg.sender, paranetId, start, end) + // paranetKnowledgeMinersRegistry.getUpdatingKnowledgeCollectionStates(msg.sender, paranetId, start, end) // ); // } - // function _updateSubmittedKnowledgeAssetMetadata( - // address paranetKAStorageContract, - // uint256 paranetKATokenId, - // address knowledgeAssetStorageContract, - // uint256 knowledgeAssetTokenId, - // uint96 tokenAmount - // ) internal { - // ParanetsRegistry pr = paranetsRegistry; - // ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + function _updateSubmittedKnowledgeCollectionMetadata( + bytes32 paranetId, + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId + ) internal { + KnowledgeCollectionStorage kcs = KnowledgeCollectionStorage(knowledgeCollectionStorageContract); - // bytes32 paranetId = keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)); - // bytes32 knowledgeAssetId = keccak256(abi.encodePacked(knowledgeAssetStorageContract, knowledgeAssetTokenId)); + ParanetsRegistry pr = paranetsRegistry; + ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; - // // Add Knowledge Asset to the KnowledgeAssetsRegistry - // paranetKnowledgeAssetsRegistry.addKnowledgeAsset( - // paranetId, - // knowledgeAssetStorageContract, - // knowledgeAssetTokenId, - // msg.sender - // ); + uint96 remainingTokenAmount = kcs.getTokenAmount(knowledgeCollectionTokenId); + KnowledgeCollectionLib.MerkleRoot[] memory merkleRoots = kcs.getMerkleRoots(knowledgeCollectionTokenId); + bytes32 knowledgeCollectionId = keccak256( + abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId) + ); + + // Add Knowledge Collection to the KnowledgeCollectionsRegistry + paranetKnowledgeCollectionsRegistry.addKnowledgeCollection( + paranetId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + msg.sender + ); - // // Add Knowledge Asset Metadata to the ParanetsRegistry - // pr.addKnowledgeAsset(paranetId, knowledgeAssetId); - // pr.addCumulativeKnowledgeValue(paranetId, tokenAmount); + // Add Knowledge Collection Metadata to the ParanetsRegistry + pr.addKnowledgeCollecton(paranetId, knowledgeCollectionId); + pr.addCumulativeKnowledgeValue(paranetId, remainingTokenAmount); - // // Add Knowledge Asset Metadata to the KnowledgeMinersRegistry - // pkmr.addSubmittedKnowledgeAsset(msg.sender, paranetId, knowledgeAssetId); - // pkmr.addCumulativeTracSpent(msg.sender, paranetId, tokenAmount); - // pkmr.addUnrewardedTracSpent(msg.sender, paranetId, tokenAmount); - // pkmr.incrementTotalSubmittedKnowledgeAssetsCount(msg.sender); - // pkmr.addTotalTracSpent(msg.sender, tokenAmount); - // } + // Add Knowledge Collection Metadata to the KnowledgeMinersRegistry + for (uint256 i = 0; i < merkleRoots.length - 1; i++) { + pkmr.addUpdatingKnowledgeCollectionState( + msg.sender, + paranetId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + merkleRoots[i].merkleRoot, + 0 + ); + } + pkmr.addUpdatingKnowledgeCollectionState( + msg.sender, + paranetId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + merkleRoots[merkleRoots.length - 1].merkleRoot, + remainingTokenAmount + ); + pkmr.addSubmittedKnowledgeCollection(msg.sender, paranetId, knowledgeCollectionId); + pkmr.addCumulativeTracSpent(msg.sender, paranetId, remainingTokenAmount); + pkmr.addUnrewardedTracSpent(msg.sender, paranetId, remainingTokenAmount); + pkmr.incrementTotalSubmittedKnowledgeCollectionsCount(msg.sender); + pkmr.addTotalTracSpent(msg.sender, remainingTokenAmount); + + emit KnowledgeCollectionSubmittedToParanet( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId, + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId + ); + } - // function _processUpdatedKnowledgeAssetStatesMetadata( + function _getParanetId( + address paranetKCStorageContract, + uint256 paranetKCTokenId, + uint256 paranetKATokenId + ) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId)); + } + + // function _processUpdatedKnowledgeCollectionStatesMetadata( // bytes32 paranetId, - // ParanetLib.UpdatingKnowledgeAssetState[] memory updatingKnowledgeAssetStates + // ParanetLib.UpdatingKnowledgeCollectionState[] memory updatingKnowledgeCollectionStates // ) internal { // ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; // ParanetsRegistry pr = paranetsRegistry; - // ContentAsset ca = contentAsset; + // ContentCollection ca = contentCollection; - // for (uint i; i < updatingKnowledgeAssetStates.length; ) { - // _checkKnowledgeAssetOwner( - // updatingKnowledgeAssetStates[i].knowledgeAssetStorageContract, - // updatingKnowledgeAssetStates[i].tokenId + // for (uint i; i < updatingKnowledgeCollectionStates.length; ) { + // _checkKnowledgeCollectionOwner( + // updatingKnowledgeCollectionStates[i].knowledgeCollectionStorageContract, + // updatingKnowledgeCollectionStates[i].tokenId // ); // bool continueOuterLoop = false; - // bytes32[] memory assertionIds = ContentAssetStorage( - // updatingKnowledgeAssetStates[i].knowledgeAssetStorageContract - // ).getAssertionIds(updatingKnowledgeAssetStates[i].tokenId); + // bytes32[] memory assertionIds = ContentCollectionStorage( + // updatingKnowledgeCollectionStates[i].knowledgeCollectionStorageContract + // ).getAssertionIds(updatingKnowledgeCollectionStates[i].tokenId); // for (uint j = assertionIds.length; j > 0; ) { - // if (assertionIds[j - 1] == updatingKnowledgeAssetStates[i].assertionId) { - // // Add Knowledge Asset Token Amount Metadata to the ParanetsRegistry - // pr.addCumulativeKnowledgeValue(paranetId, updatingKnowledgeAssetStates[i].updateTokenAmount); + // if (assertionIds[j - 1] == updatingKnowledgeCollectionStates[i].assertionId) { + // // Add Knowledge Collection Token Amount Metadata to the ParanetsRegistry + // pr.addCumulativeKnowledgeValue(paranetId, updatingKnowledgeCollectionStates[i].updateTokenAmount); - // // Add Knowledge Asset Token Amount Metadata to the KnowledgeMinersRegistry + // // Add Knowledge Collection Token Amount Metadata to the KnowledgeMinersRegistry // pkmr.addCumulativeTracSpent( // msg.sender, // paranetId, - // updatingKnowledgeAssetStates[i].updateTokenAmount + // updatingKnowledgeCollectionStates[i].updateTokenAmount // ); // pkmr.addUnrewardedTracSpent( // msg.sender, // paranetId, - // updatingKnowledgeAssetStates[i].updateTokenAmount + // updatingKnowledgeCollectionStates[i].updateTokenAmount // ); - // pkmr.addTotalTracSpent(msg.sender, updatingKnowledgeAssetStates[i].updateTokenAmount); + // pkmr.addTotalTracSpent(msg.sender, updatingKnowledgeCollectionStates[i].updateTokenAmount); - // pkmr.removeUpdatingKnowledgeAssetState( + // pkmr.removeUpdatingKnowledgeCollectionState( // msg.sender, // paranetId, // keccak256( // abi.encodePacked( - // updatingKnowledgeAssetStates[i].knowledgeAssetStorageContract, - // updatingKnowledgeAssetStates[i].tokenId, - // updatingKnowledgeAssetStates[i].assertionId + // updatingKnowledgeCollectionStates[i].knowledgeCollectionStorageContract, + // updatingKnowledgeCollectionStates[i].tokenId, + // updatingKnowledgeCollectionStates[i].assertionId // ) // ) // ); @@ -1034,15 +1489,15 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { // continue; // } - // try ca.cancelAssetStateUpdateFromContract(updatingKnowledgeAssetStates[i].tokenId) { - // pkmr.removeUpdatingKnowledgeAssetState( + // try ca.cancelCollectionStateUpdateFromContract(updatingKnowledgeCollectionStates[i].tokenId) { + // pkmr.removeUpdatingKnowledgeCollectionState( // msg.sender, // paranetId, // keccak256( // abi.encodePacked( - // updatingKnowledgeAssetStates[i].knowledgeAssetStorageContract, - // updatingKnowledgeAssetStates[i].tokenId, - // updatingKnowledgeAssetStates[i].assertionId + // updatingKnowledgeCollectionStates[i].knowledgeCollectionStorageContract, + // updatingKnowledgeCollectionStates[i].tokenId, + // updatingKnowledgeCollectionStates[i].assertionId // ) // ) // ); @@ -1051,27 +1506,69 @@ contract Paranet is INamed, IVersioned, ContractStatus, IInitializable { // } // } - function _checkParanetOperator(bytes32 paranetId) internal view virtual { - (address paranetKAStorageContract, uint256 paranetKATokenId) = paranetsRegistry.getParanetKnowledgeAssetLocator( - paranetId + function _checkKnowledgeAssetOwner( + address knowledgeCollectionStorageContractAddress, + uint256 knowledgeCollectionId, + uint256 knowledgeAssetId + ) internal virtual { + require(hub.isAssetStorage(knowledgeCollectionStorageContractAddress), "Given address isn't KC Storage"); + + KnowledgeCollectionStorage knowledgeCollectionStorage = KnowledgeCollectionStorage( + knowledgeCollectionStorageContractAddress ); - _checkKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId); + + uint256 startTokenId = (knowledgeCollectionId - 1) * + knowledgeCollectionStorage.knowledgeCollectionMaxSize() + + knowledgeAssetId; + + uint256 ownedCountInRange = knowledgeCollectionStorage.balanceOf(msg.sender, startTokenId, startTokenId + 1); + require(ownedCountInRange == 1, "Caller isn't the owner of the KA"); } - function _checkParanetServiceOperator(bytes32 paranetServiceId) internal view virtual { - (address paranetServiceKAStorageContract, uint256 paranetServiceKATokenId) = paranetServicesRegistry - .getParanetServiceKnowledgeAssetLocator(paranetServiceId); - _checkKnowledgeAssetOwner(paranetServiceKAStorageContract, paranetServiceKATokenId); + function _checkKnowledgeCollectionOwner( + address knowledgeCollectionStorageContractAddress, + uint256 knowledgeCollectionId + ) internal virtual { + require(hub.isAssetStorage(knowledgeCollectionStorageContractAddress), "Given address isn't KC Storage"); + + KnowledgeCollectionStorage knowledgeCollectionStorage = KnowledgeCollectionStorage( + knowledgeCollectionStorageContractAddress + ); + uint256 minted = knowledgeCollectionStorage.getMinted(knowledgeCollectionId); + uint256 burnedCount = knowledgeCollectionStorage.getBurnedAmount(knowledgeCollectionId); + uint256 activeCount = minted - burnedCount; + require(activeCount != 0, "No KAs in Collection"); + + uint256 startTokenId = (knowledgeCollectionId - 1) * + knowledgeCollectionStorage.knowledgeCollectionMaxSize() + + 1; // _startTokenId() + + uint256 ownedCountInRange = knowledgeCollectionStorage.balanceOf( + msg.sender, + startTokenId, + startTokenId + minted + burnedCount + ); + + require(ownedCountInRange == activeCount, "Caller isn't the owner of the KC"); } - function _checkKnowledgeAssetOwner( - address knowledgeAssetStorageContract, - uint256 knowledgeAssetTokenId - ) internal view virtual { - require(hub.isAssetStorage(knowledgeAssetStorageContract), "Given address isn't KA Storage"); + function _checkCurator( + address paranetKCStorageContract, + uint256 paranetKnowledgeCollectionTokenId, + uint256 paranetKnowledgeAssetTokenId + ) internal view { require( - IERC721(knowledgeAssetStorageContract).ownerOf(knowledgeAssetTokenId) == msg.sender, - "Caller isn't the owner of the KA" + paranetStagingRegistry.isCurator( + keccak256( + abi.encodePacked( + paranetKCStorageContract, + paranetKnowledgeCollectionTokenId, + paranetKnowledgeAssetTokenId + ) + ), + msg.sender + ), + "Not authorized curator" ); } } diff --git a/contracts/paranets/ParanetIncentivesPool.sol b/contracts/paranets/ParanetIncentivesPool.sol new file mode 100644 index 00000000..123fb15d --- /dev/null +++ b/contracts/paranets/ParanetIncentivesPool.sol @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {ParanetKnowledgeMinersRegistry} from "../storage/paranets/ParanetKnowledgeMinersRegistry.sol"; +import {ParanetsRegistry} from "../storage/paranets/ParanetsRegistry.sol"; +import {ParanetIncentivesPoolStorage} from "./ParanetIncentivesPoolStorage.sol"; +import {KnowledgeCollectionStorage} from "../storage/KnowledgeCollectionStorage.sol"; +import {Hub} from "../storage/Hub.sol"; +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; +import {ParanetLib} from "../libraries/ParanetLib.sol"; +import {IParanetIncentivesPool} from "../interfaces/IParanetIncentivesPool.sol"; + +contract ParanetIncentivesPool is INamed, IVersioned, IParanetIncentivesPool { + event TokenEmissionMultiplierUpdateInitiated(uint256 oldMultiplier, uint256 newMultiplier, uint256 timestamp); + event TokenEmissionMultiplierUpdateFinalized(uint256 oldMultiplier, uint256 newMultiplier); + + string private constant _NAME = "ParanetIncentivesPool"; + string private constant _VERSION = "1.0.0"; + + Hub public hub; + ParanetsRegistry public paranetsRegistry; + ParanetKnowledgeMinersRegistry public paranetKnowledgeMinersRegistry; + ParanetIncentivesPoolStorage public paranetIncentivesPoolStorage; + + // Array of Total TOKEN Emission Multipliers + // Total TOKEN Emission Multiplier = Ratio of how much TOKEN is released per 1 TRAC spent + // + // Examples: + // 1 * 10^12 = 1 TOKEN per 1 TRAC + // 0.5 * 10^12 = 5 * 10^11 = 0.5 TOKEN per 1 TRAC + // 1 = 1 TOKEN wei per 1 TRAC + // + ParanetLib.TokenEmissionMultiplier[] public tokenEmissionMultipliers; + + uint256 public tokenEmissionMultiplierUpdateDelay = 7 days; + + // solhint-disable-next-line no-empty-blocks + constructor( + address hubAddress, + address knowledgeMinersRegistryAddress, + address paranetIncentivesPoolStorageAddress, + address paranetsRegistryAddress, + uint256 tracToTokenEmissionMultiplier + ) { + hub = Hub(hubAddress); + paranetKnowledgeMinersRegistry = ParanetKnowledgeMinersRegistry(knowledgeMinersRegistryAddress); + paranetIncentivesPoolStorage = ParanetIncentivesPoolStorage(payable(paranetIncentivesPoolStorageAddress)); + paranetsRegistry = ParanetsRegistry(paranetsRegistryAddress); + + tokenEmissionMultipliers.push( + ParanetLib.TokenEmissionMultiplier({ + multiplier: tracToTokenEmissionMultiplier, + timestamp: block.timestamp, + finalized: true + }) + ); + } + + modifier onlyHubOwner() { + _checkHubOwner(); + _; + } + + modifier onlyVotersRegistrar() { + _checkVotersRegistrar(); + _; + } + + modifier onlyParanetOperator() { + _checkParanetOperator(); + _; + } + + modifier onlyParanetIncentivizationProposalVoter() { + _checkParanetIncentivizationProposalVoter(); + _; + } + + modifier onlyParanetKnowledgeMiner() { + _checkParanetKnowledgeMiner(); + _; + } + + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + function updatetokenEmissionMultiplierUpdateDelay(uint256 newDelay) external onlyHubOwner { + tokenEmissionMultiplierUpdateDelay = newDelay; + } + + function voterclaimedToken(address addr) external view returns (uint256) { + uint256 voterIndex = paranetIncentivesPoolStorage.votersIndexes(addr); + + // If the index is out of range or the stored voter doesn't match `voterAddress`, + // return 0 as a default. + if ( + voterIndex >= paranetIncentivesPoolStorage.getVotersCount() || + paranetIncentivesPoolStorage.getVoterAtIndex(voterIndex).addr != addr + ) { + return 0; + } + + return paranetIncentivesPoolStorage.getVoterAtIndex(voterIndex).claimedToken; + } + + function isKnowledgeMiner(address addr) public view returns (bool) { + return paranetsRegistry.isKnowledgeMinerRegistered(paranetIncentivesPoolStorage.paranetId(), addr); + } + + function isParanetOperator(address addr) public view returns (bool) { + (address paranetKCStorageContract, uint256 paranetKCTokenId, uint256 paranetKATokenId) = paranetsRegistry + .getParanetKnowledgeAssetLocator(paranetIncentivesPoolStorage.paranetId()); + + KnowledgeCollectionStorage knowledgeCollectionStorage = KnowledgeCollectionStorage(paranetKCStorageContract); + + uint256 startTokenId = (paranetKCTokenId - 1) * + knowledgeCollectionStorage.knowledgeCollectionMaxSize() + + paranetKATokenId; + + uint256 ownedCountInRange = knowledgeCollectionStorage.balanceOf(addr, startTokenId, startTokenId + 1); + + return ownedCountInRange == 1; + } + + function isProposalVoter(address addr) public view returns (bool) { + return (paranetIncentivesPoolStorage.getVotersCount() != 0 && + paranetIncentivesPoolStorage.getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(addr)).addr == + addr); + } + + function gettokenEmissionMultipliers() external view returns (ParanetLib.TokenEmissionMultiplier[] memory) { + return tokenEmissionMultipliers; + } + + function getEffectiveTokenEmissionMultiplier(uint256 timestamp) public view returns (uint256) { + for (uint256 i = tokenEmissionMultipliers.length; i > 0; i--) { + if (tokenEmissionMultipliers[i - 1].finalized && timestamp >= tokenEmissionMultipliers[i - 1].timestamp) { + return tokenEmissionMultipliers[i - 1].multiplier; + } + } + return tokenEmissionMultipliers[0].multiplier; + } + + // TODO:Should there be some check of this value? + function initiateTokenEmissionMultiplierUpdate(uint256 newMultiplier) external onlyVotersRegistrar { + if (!tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].finalized) { + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].multiplier = newMultiplier; + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].timestamp = + block.timestamp + + tokenEmissionMultiplierUpdateDelay; + } else { + tokenEmissionMultipliers.push( + ParanetLib.TokenEmissionMultiplier({ + multiplier: newMultiplier, + timestamp: block.timestamp + tokenEmissionMultiplierUpdateDelay, + finalized: false + }) + ); + } + + emit TokenEmissionMultiplierUpdateInitiated( + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 2].multiplier, + newMultiplier, + block.timestamp + tokenEmissionMultiplierUpdateDelay + ); + } + + function finalizeTokenEmissionMultiplierUpdate() external onlyVotersRegistrar { + require(tokenEmissionMultipliers.length > 0, "No emission multiplier updates"); + require( + !tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].finalized, + "Last update already finalized" + ); + require( + block.timestamp >= tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].timestamp, + "Delay period not yet passed" + ); + + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].finalized = true; + + emit TokenEmissionMultiplierUpdateFinalized( + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 2].multiplier, + tokenEmissionMultipliers[tokenEmissionMultipliers.length - 1].multiplier + ); + } + + function getTotalKnowledgeMinerIncentiveEstimation() public view returns (uint256) { + uint96 unrewardedTracSpent = paranetKnowledgeMinersRegistry.getUnrewardedTracSpent( + msg.sender, + paranetIncentivesPoolStorage.paranetId() + ); + + if (unrewardedTracSpent < ParanetLib.TOKENS_DIGITS_DIFF) { + return 0; + } + + // Unrewarded TRAC Spent = how much TRAC Knowledge Miner spent for Mining and haven't got a reward for + // Effective Emission Ratio = Current active Multiplier for how much TOKEN is released per TRAC spent + // + // Basic Formula: + // Reward = UnrewardedTRAC * TotalEmissionRatio * (MinersRewardPercentage / 100) + // + // Example: + // Let's say we have 10 unrewarded TRAC, 0.5 TOKEN per TRAC Total Emission and 80% Miners Reward Percentage, + // 10% Operator Reward Percentage, 10% Voters Reward Percentage + // Reward = (((10 * 10^18) * (5 * 10^11)) / (10^18)) * (10,000 - 1,000 - 1,000) / 10,000) = + // = 10 * 5 * 10^11 * 8,000 / 10,000 = 8/10 * (5 * 10^12) = 80% of 5 TOKEN = 4 TOKEN + return + (((unrewardedTracSpent * getEffectiveTokenEmissionMultiplier(block.timestamp)) / + ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) * + (ParanetLib.PERCENTAGE_SCALING_FACTOR - + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage() - + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage())) / + ParanetLib.PERCENTAGE_SCALING_FACTOR; + } + + function getTotalAllKnowledgeMinersIncentiveEstimation() public view returns (uint256) { + return + _getIncentiveEstimation( + ParanetLib.PERCENTAGE_SCALING_FACTOR - + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage() - + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage(), + paranetIncentivesPoolStorage.totalMinersclaimedToken() + ); + } + + function getClaimableKnowledgeMinerRewardAmount() public view returns (uint256) { + uint256 tokenReward = getTotalKnowledgeMinerIncentiveEstimation(); + + // Here we should have a limit for Knowledge Miners, which is determined by the % of the Miners Reward + // and total TOKEN received by the contract, so that Miners don't get tokens belonging to Operator/Voters + // Following the example from the above, if we have 100 TOKEN as a total reward, Miners should never get + // more than 80 TOKEN. minersRewardLimit = 80 TOKEN + uint256 totalMinersclaimedToken = paranetIncentivesPoolStorage.totalMinersclaimedToken(); + uint256 minersRewardLimit = ((paranetIncentivesPoolStorage.getBalance() + + totalMinersclaimedToken + + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + + paranetIncentivesPoolStorage.totalVotersclaimedToken()) * + (ParanetLib.PERCENTAGE_SCALING_FACTOR - + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage() - + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage())) / + ParanetLib.PERCENTAGE_SCALING_FACTOR; + + return + totalMinersclaimedToken + tokenReward <= minersRewardLimit + ? tokenReward + : minersRewardLimit - totalMinersclaimedToken; + } + + function getClaimableAllKnowledgeMinersRewardAmount() public view returns (uint256) { + uint256 tokenReward = getTotalAllKnowledgeMinersIncentiveEstimation(); + + uint256 minersRewardLimit = ((paranetIncentivesPoolStorage.getBalance() + + paranetIncentivesPoolStorage.totalMinersclaimedToken() + + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + + paranetIncentivesPoolStorage.totalVotersclaimedToken()) * + (ParanetLib.PERCENTAGE_SCALING_FACTOR - + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage() - + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage())) / + ParanetLib.PERCENTAGE_SCALING_FACTOR; + + return + paranetIncentivesPoolStorage.totalMinersclaimedToken() + tokenReward <= minersRewardLimit + ? tokenReward + : minersRewardLimit - paranetIncentivesPoolStorage.totalMinersclaimedToken(); + } + + function claimKnowledgeMinerReward(uint256 amount) external onlyParanetKnowledgeMiner { + ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; + + uint256 tokenReward = getTotalKnowledgeMinerIncentiveEstimation(); + uint256 claimableTokenReward = getClaimableKnowledgeMinerRewardAmount(); + if (claimableTokenReward == 0 || amount == 0 || amount > claimableTokenReward) { + revert ParanetLib.NoRewardAvailable(paranetIncentivesPoolStorage.paranetId(), msg.sender); + } + + uint96 newUnrewardedTracSpent = amount == tokenReward + ? 0 + : uint96( + ((tokenReward - amount) * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / + getEffectiveTokenEmissionMultiplier(block.timestamp) + ); + + pkmr.setUnrewardedTracSpent(msg.sender, paranetIncentivesPoolStorage.paranetId(), newUnrewardedTracSpent); + pkmr.addcumulativeAwardedToken(msg.sender, paranetIncentivesPoolStorage.paranetId(), amount); + + if ( + paranetIncentivesPoolStorage.getClaimedMinerRewardsLength() == 0 || + paranetIncentivesPoolStorage + .getClaimedMinerRewardsAtIndex(paranetIncentivesPoolStorage.claimedMinerRewardsIndexes(msg.sender)) + .addr != + msg.sender + ) { + paranetIncentivesPoolStorage.addMinerClaimedRewardProfile(msg.sender, amount); + } else { + paranetIncentivesPoolStorage.addMinerClaimedReward(msg.sender, amount); + } + paranetIncentivesPoolStorage.addTotalMinersclaimedToken(amount); + + paranetIncentivesPoolStorage.transferReward(msg.sender, amount); + } + + function getTotalParanetOperatorIncentiveEstimation() public view returns (uint256) { + return + _getIncentiveEstimation( + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage(), + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + ); + } + + function getClaimableParanetOperatorRewardAmount() public view returns (uint256) { + uint256 tokenReward = getTotalParanetOperatorIncentiveEstimation(); + + uint256 operatorRewardLimit = ((paranetIncentivesPoolStorage.getBalance() + + paranetIncentivesPoolStorage.totalMinersclaimedToken() + + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + + paranetIncentivesPoolStorage.totalVotersclaimedToken()) * + paranetIncentivesPoolStorage.paranetOperatorRewardPercentage()) / ParanetLib.PERCENTAGE_SCALING_FACTOR; + + return + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + tokenReward <= operatorRewardLimit + ? tokenReward + : operatorRewardLimit - paranetIncentivesPoolStorage.totalOperatorsclaimedToken(); + } + + function claimParanetOperatorReward() external onlyParanetOperator { + uint256 claimableTokenReward = getClaimableParanetOperatorRewardAmount(); + + if (claimableTokenReward == 0) { + revert ParanetLib.NoRewardAvailable(paranetIncentivesPoolStorage.paranetId(), msg.sender); + } + + if ( + paranetIncentivesPoolStorage.getClaimedOperatorRewardsLength() == 0 || + paranetIncentivesPoolStorage + .getClaimedOperatorRewardsAtIndex( + paranetIncentivesPoolStorage.claimedOperatorRewardsIndexes(msg.sender) + ) + .addr != + msg.sender + ) { + paranetIncentivesPoolStorage.addOperatorClaimedRewardsProfile(msg.sender, claimableTokenReward); + } else { + paranetIncentivesPoolStorage.addClaimedOperatorReward(msg.sender, claimableTokenReward); + } + paranetIncentivesPoolStorage.addTotalOperatorsclaimedToken(claimableTokenReward); + + paranetIncentivesPoolStorage.transferReward(msg.sender, claimableTokenReward); + } + + function getTotalProposalVoterIncentiveEstimation() public view returns (uint256) { + uint256 effectiveTokenEmissionMultiplier = getEffectiveTokenEmissionMultiplier(block.timestamp); + uint96 cumulativeKnowledgeValueSingleVoterPart = (((paranetsRegistry.getCumulativeKnowledgeValue( + paranetIncentivesPoolStorage.paranetId() + ) * paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage()) / + ParanetLib.PERCENTAGE_SCALING_FACTOR) * + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .weight) / ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT; + uint96 rewardedTracSpentSingleVoterPart = uint96( + (paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .claimedToken * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / effectiveTokenEmissionMultiplier + ); + + if ( + cumulativeKnowledgeValueSingleVoterPart - rewardedTracSpentSingleVoterPart < ParanetLib.TOKENS_DIGITS_DIFF + ) { + return 0; + } + + return + ((cumulativeKnowledgeValueSingleVoterPart * effectiveTokenEmissionMultiplier) / + ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) - + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .claimedToken; + } + + function getTotalAllProposalVotersIncentiveEstimation() public view returns (uint256) { + return + _getIncentiveEstimation( + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage(), + paranetIncentivesPoolStorage.totalVotersclaimedToken() + ); + } + + function getClaimableProposalVoterRewardAmount() public view returns (uint256) { + if ( + paranetIncentivesPoolStorage.getVotersCount() == 0 || + paranetIncentivesPoolStorage.getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)).addr != + msg.sender + ) { + return 0; + } + + uint256 tokenReward = getTotalProposalVoterIncentiveEstimation(); + + uint256 voterRewardLimit = ((((paranetIncentivesPoolStorage.getBalance() + + paranetIncentivesPoolStorage.totalMinersclaimedToken() + + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + + paranetIncentivesPoolStorage.totalVotersclaimedToken()) * + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage()) / + ParanetLib.PERCENTAGE_SCALING_FACTOR) * + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .weight) / ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT; + + return + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .claimedToken + + tokenReward <= + voterRewardLimit + ? tokenReward + : voterRewardLimit - + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .claimedToken; + } + + function getClaimableAllProposalVotersRewardAmount() public view returns (uint256) { + uint256 tokenReward = getTotalAllProposalVotersIncentiveEstimation(); + + uint256 votersRewardLimit = ((paranetIncentivesPoolStorage.getBalance() + + paranetIncentivesPoolStorage.totalMinersclaimedToken() + + paranetIncentivesPoolStorage.totalOperatorsclaimedToken() + + paranetIncentivesPoolStorage.totalVotersclaimedToken()) * + paranetIncentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage()) / + ParanetLib.PERCENTAGE_SCALING_FACTOR; + + return + paranetIncentivesPoolStorage.totalVotersclaimedToken() + tokenReward <= votersRewardLimit + ? tokenReward + : votersRewardLimit - paranetIncentivesPoolStorage.totalVotersclaimedToken(); + } + + function claimIncentivizationProposalVoterReward() external onlyParanetIncentivizationProposalVoter { + if (paranetIncentivesPoolStorage.cumulativeVotersWeight() != ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT) { + revert ParanetLib.InvalidCumulativeVotersWeight( + paranetIncentivesPoolStorage.paranetId(), + paranetIncentivesPoolStorage.cumulativeVotersWeight(), + ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT + ); + } + + uint256 claimableTokenReward = getClaimableProposalVoterRewardAmount(); + + if (claimableTokenReward == 0) { + revert ParanetLib.NoRewardAvailable(paranetIncentivesPoolStorage.paranetId(), msg.sender); + } + + paranetIncentivesPoolStorage + .getVoterAtIndex(paranetIncentivesPoolStorage.votersIndexes(msg.sender)) + .claimedToken += claimableTokenReward; + paranetIncentivesPoolStorage.addTotalVotersclaimedToken(claimableTokenReward); + + paranetIncentivesPoolStorage.transferReward(msg.sender, claimableTokenReward); + } + + function _getIncentiveEstimation( + uint16 rewardPercentage, + uint256 totalclaimedToken + ) internal view returns (uint256) { + uint256 effectiveTokenEmissionMultiplier = getEffectiveTokenEmissionMultiplier(block.timestamp); + uint96 cumulativeKnowledgeValuePart = (paranetsRegistry.getCumulativeKnowledgeValue( + paranetIncentivesPoolStorage.paranetId() + ) * rewardPercentage) / ParanetLib.PERCENTAGE_SCALING_FACTOR; + uint96 rewardedTracSpentPart = uint96( + (totalclaimedToken * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / effectiveTokenEmissionMultiplier + ); + + if (cumulativeKnowledgeValuePart - rewardedTracSpentPart < ParanetLib.TOKENS_DIGITS_DIFF) { + return 0; + } + + return + ((cumulativeKnowledgeValuePart * effectiveTokenEmissionMultiplier) / + ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) - totalclaimedToken; + } + + function getParanetIncentivesPoolStorage() external view returns (address) { + return address(paranetIncentivesPoolStorage); + } + + function _checkHubOwner() internal view virtual { + require(msg.sender == hub.owner(), "Fn can only be used by hub owner"); + } + + function _checkVotersRegistrar() internal view virtual { + require(msg.sender == paranetIncentivesPoolStorage.votersRegistrar(), "Fn can only be used by registrar"); + } + + function _checkParanetOperator() internal view virtual { + require(isParanetOperator(msg.sender), "Fn can only be used by operator"); + } + + function _checkParanetIncentivizationProposalVoter() internal view virtual { + require(isProposalVoter(msg.sender), "Fn can only be used by voter"); + } + + function _checkParanetKnowledgeMiner() internal view virtual { + require(isKnowledgeMiner(msg.sender), "Fn can only be used by K-Miners"); + } +} diff --git a/contracts/paranets/ParanetIncentivesPoolFactory.sol b/contracts/paranets/ParanetIncentivesPoolFactory.sol index 2708a19d..7dce7876 100644 --- a/contracts/paranets/ParanetIncentivesPoolFactory.sol +++ b/contracts/paranets/ParanetIncentivesPoolFactory.sol @@ -4,36 +4,60 @@ pragma solidity ^0.8.20; import {Hub} from "../storage/Hub.sol"; import {ParanetsRegistry} from "../storage/paranets/ParanetsRegistry.sol"; -import {ParanetNeuroIncentivesPool} from "./ParanetNeuroIncentivesPool.sol"; +import {ParanetIncentivesPool} from "./ParanetIncentivesPool.sol"; +import {ParanetIncentivesPoolStorage} from "./ParanetIncentivesPoolStorage.sol"; +import {ParanetIncentivesPoolFactoryHelper} from "./ParanetIncentivesPoolFactoryHelper.sol"; +import {KnowledgeCollectionStorage} from "../storage/KnowledgeCollectionStorage.sol"; import {ContractStatus} from "../abstract/ContractStatus.sol"; import {IInitializable} from "../interfaces/IInitializable.sol"; import {INamed} from "../interfaces/INamed.sol"; import {IVersioned} from "../interfaces/IVersioned.sol"; -import {ParanetLib} from "../libraries/ParanetLib.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; contract ParanetIncentivesPoolFactory is INamed, IVersioned, ContractStatus, IInitializable { - event ParanetIncetivesPoolDeployed( - address indexed paranetKAStorageContract, + event ParanetIncentivesPoolDeployed( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, uint256 indexed paranetKATokenId, - ParanetLib.IncentivesPool incentivesPool + address storageAddress, + address poolAddress, + address rewardTokenAddress + ); + + event ParanetIncentivesPoolRedeployed( + address indexed paranetKCStorageContract, + uint256 indexed paranetKCTokenId, + uint256 indexed paranetKATokenId, + address storageAddress, + address newPoolAddress ); string private constant _NAME = "ParanetIncentivesPoolFactory"; string private constant _VERSION = "1.0.0"; ParanetsRegistry public paranetsRegistry; + ParanetIncentivesPoolFactoryHelper public paranetIncentivesPoolFactoryHelper; // solhint-disable-next-line no-empty-blocks constructor(address hubAddress) ContractStatus(hubAddress) {} - modifier onlyKnowledgeAssetOwner(address knowledgeAssetStorageContract, uint256 knowledgeAssetTokenId) { - _checkKnowledgeAssetOwner(knowledgeAssetStorageContract, knowledgeAssetTokenId); + modifier onlyKnowledgeAssetOwner( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ) { + _checkKnowledgeAssetOwner( + knowledgeCollectionStorageContract, + knowledgeCollectionTokenId, + knowledgeAssetTokenId + ); _; } function initialize() public onlyHub { paranetsRegistry = ParanetsRegistry(hub.getContractAddress("ParanetsRegistry")); + paranetIncentivesPoolFactoryHelper = ParanetIncentivesPoolFactoryHelper( + hub.getContractAddress("ParanetIncentivesPoolFactoryHelper") + ); } function name() external pure virtual override returns (string memory) { @@ -44,66 +68,119 @@ contract ParanetIncentivesPoolFactory is INamed, IVersioned, ContractStatus, IIn return _VERSION; } - function deployNeuroIncentivesPool( - address paranetKAStorageContract, + function deployIncentivesPool( + address paranetKCStorageContract, + uint256 paranetKCTokenId, uint256 paranetKATokenId, - uint256 tracToNeuroEmissionMultiplier, + uint256 tracToTokenEmissionMultiplier, uint16 paranetOperatorRewardPercentage, - uint16 paranetIncentivizationProposalVotersRewardPercentage - ) external onlyKnowledgeAssetOwner(paranetKAStorageContract, paranetKATokenId) returns (address) { - Hub h = hub; + uint16 paranetIncentivizationProposalVotersRewardPercentage, + string calldata incentivesPoolName, + address rewardTokenAddress + ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + bytes32 paranetId = _computeParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); ParanetsRegistry pr = paranetsRegistry; - - if ( - pr.hasIncentivesPoolByType( - keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)), - "Neuroweb" - ) - ) { - revert ParanetLib.ParanetIncentivesPoolAlreadyExists( - paranetKAStorageContract, - paranetKATokenId, - "Neuroweb", - pr.getIncentivesPoolAddress( - keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)), - "Neuroweb" - ) - ); - } - - ParanetNeuroIncentivesPool incentivesPool = new ParanetNeuroIncentivesPool( - address(h), - h.getContractAddress("ParanetsRegistry"), - h.getContractAddress("ParanetKnowledgeMinersRegistry"), - keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)), - tracToNeuroEmissionMultiplier, + require(pr.paranetExists(paranetId), "Paranet does not exist"); + require(tracToTokenEmissionMultiplier > 0, "Emission multiplier must be greater than 0"); + require(bytes(incentivesPoolName).length > 0, "Pool name cannot be empty"); + require(!pr.hasIncentivesPoolByName(paranetId, incentivesPoolName), "Pool name already exists"); + + ParanetIncentivesPoolStorage storage_ = new ParanetIncentivesPoolStorage( + address(hub), + rewardTokenAddress, + paranetId, paranetOperatorRewardPercentage, paranetIncentivizationProposalVotersRewardPercentage ); + address storageAddress = address(storage_); + + address poolAddress = paranetIncentivesPoolFactoryHelper.deployIncentivesPool( + storageAddress, + tracToTokenEmissionMultiplier, + address(storage_) + ); + + storage_.initialize(); + paranetsRegistry.addIncentivesPool(paranetId, incentivesPoolName, storageAddress, rewardTokenAddress); + + emit ParanetIncentivesPoolDeployed( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + storageAddress, + poolAddress, + rewardTokenAddress + ); + } + + function redeployIncentivesPool( + address paranetKCStorageContract, + uint256 paranetKCTokenId, + uint256 paranetKATokenId, + address storageAddress + ) external onlyKnowledgeAssetOwner(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId) { + bytes32 paranetId = _computeParanetId(paranetKCStorageContract, paranetKCTokenId, paranetKATokenId); - pr.setIncentivesPoolAddress( - keccak256(abi.encodePacked(paranetKAStorageContract, paranetKATokenId)), - "Neuroweb", - address(incentivesPool) + ParanetsRegistry pr = paranetsRegistry; + require(pr.paranetExists(paranetId), "Paranet does not exist"); + require( + pr.hasIncentivesPoolByStorageAddress(paranetId, storageAddress), + "Cannot redeploy an incentives pool that does not exist" ); - emit ParanetIncetivesPoolDeployed( - paranetKAStorageContract, + ParanetIncentivesPoolStorage storage_ = ParanetIncentivesPoolStorage(payable(storageAddress)); + require(storage_.paranetId() == paranetId, "Storage contract does not point to the paranet you provided"); + + address oldPoolAddress = storage_.paranetIncentivesPoolAddress(); + uint256 tracToTokenEmissionMultiplier = ParanetIncentivesPool(oldPoolAddress) + .getEffectiveTokenEmissionMultiplier(block.timestamp); + + address newPoolAddress = paranetIncentivesPoolFactoryHelper.deployIncentivesPool( + storageAddress, + tracToTokenEmissionMultiplier, + address(storage_) + ); + emit ParanetIncentivesPoolRedeployed( + paranetKCStorageContract, + paranetKCTokenId, paranetKATokenId, - ParanetLib.IncentivesPool({poolType: "Neuroweb", addr: address(incentivesPool)}) + storageAddress, + newPoolAddress ); + } - return address(incentivesPool); + function _computeParanetId( + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId + ) internal pure returns (bytes32) { + return + keccak256( + abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId, knowledgeAssetTokenId) + ); } function _checkKnowledgeAssetOwner( - address knowledgeAssetStorageContract, - uint256 knowledgeAssetTokenId - ) internal view virtual { - require(hub.isAssetStorage(knowledgeAssetStorageContract), "Given address isn't KA Storage"); + address knowledgeCollectionStorageContractAddress, + uint256 knowledgeCollectionId, + uint256 knowledgeAssetId + ) internal view { + require( + hub.isAssetStorage(knowledgeCollectionStorageContractAddress), + "Knowledge collection storage contract with the provided address is not registered" + ); + + KnowledgeCollectionStorage knowledgeCollectionStorage = KnowledgeCollectionStorage( + knowledgeCollectionStorageContractAddress + ); + + uint256 startTokenId = (knowledgeCollectionId - 1) * + knowledgeCollectionStorage.knowledgeCollectionMaxSize() + + knowledgeAssetId; + require( - IERC721(knowledgeAssetStorageContract).ownerOf(knowledgeAssetTokenId) == msg.sender, - "Caller isn't the owner of the KA" + knowledgeCollectionStorage.balanceOf(msg.sender, startTokenId, startTokenId + 1) == 1, + "Caller is not the owner of the knowledge asset" ); } } diff --git a/contracts/paranets/ParanetIncentivesPoolFactoryHelper.sol b/contracts/paranets/ParanetIncentivesPoolFactoryHelper.sol new file mode 100644 index 00000000..8934a071 --- /dev/null +++ b/contracts/paranets/ParanetIncentivesPoolFactoryHelper.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {Hub} from "../storage/Hub.sol"; +import {ParanetIncentivesPool} from "./ParanetIncentivesPool.sol"; +import {ParanetIncentivesPoolStorage} from "./ParanetIncentivesPoolStorage.sol"; +import {KnowledgeCollectionStorage} from "../storage/KnowledgeCollectionStorage.sol"; +import {ContractStatus} from "../abstract/ContractStatus.sol"; +import {IInitializable} from "../interfaces/IInitializable.sol"; +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; + +contract ParanetIncentivesPoolFactoryHelper is INamed, IVersioned, ContractStatus { + string private constant _NAME = "ParanetIncentivesPoolFactoryHelper"; + string private constant _VERSION = "1.0.0"; + + // solhint-disable-next-line no-empty-blocks + constructor(address hubAddress) ContractStatus(hubAddress) {} + + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + function deployIncentivesPool( + address storageAddress, + uint256 tracToTokenEmissionMultiplier, + address poolStorageAddress + ) external onlyContracts returns (address) { + address addr = address( + new ParanetIncentivesPool( + address(hub), + hub.getContractAddress("ParanetKnowledgeMinersRegistry"), + storageAddress, + hub.getContractAddress("ParanetsRegistry"), + tracToTokenEmissionMultiplier + ) + ); + ParanetIncentivesPoolStorage(payable(poolStorageAddress)).setParanetIncentivesPool(addr); + + return addr; + } +} diff --git a/contracts/paranets/ParanetIncentivesPoolStorage.sol b/contracts/paranets/ParanetIncentivesPoolStorage.sol new file mode 100644 index 00000000..9ed4ea59 --- /dev/null +++ b/contracts/paranets/ParanetIncentivesPoolStorage.sol @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {HubDependent} from "../abstract/HubDependent.sol"; +import {ParanetsRegistry} from "../storage/paranets/ParanetsRegistry.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {INamed} from "../interfaces/INamed.sol"; +import {IVersioned} from "../interfaces/IVersioned.sol"; +import {IInitializable} from "../interfaces/IInitializable.sol"; +import {ParanetLib} from "../libraries/ParanetLib.sol"; + +contract ParanetIncentivesPoolStorage is INamed, IVersioned, HubDependent, IInitializable { + event TokenRewardDeposit(address sender, uint256 amount); + event VoterWeightUpdated(address indexed voter, uint96 oldWeight, uint96 newWeight); + event TotalMinersclaimedTokenSet(uint256 oldAmount, uint256 newAmount); + event TotalOperatorsclaimedTokenSet(uint256 oldAmount, uint256 newAmount); + event TotalVotersclaimedTokenSet(uint256 oldAmount, uint256 newAmount); + event TotalMinersclaimedTokenDecremented(uint256 amount, uint256 newTotal); + event TotalOperatorsclaimedTokenDecremented(uint256 amount, uint256 newTotal); + event TotalVotersclaimedTokenDecremented(uint256 amount, uint256 newTotal); + event VotersRegistrarTransferred(address indexed previousRegistrar, address indexed newRegistrar); + event MinerRewardProfileAdded(address indexed miner, uint256 amount); + event MinerRewardIncreased(address indexed miner, uint256 additionalAmount, uint256 newTotal); + event OperatorRewardProfileAdded(address indexed operator, uint256 amount); + event OperatorRewardIncreased(address indexed operator, uint256 additionalAmount, uint256 newTotal); + event VoterAdded(address indexed voter, uint16 weight); + event VoterRemoved(address indexed voter, uint96 weight); + event VotersRemoved(uint256 count); + event VoterRewardClaimed(address indexed voter, uint256 amount); + event IncentivesPoolAddressSet(address indexed oldAddress, address indexed newAddress); + event RewardTransferred(address indexed recipient, uint256 amount); + event TokenOriginSet(address indexed oldOrigin, address indexed newOrigin); + + string private constant _NAME = "ParanetIncentivesPoolStorage"; + string private constant _VERSION = "1.0.0"; + uint256 private constant MAX_VOTERS_PER_BATCH = 100; + + IERC20 public token; + ParanetsRegistry public paranetsRegistry; + address public paranetIncentivesPoolAddress; + bytes32 public paranetId; + + // Percentage of how much tokens from total TOKEN emission goes to the Paranet Operator + // Minimum: 0, Maximum: 10,000 (which is 100%) + uint16 public paranetOperatorRewardPercentage; + // Percentage of how much tokens from total TOKEN emission goes to the Paranet Incentivization + // Proposal Voters. Minimum: 0, Maximum: 10,000 (which is 100%) + uint16 public paranetIncentivizationProposalVotersRewardPercentage; + + // Address which can set Voters list and update Total TOKEN Emission multiplier + address public votersRegistrar; + + uint256 public totalMinersclaimedToken; + uint256 public totalOperatorsclaimedToken; + uint256 public totalVotersclaimedToken; + + ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] public claimedMinerRewards; + mapping(address => uint256) public claimedMinerRewardsIndexes; + + ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] public claimedOperatorRewards; + mapping(address => uint256) public claimedOperatorRewardsIndexes; + + uint96 public cumulativeVotersWeight; + ParanetLib.ParanetIncentivizationProposalVoter[] public voters; + mapping(address => uint256) public votersIndexes; + + address public tokenOrigin; + + constructor( + address hubAddress, + address rewardTokenAddress, + bytes32 paranetId_, + uint16 paranetOperatorRewardPercentage_, + uint16 paranetIncentivizationProposalVotersRewardPercentage_ + ) HubDependent(hubAddress) { + require( + paranetOperatorRewardPercentage_ + paranetIncentivizationProposalVotersRewardPercentage_ < + ParanetLib.PERCENTAGE_SCALING_FACTOR, + "Invalid rewards ratio" + ); + + if (rewardTokenAddress != address(0)) { + token = IERC20(rewardTokenAddress); + } + + ParanetsRegistry pr = ParanetsRegistry(hub.getContractAddress("ParanetsRegistry")); + require(pr.paranetExists(paranetId_), "Non existent paranet"); + paranetId = paranetId_; + + paranetOperatorRewardPercentage = paranetOperatorRewardPercentage_; + paranetIncentivizationProposalVotersRewardPercentage = paranetIncentivizationProposalVotersRewardPercentage_; + + address hubOwner = hub.owner(); + uint256 size; + assembly { + size := extcodesize(hubOwner) + } + if (size > 0) { + votersRegistrar = Ownable(hubOwner).owner(); + } else { + votersRegistrar = hubOwner; + } + } + + function initialize() public onlyContracts { + paranetsRegistry = ParanetsRegistry(hub.getContractAddress("ParanetsRegistry")); + } + + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + receive() external payable { + emit TokenRewardDeposit(msg.sender, msg.value); + } + + function totalReceived() external view returns (uint256) { + return getBalance() + totalMinersclaimedToken + totalOperatorsclaimedToken + totalVotersclaimedToken; + } + + function transferVotersRegistrarRole(address newRegistrar) external onlyVotersRegistrar { + require(newRegistrar != address(0), "New registrar cannot be zero address"); + address oldRegistrar = votersRegistrar; + votersRegistrar = newRegistrar; + emit VotersRegistrarTransferred(oldRegistrar, newRegistrar); + } + + function getAllRewardedMiners() + external + view + returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory) + { + return claimedMinerRewards; + } + + function minerclaimedToken(address minerAddress) external view returns (uint256) { + return claimedMinerRewards[claimedMinerRewardsIndexes[minerAddress]].claimedToken; + } + + function operatorclaimedToken(address operatorAddress) external view returns (uint256) { + return claimedOperatorRewards[claimedOperatorRewardsIndexes[operatorAddress]].claimedToken; + } + + function addMinerClaimedRewardProfile(address addr, uint256 claimableTokenReward) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + claimedMinerRewardsIndexes[addr] = claimedMinerRewards.length; + claimedMinerRewards.push( + ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile({addr: addr, claimedToken: claimableTokenReward}) + ); + emit MinerRewardProfileAdded(addr, claimableTokenReward); + } + + function addMinerClaimedReward(address addr, uint256 claimableTokenReward) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + uint256 newTotal = claimedMinerRewards[claimedMinerRewardsIndexes[addr]].claimedToken + claimableTokenReward; + claimedMinerRewards[claimedMinerRewardsIndexes[addr]].claimedToken = newTotal; + emit MinerRewardIncreased(addr, claimableTokenReward, newTotal); + } + + function addOperatorClaimedRewardsProfile(address addr, uint256 claimableTokenReward) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + claimedOperatorRewardsIndexes[addr] = claimedOperatorRewards.length; + claimedOperatorRewards.push( + ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile({addr: addr, claimedToken: claimableTokenReward}) + ); + emit OperatorRewardProfileAdded(addr, claimableTokenReward); + } + + function addClaimedOperatorReward(address addr, uint256 claimableTokenReward) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + uint256 newTotal = claimedOperatorRewards[claimedOperatorRewardsIndexes[addr]].claimedToken + + claimableTokenReward; + claimedOperatorRewards[claimedOperatorRewardsIndexes[addr]].claimedToken = newTotal; + emit OperatorRewardIncreased(addr, claimableTokenReward, newTotal); + } + + function getAllRewardedOperators() + external + view + returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory) + { + return claimedOperatorRewards; + } + + function addVoters( + ParanetLib.ParanetIncentivizationProposalVoterInput[] calldata voters_ + ) external onlyVotersRegistrar { + require(voters_.length <= MAX_VOTERS_PER_BATCH, "Batch too large"); + for (uint256 i; i < voters_.length; ) { + address voterAddr = voters_[i].addr; + uint16 weight = uint16(voters_[i].weight); + + uint256 existingIndex = votersIndexes[voterAddr]; + if (existingIndex < voters.length && voters[existingIndex].addr == voterAddr) { + revert("Voter already exists"); + } + + votersIndexes[voterAddr] = voters.length; + voters.push( + ParanetLib.ParanetIncentivizationProposalVoter({addr: voterAddr, weight: weight, claimedToken: 0}) + ); + + cumulativeVotersWeight += weight; + + emit VoterAdded(voterAddr, weight); + + unchecked { + i++; + } + } + + require(cumulativeVotersWeight <= ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT, "Cumulative weight is too big"); + } + + function removeVoters(address[] calldata votersToRemove) external onlyVotersRegistrar { + for (uint256 i; i < votersToRemove.length; ) { + removeVoter(votersToRemove[i]); + unchecked { + i++; + } + } + } + + function removeVoter(address voterAddress) public onlyVotersRegistrar { + uint256 index = votersIndexes[voterAddress]; + require(index < voters.length, "Invalid voter index"); + + ParanetLib.ParanetIncentivizationProposalVoter memory voterToRemove = voters[index]; + require(voterToRemove.addr == voterAddress, "Voter not found"); + + uint96 removedWeight = voterToRemove.weight; + require(cumulativeVotersWeight >= removedWeight, "Weight underflow"); + + // Move last element to deleted position + uint256 lastIndex = voters.length - 1; + if (index != lastIndex) { + ParanetLib.ParanetIncentivizationProposalVoter memory lastVoter = voters[lastIndex]; + voters[index] = lastVoter; + votersIndexes[lastVoter.addr] = index; + } + + voters.pop(); + delete votersIndexes[voterAddress]; + cumulativeVotersWeight -= removedWeight; + + emit VoterRemoved(voterAddress, removedWeight); + } + + function getVotersCount() external view returns (uint256) { + return voters.length; + } + + function getVoters() external view returns (ParanetLib.ParanetIncentivizationProposalVoter[] memory) { + return voters; + } + + function getVoter( + address voterAddress + ) external view returns (ParanetLib.ParanetIncentivizationProposalVoter memory) { + require(voters.length > 0, "Address is not a registered voter"); + + uint256 index = votersIndexes[voterAddress]; + require(index < voters.length && voters[index].addr == voterAddress, "Address is not a registered voter"); + + return voters[index]; + } + + function getVoterAtIndex( + uint256 index + ) external view returns (ParanetLib.ParanetIncentivizationProposalVoter memory) { + require(index < voters.length, "Index is out of bounds"); + return voters[index]; + } + + function isProposalVoter(address addr) external view returns (bool) { + if (voters.length == 0) return false; + uint256 idx = votersIndexes[addr]; + return (idx < voters.length && voters[idx].addr == addr); + } + + function addVoterClaimedToken(address voter, uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + uint256 idx = votersIndexes[voter]; + if (idx < voters.length && voters[idx].addr == voter) { + voters[idx].claimedToken += amount; + emit VoterRewardClaimed(voter, amount); + } + } + + function getClaimedMinerRewardsLength() external view returns (uint256) { + return claimedMinerRewards.length; + } + + function getClaimedMinerRewardsAtIndex( + uint256 index + ) external view returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile memory) { + require(index < claimedMinerRewards.length, "Index is out of bounds"); + return claimedMinerRewards[index]; + } + + function getClaimedOperatorRewardsLength() external view returns (uint256) { + return claimedOperatorRewards.length; + } + + function getClaimedOperatorRewardsAtIndex( + uint256 index + ) external view returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile memory) { + require(index < claimedOperatorRewards.length, "Index is out of bounds"); + return claimedOperatorRewards[index]; + } + + function addTotalMinersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalMinersclaimedToken += amount; + } + + function addTotalOperatorsclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalOperatorsclaimedToken += amount; + } + + function addTotalVotersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalVotersclaimedToken += amount; + } + + function setParanetIncentivesPool(address _paranetIncentivesPoolAddress) external onlyContracts { + address oldAddress = paranetIncentivesPoolAddress; + paranetIncentivesPoolAddress = _paranetIncentivesPoolAddress; + emit IncentivesPoolAddressSet(oldAddress, _paranetIncentivesPoolAddress); + } + + function getBalance() public view returns (uint256) { + if (address(token) == address(0)) { + return address(this).balance; + } else { + return token.balanceOf(address(this)); + } + } + + function transferReward(address rewardAddress, uint256 amount) public { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + if (address(token) == address(0)) { + payable(rewardAddress).transfer(amount); + } else { + token.transfer(rewardAddress, amount); + } + emit RewardTransferred(rewardAddress, amount); + } + + function setTotalMinersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalMinersclaimedToken = amount; + } + + function setTotalOperatorsclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalOperatorsclaimedToken = amount; + } + + function setTotalVotersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalVotersclaimedToken = amount; + } + + function decrementTotalMinersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalMinersclaimedToken -= amount; + } + + function decrementTotalOperatorsclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalOperatorsclaimedToken -= amount; + } + + function decrementTotalVotersclaimedToken(uint256 amount) external { + require(msg.sender == paranetIncentivesPoolAddress, "Caller is not incentives pool contract"); + totalVotersclaimedToken -= amount; + } + + function getPaginatedClaimedMinerRewards( + uint256 offset, + uint256 limit + ) external view returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory rewards, uint256 total) { + total = claimedMinerRewards.length; + + if (offset >= total || limit == 0) { + return (new ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[](0), total); + } + + uint256 end = offset + limit; + if (end > total) { + end = total; + } + uint256 resultLength = end - offset; + + rewards = new ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[](resultLength); + for (uint256 i = 0; i < resultLength; i++) { + rewards[i] = claimedMinerRewards[offset + i]; + } + } + + function getPaginatedClaimedOperatorRewards( + uint256 offset, + uint256 limit + ) external view returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory rewards, uint256 total) { + total = claimedOperatorRewards.length; + + if (offset >= total || limit == 0) { + return (new ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[](0), total); + } + + uint256 end = offset + limit; + if (end > total) { + end = total; + } + uint256 resultLength = end - offset; + + rewards = new ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[](resultLength); + for (uint256 i = 0; i < resultLength; i++) { + rewards[i] = claimedOperatorRewards[offset + i]; + } + } + + function getPaginatedVoters( + uint256 offset, + uint256 limit + ) external view returns (ParanetLib.ParanetIncentivizationProposalVoter[] memory votersList, uint256 total) { + total = voters.length; + + if (offset >= total || limit == 0) { + return (new ParanetLib.ParanetIncentivizationProposalVoter[](0), total); + } + + uint256 end = offset + limit; + if (end > total) { + end = total; + } + uint256 resultLength = end - offset; + + votersList = new ParanetLib.ParanetIncentivizationProposalVoter[](resultLength); + for (uint256 i = 0; i < resultLength; i++) { + votersList[i] = voters[offset + i]; + } + } + + function updateVoterWeight(address voter, uint96 newWeight) external onlyVotersRegistrar { + uint256 index = votersIndexes[voter]; + require(index < voters.length && voters[index].addr == voter, "Voter not found"); + + uint96 oldWeight = voters[index].weight; + require( + cumulativeVotersWeight - oldWeight + newWeight <= ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT, + "New weight would exceed maximum" + ); + + cumulativeVotersWeight = cumulativeVotersWeight - oldWeight + newWeight; + voters[index].weight = newWeight; + + emit VoterWeightUpdated(voter, oldWeight, newWeight); + } + + function setTokenOrigin(address newOrigin) external onlyContracts { + require(newOrigin != address(0), "Token origin cannot be zero address"); + address oldOrigin = tokenOrigin; + tokenOrigin = newOrigin; + emit TokenOriginSet(oldOrigin, newOrigin); + } + + modifier onlyVotersRegistrar() { + require(msg.sender == votersRegistrar, "Fn can only be used by registrar"); + _; + } +} diff --git a/contracts/paranets/ParanetNeuroIncentivesPool.sol b/contracts/paranets/ParanetNeuroIncentivesPool.sol deleted file mode 100644 index 43af89a5..00000000 --- a/contracts/paranets/ParanetNeuroIncentivesPool.sol +++ /dev/null @@ -1,612 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.20; - -import {ParanetKnowledgeMinersRegistry} from "../storage/paranets/ParanetKnowledgeMinersRegistry.sol"; -import {ParanetsRegistry} from "../storage/paranets/ParanetsRegistry.sol"; -import {Hub} from "../storage/Hub.sol"; -import {INamed} from "../interfaces/INamed.sol"; -import {IVersioned} from "../interfaces/IVersioned.sol"; -import {ParanetLib} from "../libraries/ParanetLib.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -contract ParanetNeuroIncentivesPool is INamed, IVersioned { - event NeuroRewardDeposit(address indexed sender, uint256 amount); - event NeuroEmissionMultiplierUpdateInitiated(uint256 oldMultiplier, uint256 newMultiplier, uint256 timestamp); - event NeuroEmissionMultiplierUpdateFinalized(uint256 oldMultiplier, uint256 newMultiplier); - event ParanetKnowledgeMinerRewardClaimed(address indexed miner, uint256 amount); - event ParanetOperatorRewardClaimed(address indexed operator, uint256 amount); - event ParanetIncentivizationProposalVoterRewardClaimed(address indexed voter, uint256 amount); - - string private constant _NAME = "ParanetNeuroIncentivesPool"; - string private constant _VERSION = "1.0.0"; - - Hub public hub; - ParanetsRegistry public paranetsRegistry; - ParanetKnowledgeMinersRegistry public paranetKnowledgeMinersRegistry; - - bytes32 public parentParanetId; - // Array of Total NEURO Emission Multipliers - // Total NEURO Emission Multiplier = Ratio of how much NEURO is released per 1 TRAC spent - // - // Examples: - // 1 * 10^12 = 1 NEURO per 1 TRAC - // 0.5 * 10^12 = 5 * 10^11 = 0.5 NEURO per 1 TRAC - // 1 = 1 NEURO wei per 1 TRAC - // - ParanetLib.NeuroEmissionMultiplier[] public neuroEmissionMultipliers; - - uint256 public neuroEmissionMultiplierUpdateDelay = 7 days; - - // Percentage of how much tokens from total NEURO emission goes to the Paranet Operator - // Minimum: 0, Maximum: 10,000 (which is 100%) - uint16 public paranetOperatorRewardPercentage; - // Percentage of how much tokens from total NEURO emission goes to the Paranet Incentivization - // Proposal Voters. Minimum: 0, Maximum: 10,000 (which is 100%) - uint16 public paranetIncentivizationProposalVotersRewardPercentage; - uint16 public cumulativeVotersWeight; - - // Address which can set Voters list and update Total NEURO Emission multiplier - address public votersRegistrar; - - uint256 public totalMinersClaimedNeuro; - uint256 public totalOperatorsClaimedNeuro; - uint256 public totalVotersClaimedNeuro; - - ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] public claimedMinerRewards; - mapping(address => uint256) public claimedMinerRewardsIndexes; - - ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] public claimedOperatorRewards; - mapping(address => uint256) public claimedOperatorRewardsIndexes; - - ParanetLib.ParanetIncentivizationProposalVoter[] public voters; - mapping(address => uint256) public votersIndexes; - - // solhint-disable-next-line no-empty-blocks - constructor( - address hubAddress, - address paranetsRegistryAddress, - address knowledgeMinersRegistryAddress, - bytes32 paranetId, - uint256 tracToNeuroEmissionMultiplier, - uint16 paranetOperatorRewardPercentage_, - uint16 paranetIncentivizationProposalVotersRewardPercentage_ - ) { - require( - paranetOperatorRewardPercentage_ + paranetIncentivizationProposalVotersRewardPercentage_ < - ParanetLib.PERCENTAGE_SCALING_FACTOR, - "Invalid rewards ratio" - ); - - hub = Hub(hubAddress); - paranetsRegistry = ParanetsRegistry(paranetsRegistryAddress); - paranetKnowledgeMinersRegistry = ParanetKnowledgeMinersRegistry(knowledgeMinersRegistryAddress); - - parentParanetId = paranetId; - neuroEmissionMultipliers.push( - ParanetLib.NeuroEmissionMultiplier({ - multiplier: tracToNeuroEmissionMultiplier, - timestamp: block.timestamp, - finalized: true - }) - ); - paranetOperatorRewardPercentage = paranetOperatorRewardPercentage_; - paranetIncentivizationProposalVotersRewardPercentage = paranetIncentivizationProposalVotersRewardPercentage_; - - address hubOwner = hub.owner(); - uint256 size; - assembly { - size := extcodesize(hubOwner) - } - if (size > 0) { - votersRegistrar = Ownable(hubOwner).owner(); - } else { - votersRegistrar = hubOwner; - } - } - - modifier onlyHubOwner() { - _checkHubOwner(); - _; - } - - modifier onlyVotersRegistrar() { - _checkVotersRegistrar(); - _; - } - - modifier onlyParanetOperator() { - _checkParanetOperator(); - _; - } - - modifier onlyParanetIncentivizationProposalVoter() { - _checkParanetIncentivizationProposalVoter(); - _; - } - - modifier onlyParanetKnowledgeMiner() { - _checkParanetKnowledgeMiner(); - _; - } - - function name() external pure virtual override returns (string memory) { - return _NAME; - } - - function version() external pure virtual override returns (string memory) { - return _VERSION; - } - - receive() external payable { - emit NeuroRewardDeposit(msg.sender, msg.value); - } - - function totalNeuroReceived() external view returns (uint256) { - return address(this).balance + totalMinersClaimedNeuro + totalOperatorsClaimedNeuro + totalVotersClaimedNeuro; - } - - function getNeuroBalance() external view returns (uint256) { - return address(this).balance; - } - - function updateNeuroEmissionMultiplierUpdateDelay(uint256 newDelay) external onlyHubOwner { - neuroEmissionMultiplierUpdateDelay = newDelay; - } - - function transferVotersRegistrarRole(address newRegistrar) external onlyVotersRegistrar { - votersRegistrar = newRegistrar; - } - - function minerClaimedNeuro(address minerAddress) external view returns (uint256) { - return claimedMinerRewards[claimedMinerRewardsIndexes[minerAddress]].claimedNeuro; - } - - function getAllRewardedMiners() - external - view - returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory) - { - return claimedMinerRewards; - } - - function operatorClaimedNeuro(address operatorAddress) external view returns (uint256) { - return claimedOperatorRewards[claimedOperatorRewardsIndexes[operatorAddress]].claimedNeuro; - } - - function getAllRewardedOperators() - external - view - returns (ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile[] memory) - { - return claimedOperatorRewards; - } - - function voterClaimedNeuro(address voterAddress) external view returns (uint256) { - return voters[votersIndexes[voterAddress]].claimedNeuro; - } - - function addVoters( - ParanetLib.ParanetIncentivizationProposalVoterInput[] calldata voters_ - ) external onlyVotersRegistrar { - for (uint256 i; i < voters_.length; ) { - votersIndexes[voters_[i].addr] = voters.length; - voters.push( - ParanetLib.ParanetIncentivizationProposalVoter({ - addr: voters_[i].addr, - weight: voters_[i].weight, - claimedNeuro: 0 - }) - ); - - cumulativeVotersWeight += uint16(voters_[i].weight); - - unchecked { - i++; - } - } - - require(cumulativeVotersWeight <= ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT, "Cumulative weight is too big"); - } - - function getVoter( - address voterAddress - ) external view returns (ParanetLib.ParanetIncentivizationProposalVoter memory) { - return voters[votersIndexes[voterAddress]]; - } - - function getVoters() external view returns (ParanetLib.ParanetIncentivizationProposalVoter[] memory) { - return voters; - } - - function getVotersCount() external view returns (uint256) { - return voters.length; - } - - function removeVoters(uint256 limit) external onlyVotersRegistrar { - require(voters.length >= limit, "Limit exceeds the num of voters"); - - for (uint256 i; i < limit; ) { - cumulativeVotersWeight -= uint16(voters[voters.length - 1 - i].weight); - - delete votersIndexes[voters[voters.length - 1 - i].addr]; - voters.pop(); - - unchecked { - i++; - } - } - } - - function isKnowledgeMiner(address addr) public view returns (bool) { - return paranetsRegistry.isKnowledgeMinerRegistered(parentParanetId, addr); - } - - function isParanetOperator(address addr) public view returns (bool) { - (address paranetKAStorageContract, uint256 paranetKATokenId) = paranetsRegistry.getParanetKnowledgeAssetLocator( - parentParanetId - ); - - return IERC721(paranetKAStorageContract).ownerOf(paranetKATokenId) == addr; - } - - function isProposalVoter(address addr) public view returns (bool) { - return (voters.length != 0 && voters[votersIndexes[addr]].addr == addr); - } - - function getNeuroEmissionMultipliers() external view returns (ParanetLib.NeuroEmissionMultiplier[] memory) { - return neuroEmissionMultipliers; - } - - function getEffectiveNeuroEmissionMultiplier(uint256 timestamp) public view returns (uint256) { - for (uint256 i = neuroEmissionMultipliers.length; i > 0; i--) { - if (neuroEmissionMultipliers[i - 1].finalized && timestamp >= neuroEmissionMultipliers[i - 1].timestamp) { - return neuroEmissionMultipliers[i - 1].multiplier; - } - } - return neuroEmissionMultipliers[0].multiplier; - } - - function initiateNeuroEmissionMultiplierUpdate(uint256 newMultiplier) external onlyVotersRegistrar { - if (!neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].finalized) { - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].multiplier = newMultiplier; - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].timestamp = - block.timestamp + - neuroEmissionMultiplierUpdateDelay; - } else { - neuroEmissionMultipliers.push( - ParanetLib.NeuroEmissionMultiplier({ - multiplier: newMultiplier, - timestamp: block.timestamp + neuroEmissionMultiplierUpdateDelay, - finalized: false - }) - ); - } - - emit NeuroEmissionMultiplierUpdateInitiated( - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 2].multiplier, - newMultiplier, - block.timestamp + neuroEmissionMultiplierUpdateDelay - ); - } - - function finalizeNeuroEmissionMultiplierUpdate() external onlyVotersRegistrar { - require(neuroEmissionMultipliers.length > 0, "No emission multiplier updates"); - require( - !neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].finalized, - "Last update already finalized" - ); - require( - block.timestamp >= neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].timestamp, - "Delay period not yet passed" - ); - - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].finalized = true; - - emit NeuroEmissionMultiplierUpdateFinalized( - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 2].multiplier, - neuroEmissionMultipliers[neuroEmissionMultipliers.length - 1].multiplier - ); - } - - function getTotalKnowledgeMinerIncentiveEstimation() public view returns (uint256) { - uint96 unrewardedTracSpent = paranetKnowledgeMinersRegistry.getUnrewardedTracSpent(msg.sender, parentParanetId); - - if (unrewardedTracSpent < ParanetLib.TOKENS_DIGITS_DIFF) { - return 0; - } - - // Unrewarded TRAC Spent = how much TRAC Knowledge Miner spent for Mining and haven't got a reward for - // Effective Emission Ratio = Current active Multiplier for how much NEURO is released per TRAC spent - // - // Basic Formula: - // Reward = UnrewardedTRAC * TotalEmissionRatio * (MinersRewardPercentage / 100) - // - // Example: - // Let's say we have 10 unrewarded TRAC, 0.5 NEURO per TRAC Total Emission and 80% Miners Reward Percentage, - // 10% Operator Reward Percentage, 10% Voters Reward Percentage - // Reward = (((10 * 10^18) * (5 * 10^11)) / (10^18)) * (10,000 - 1,000 - 1,000) / 10,000) = - // = 10 * 5 * 10^11 * 8,000 / 10,000 = 8/10 * (5 * 10^12) = 80% of 5 NEURO = 4 NEURO - return - (((unrewardedTracSpent * getEffectiveNeuroEmissionMultiplier(block.timestamp)) / - ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) * - (ParanetLib.PERCENTAGE_SCALING_FACTOR - - paranetOperatorRewardPercentage - - paranetIncentivizationProposalVotersRewardPercentage)) / ParanetLib.PERCENTAGE_SCALING_FACTOR; - } - - function getTotalAllKnowledgeMinersIncentiveEstimation() public view returns (uint256) { - return - _getIncentiveEstimation( - ParanetLib.PERCENTAGE_SCALING_FACTOR - - paranetOperatorRewardPercentage - - paranetIncentivizationProposalVotersRewardPercentage, - totalMinersClaimedNeuro - ); - } - - function getClaimableKnowledgeMinerRewardAmount() public view returns (uint256) { - uint256 neuroReward = getTotalKnowledgeMinerIncentiveEstimation(); - - // Here we should have a limit for Knowledge Miners, which is determined by the % of the Miners Reward - // and total NEURO received by the contract, so that Miners don't get tokens belonging to Operator/Voters - // Following the example from the above, if we have 100 NEURO as a total reward, Miners should never get - // more than 80 NEURO. minersRewardLimit = 80 NEURO - uint256 minersRewardLimit = ((address(this).balance + - totalMinersClaimedNeuro + - totalOperatorsClaimedNeuro + - totalVotersClaimedNeuro) * - (ParanetLib.PERCENTAGE_SCALING_FACTOR - - paranetOperatorRewardPercentage - - paranetIncentivizationProposalVotersRewardPercentage)) / ParanetLib.PERCENTAGE_SCALING_FACTOR; - - return - totalMinersClaimedNeuro + neuroReward <= minersRewardLimit - ? neuroReward - : minersRewardLimit - totalMinersClaimedNeuro; - } - - function getClaimableAllKnowledgeMinersRewardAmount() public view returns (uint256) { - uint256 neuroReward = getTotalAllKnowledgeMinersIncentiveEstimation(); - - uint256 minersRewardLimit = ((address(this).balance + - totalMinersClaimedNeuro + - totalOperatorsClaimedNeuro + - totalVotersClaimedNeuro) * - (ParanetLib.PERCENTAGE_SCALING_FACTOR - - paranetOperatorRewardPercentage - - paranetIncentivizationProposalVotersRewardPercentage)) / ParanetLib.PERCENTAGE_SCALING_FACTOR; - - return - totalMinersClaimedNeuro + neuroReward <= minersRewardLimit - ? neuroReward - : minersRewardLimit - totalMinersClaimedNeuro; - } - - function claimKnowledgeMinerReward() external onlyParanetKnowledgeMiner { - ParanetKnowledgeMinersRegistry pkmr = paranetKnowledgeMinersRegistry; - - uint256 neuroReward = getTotalKnowledgeMinerIncentiveEstimation(); - uint256 claimableNeuroReward = getClaimableKnowledgeMinerRewardAmount(); - - if (claimableNeuroReward == 0) { - revert ParanetLib.NoRewardAvailable(parentParanetId, msg.sender); - } - - // Updating the Unrewarded TRAC variable in the Knowledge Miner Profile - // If limit for reward wasn't exceeded, we set Unrewarded TRAC to 0, otherwise we need to calculate - // how many TRAC tokens were rewarded in this specific call and set variable to the amount that is left - // unrewarded - // - // Example: We have 100 NEURO total reward. 80 NEURO is for Knowledge Miners. Total NEURO Emission Rate is - // 0.5 NEURO per 1 TRAC. Knowledge Miner has 200 Unrewarded TRAC. 10% Operator Reward Percentage, - // 10% Voters Reward Percentage - // - // neuroReward = 100 NEURO = 100 * 10^12 - // claimableNeuroReward = 80 NEURO = 80 * 10^12 - // newUnrewardedTracSpent = (100 * 10^12 - 80 * 10^12) * 10^18) / (5 * 10^11) = (20 * 10^30) / (5 * 10^11) = - // = 40 * 10^18 = 40 TRAC - pkmr.setUnrewardedTracSpent( - msg.sender, - parentParanetId, - neuroReward == claimableNeuroReward - ? 0 - : uint96( - ((neuroReward - claimableNeuroReward) * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / - getEffectiveNeuroEmissionMultiplier(block.timestamp) - ) - ); - pkmr.addCumulativeAwardedNeuro(msg.sender, parentParanetId, claimableNeuroReward); - - if ( - claimedMinerRewards.length == 0 || - claimedMinerRewards[claimedMinerRewardsIndexes[msg.sender]].addr != msg.sender - ) { - claimedMinerRewardsIndexes[msg.sender] = claimedMinerRewards.length; - claimedMinerRewards.push( - ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile({ - addr: msg.sender, - claimedNeuro: claimableNeuroReward - }) - ); - } else { - claimedMinerRewards[claimedMinerRewardsIndexes[msg.sender]].claimedNeuro += claimableNeuroReward; - } - totalMinersClaimedNeuro += claimableNeuroReward; - - payable(msg.sender).transfer(claimableNeuroReward); - - emit ParanetKnowledgeMinerRewardClaimed(msg.sender, claimableNeuroReward); - } - - function getTotalParanetOperatorIncentiveEstimation() public view returns (uint256) { - return _getIncentiveEstimation(paranetOperatorRewardPercentage, totalOperatorsClaimedNeuro); - } - - function getClaimableParanetOperatorRewardAmount() public view returns (uint256) { - uint256 neuroReward = getTotalParanetOperatorIncentiveEstimation(); - - uint256 operatorRewardLimit = ((address(this).balance + - totalMinersClaimedNeuro + - totalOperatorsClaimedNeuro + - totalVotersClaimedNeuro) * paranetOperatorRewardPercentage) / ParanetLib.PERCENTAGE_SCALING_FACTOR; - - return - totalOperatorsClaimedNeuro + neuroReward <= operatorRewardLimit - ? neuroReward - : operatorRewardLimit - totalOperatorsClaimedNeuro; - } - - function claimParanetOperatorReward() external onlyParanetOperator { - uint256 claimableNeuroReward = getClaimableParanetOperatorRewardAmount(); - - if (claimableNeuroReward == 0) { - revert ParanetLib.NoRewardAvailable(parentParanetId, msg.sender); - } - - if ( - claimedOperatorRewards.length == 0 || - claimedOperatorRewards[claimedOperatorRewardsIndexes[msg.sender]].addr != msg.sender - ) { - claimedOperatorRewardsIndexes[msg.sender] = claimedOperatorRewards.length; - claimedOperatorRewards.push( - ParanetLib.ParanetIncentivesPoolClaimedRewardsProfile({ - addr: msg.sender, - claimedNeuro: claimableNeuroReward - }) - ); - } else { - claimedOperatorRewards[claimedOperatorRewardsIndexes[msg.sender]].claimedNeuro += claimableNeuroReward; - } - totalOperatorsClaimedNeuro += claimableNeuroReward; - - payable(msg.sender).transfer(claimableNeuroReward); - - emit ParanetOperatorRewardClaimed(msg.sender, claimableNeuroReward); - } - - function getTotalProposalVoterIncentiveEstimation() public view returns (uint256) { - uint256 effectiveNeuroEmissionMultiplier = getEffectiveNeuroEmissionMultiplier(block.timestamp); - uint96 cumulativeKnowledgeValueSingleVoterPart = (((paranetsRegistry.getCumulativeKnowledgeValue( - parentParanetId - ) * paranetIncentivizationProposalVotersRewardPercentage) / ParanetLib.PERCENTAGE_SCALING_FACTOR) * - voters[votersIndexes[msg.sender]].weight) / ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT; - uint96 rewardedTracSpentSingleVoterPart = uint96( - (voters[votersIndexes[msg.sender]].claimedNeuro * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / - effectiveNeuroEmissionMultiplier - ); - - if ( - cumulativeKnowledgeValueSingleVoterPart - rewardedTracSpentSingleVoterPart < ParanetLib.TOKENS_DIGITS_DIFF - ) { - return 0; - } - - return - ((cumulativeKnowledgeValueSingleVoterPart * effectiveNeuroEmissionMultiplier) / - ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) - voters[votersIndexes[msg.sender]].claimedNeuro; - } - - function getTotalAllProposalVotersIncentiveEstimation() public view returns (uint256) { - return _getIncentiveEstimation(paranetIncentivizationProposalVotersRewardPercentage, totalVotersClaimedNeuro); - } - - function getClaimableProposalVoterRewardAmount() public view returns (uint256) { - if (voters.length == 0 || voters[votersIndexes[msg.sender]].addr != msg.sender) { - return 0; - } - - uint256 neuroReward = getTotalProposalVoterIncentiveEstimation(); - - uint256 voterRewardLimit = ((((address(this).balance + - totalMinersClaimedNeuro + - totalOperatorsClaimedNeuro + - totalVotersClaimedNeuro) * paranetIncentivizationProposalVotersRewardPercentage) / - ParanetLib.PERCENTAGE_SCALING_FACTOR) * voters[votersIndexes[msg.sender]].weight) / - ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT; - - return - voters[votersIndexes[msg.sender]].claimedNeuro + neuroReward <= voterRewardLimit - ? neuroReward - : voterRewardLimit - voters[votersIndexes[msg.sender]].claimedNeuro; - } - - function getClaimableAllProposalVotersRewardAmount() public view returns (uint256) { - uint256 neuroReward = getTotalAllProposalVotersIncentiveEstimation(); - - uint256 votersRewardLimit = ((address(this).balance + - totalMinersClaimedNeuro + - totalOperatorsClaimedNeuro + - totalVotersClaimedNeuro) * paranetIncentivizationProposalVotersRewardPercentage) / - ParanetLib.PERCENTAGE_SCALING_FACTOR; - - return - totalVotersClaimedNeuro + neuroReward <= votersRewardLimit - ? neuroReward - : votersRewardLimit - totalVotersClaimedNeuro; - } - - function claimIncentivizationProposalVoterReward() external onlyParanetIncentivizationProposalVoter { - if (cumulativeVotersWeight != ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT) { - revert ParanetLib.InvalidCumulativeVotersWeight( - parentParanetId, - cumulativeVotersWeight, - ParanetLib.MAX_CUMULATIVE_VOTERS_WEIGHT - ); - } - - uint256 claimableNeuroReward = getClaimableProposalVoterRewardAmount(); - - if (claimableNeuroReward == 0) { - revert ParanetLib.NoRewardAvailable(parentParanetId, msg.sender); - } - - voters[votersIndexes[msg.sender]].claimedNeuro += claimableNeuroReward; - totalVotersClaimedNeuro += claimableNeuroReward; - - payable(msg.sender).transfer(claimableNeuroReward); - - emit ParanetIncentivizationProposalVoterRewardClaimed(msg.sender, claimableNeuroReward); - } - - function _getIncentiveEstimation( - uint16 rewardPercentage, - uint256 totalClaimedNeuro - ) internal view returns (uint256) { - uint256 effectiveNeuroEmissionMultiplier = getEffectiveNeuroEmissionMultiplier(block.timestamp); - uint96 cumulativeKnowledgeValuePart = (paranetsRegistry.getCumulativeKnowledgeValue(parentParanetId) * - rewardPercentage) / ParanetLib.PERCENTAGE_SCALING_FACTOR; - uint96 rewardedTracSpentPart = uint96( - (totalClaimedNeuro * ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) / effectiveNeuroEmissionMultiplier - ); - - if (cumulativeKnowledgeValuePart - rewardedTracSpentPart < ParanetLib.TOKENS_DIGITS_DIFF) { - return 0; - } - - return - ((cumulativeKnowledgeValuePart * effectiveNeuroEmissionMultiplier) / - ParanetLib.EMISSION_MULTIPLIER_SCALING_FACTOR) - totalClaimedNeuro; - } - - function _checkHubOwner() internal view virtual { - require(msg.sender == hub.owner(), "Fn can only be used by hub owner"); - } - - function _checkVotersRegistrar() internal view virtual { - require(msg.sender == votersRegistrar, "Fn can only be used by registrar"); - } - - function _checkParanetOperator() internal view virtual { - require(isParanetOperator(msg.sender), "Fn can only be used by operator"); - } - - function _checkParanetIncentivizationProposalVoter() internal view virtual { - require(isProposalVoter(msg.sender), "Fn can only be used by voter"); - } - - function _checkParanetKnowledgeMiner() internal view virtual { - require(isKnowledgeMiner(msg.sender), "Fn can only be used by K-Miners"); - } -} diff --git a/contracts/storage/paranets/ParanetKnowledgeAssetsRegistry.sol b/contracts/storage/paranets/ParanetKnowledgeAssetsRegistry.sol deleted file mode 100644 index 2d6043fe..00000000 --- a/contracts/storage/paranets/ParanetKnowledgeAssetsRegistry.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.20; - -import {HubDependent} from "../../abstract/HubDependent.sol"; -import {INamed} from "../../interfaces/INamed.sol"; -import {IVersioned} from "../../interfaces/IVersioned.sol"; -import {ParanetLib} from "../../libraries/ParanetLib.sol"; - -contract ParanetKnowledgeAssetsRegistry is INamed, IVersioned, HubDependent { - string private constant _NAME = "ParanetKnowledgeAssetsRegistry"; - string private constant _VERSION = "1.0.0"; - - // Knowledge Asset ID => Knowledge Asset On Paranet - mapping(bytes32 => ParanetLib.KnowledgeAsset) internal knowledgeAssets; - - // solhint-disable-next-line no-empty-blocks - constructor(address hubAddress) HubDependent(hubAddress) {} - - function name() external pure virtual override returns (string memory) { - return _NAME; - } - - function version() external pure virtual override returns (string memory) { - return _VERSION; - } - - function addKnowledgeAsset( - bytes32 paranetId, - address knowledgeAssetStorageContract, - uint256 tokenId, - address miner - ) external onlyContracts returns (bytes32) { - knowledgeAssets[keccak256(abi.encodePacked(knowledgeAssetStorageContract, tokenId))] = ParanetLib - .KnowledgeAsset({ - knowledgeAssetStorageContract: knowledgeAssetStorageContract, - tokenId: tokenId, - minerAddress: miner, - paranetId: paranetId - }); - - return keccak256(abi.encodePacked(knowledgeAssetStorageContract, tokenId)); - } - - function removeKnowledgeAsset(bytes32 knowledgeAssetId) external onlyContracts { - delete knowledgeAssets[knowledgeAssetId]; - } - - function isParanetKnowledgeAsset(bytes32 knowledgeAssetId) external view returns (bool) { - return - keccak256( - abi.encodePacked( - knowledgeAssets[knowledgeAssetId].knowledgeAssetStorageContract, - knowledgeAssets[knowledgeAssetId].tokenId - ) - ) == knowledgeAssetId; - } - - function getKnowledgeAssetObject( - bytes32 knowledgeAssetId - ) external view returns (ParanetLib.KnowledgeAsset memory) { - return knowledgeAssets[knowledgeAssetId]; - } - - function getKnowledgeAssetLocator(bytes32 knowledgeAssetId) external view returns (address, uint256) { - return ( - knowledgeAssets[knowledgeAssetId].knowledgeAssetStorageContract, - knowledgeAssets[knowledgeAssetId].tokenId - ); - } - - function getMinerAddress(bytes32 knowledgeAssetId) external view returns (address) { - return knowledgeAssets[knowledgeAssetId].minerAddress; - } - - function setMinerAddress(bytes32 knowledgeAssetId, address minerAddress) external onlyContracts { - knowledgeAssets[knowledgeAssetId].minerAddress = minerAddress; - } - - function getParanetId(bytes32 knowledgeAssetId) external view returns (bytes32) { - return knowledgeAssets[knowledgeAssetId].paranetId; - } - - function setParanetId(bytes32 knowledgeAssetId, bytes32 paranetId) external onlyContracts { - knowledgeAssets[knowledgeAssetId].paranetId = paranetId; - } -} diff --git a/contracts/storage/paranets/ParanetKnowledgeCollectionsRegistry.sol b/contracts/storage/paranets/ParanetKnowledgeCollectionsRegistry.sol new file mode 100644 index 00000000..2ac22a68 --- /dev/null +++ b/contracts/storage/paranets/ParanetKnowledgeCollectionsRegistry.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {HubDependent} from "../../abstract/HubDependent.sol"; +import {INamed} from "../../interfaces/INamed.sol"; +import {IVersioned} from "../../interfaces/IVersioned.sol"; +import {ParanetLib} from "../../libraries/ParanetLib.sol"; + +contract ParanetKnowledgeCollectionsRegistry is INamed, IVersioned, HubDependent { + string private constant _NAME = "ParanetknowledgeCollectionIdRegistry"; + string private constant _VERSION = "1.0.1"; + + // Knowledge Collection ID => Knowledge Collection On Paranet + mapping(bytes32 => ParanetLib.KnowledgeCollection) internal knowledgeCollections; + + // solhint-disable-next-line no-empty-blocks + constructor(address hubAddress) HubDependent(hubAddress) {} + + function name() external pure virtual override returns (string memory) { + return _NAME; + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + + function addKnowledgeCollection( + bytes32 paranetId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + address miner + ) external onlyContracts returns (bytes32) { + bytes32 kcId = keccak256(abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId)); + knowledgeCollections[kcId] = ParanetLib.KnowledgeCollection({ + knowledgeCollectionStorageContract: knowledgeCollectionStorageContract, + knowledgeCollectionTokenId: knowledgeCollectionTokenId, + minerAddress: miner, + paranetId: paranetId + }); + + return kcId; + } + + function removeKnowledgeCollection(bytes32 knowledgeCollectionId) external onlyContracts { + delete knowledgeCollections[knowledgeCollectionId]; + } + + function isParanetKnowledgeCollection(bytes32 knowledgeCollectionId) external view returns (bool) { + return + keccak256( + abi.encodePacked( + knowledgeCollections[knowledgeCollectionId].knowledgeCollectionStorageContract, + knowledgeCollections[knowledgeCollectionId].knowledgeCollectionTokenId + ) + ) == knowledgeCollectionId; + } + + function getKnowledgeCollectionObject( + bytes32 knowledgeCollectionId + ) external view returns (ParanetLib.KnowledgeCollection memory) { + return knowledgeCollections[knowledgeCollectionId]; + } + + function getKnowledgeCollectionLocator(bytes32 knowledgeCollectionId) external view returns (address, uint256) { + return ( + knowledgeCollections[knowledgeCollectionId].knowledgeCollectionStorageContract, + knowledgeCollections[knowledgeCollectionId].knowledgeCollectionTokenId + ); + } + + function getKnowledgeCollectionLocators( + bytes32[] calldata knowledgeCollectionIds + ) external view returns (ParanetLib.UniversalAssetCollectionLocator[] memory) { + uint256 length = knowledgeCollectionIds.length; + + ParanetLib.UniversalAssetCollectionLocator[] memory locators = new ParanetLib.UniversalAssetCollectionLocator[]( + length + ); + + for (uint256 i = 0; i < length; i++) { + bytes32 id = knowledgeCollectionIds[i]; + + locators[i] = ParanetLib.UniversalAssetCollectionLocator({ + knowledgeCollectionStorageContract: knowledgeCollections[id].knowledgeCollectionStorageContract, + knowledgeCollectionTokenId: knowledgeCollections[id].knowledgeCollectionTokenId + }); + } + + return locators; + } + + function getMinerAddress(bytes32 knowledgeCollectionId) external view returns (address) { + return knowledgeCollections[knowledgeCollectionId].minerAddress; + } + + function setMinerAddress(bytes32 knowledgeCollectionId, address minerAddress) external onlyContracts { + knowledgeCollections[knowledgeCollectionId].minerAddress = minerAddress; + } + + function getParanetId(bytes32 knowledgeCollectionId) external view returns (bytes32) { + return knowledgeCollections[knowledgeCollectionId].paranetId; + } + + function setParanetId(bytes32 knowledgeCollectionId, bytes32 paranetId) external onlyContracts { + knowledgeCollections[knowledgeCollectionId].paranetId = paranetId; + } +} diff --git a/contracts/storage/paranets/ParanetKnowledgeMinersRegistry.sol b/contracts/storage/paranets/ParanetKnowledgeMinersRegistry.sol index adb8682d..ad417bf9 100644 --- a/contracts/storage/paranets/ParanetKnowledgeMinersRegistry.sol +++ b/contracts/storage/paranets/ParanetKnowledgeMinersRegistry.sol @@ -6,6 +6,7 @@ import {ParanetsRegistry} from "./ParanetsRegistry.sol"; import {HubDependent} from "../../abstract/HubDependent.sol"; import {INamed} from "../../interfaces/INamed.sol"; import {IVersioned} from "../../interfaces/IVersioned.sol"; +import {IParanetIncentivesPool} from "../../interfaces/IParanetIncentivesPool.sol"; import {ParanetLib} from "../../libraries/ParanetLib.sol"; contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { @@ -56,7 +57,7 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { ParanetLib.KnowledgeMinerMetadata({ addr: addr, totalTracSpent: knowledgeMiners[addr].totalTracSpent, - totalSubmittedKnowledgeAssetsCount: knowledgeMiners[addr].totalSubmittedKnowledgeAssetsCount + totalSubmittedKnowledgeCollectionsCount: knowledgeMiners[addr].totalSubmittedKnowledgeCollectionsCount }); } @@ -76,71 +77,74 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { knowledgeMiners[miner].totalTracSpent -= subtractedTracSpent; } - function getTotalSubmittedKnowledgeAssetsCount(address miner) external view returns (uint256) { - return knowledgeMiners[miner].totalSubmittedKnowledgeAssetsCount; + function getTotalSubmittedKnowledgeCollectionsCount(address miner) external view returns (uint256) { + return knowledgeMiners[miner].totalSubmittedKnowledgeCollectionsCount; } - function setTotalSubmittedKnowledgeAssetsCount( + function setTotalSubmittedKnowledgeCollectionsCount( address miner, - uint256 totalSubmittedKnowledgeAssetsCount + uint256 totalSubmittedKnowledgeCollectionsCount ) external onlyContracts { - knowledgeMiners[miner].totalSubmittedKnowledgeAssetsCount = totalSubmittedKnowledgeAssetsCount; + knowledgeMiners[miner].totalSubmittedKnowledgeCollectionsCount = totalSubmittedKnowledgeCollectionsCount; } - function incrementTotalSubmittedKnowledgeAssetsCount(address miner) external onlyContracts { + function incrementTotalSubmittedKnowledgeCollectionsCount(address miner) external onlyContracts { unchecked { - knowledgeMiners[miner].totalSubmittedKnowledgeAssetsCount++; + knowledgeMiners[miner].totalSubmittedKnowledgeCollectionsCount++; } } - function decrementTotalSubmittedKnowledgeAssetsCount(address miner) external onlyContracts { + function decrementTotalSubmittedKnowledgeCollectionsCount(address miner) external onlyContracts { unchecked { - knowledgeMiners[miner].totalSubmittedKnowledgeAssetsCount--; + knowledgeMiners[miner].totalSubmittedKnowledgeCollectionsCount--; } } - function addSubmittedKnowledgeAsset( + function addSubmittedKnowledgeCollection( address miner, bytes32 paranetId, - bytes32 knowledgeAssetId + bytes32 knowledgeCollectionId ) external onlyContracts { - knowledgeMiners[miner].submittedKnowledgeAssetsIndexes[paranetId][knowledgeAssetId] = knowledgeMiners[miner] - .submittedKnowledgeAssets[paranetId] - .length; - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId].push(knowledgeAssetId); + knowledgeMiners[miner].submittedKnowledgeCollectionsIndexes[paranetId][knowledgeCollectionId] = knowledgeMiners[ + miner + ].submittedKnowledgeCollections[paranetId].length; + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId].push(knowledgeCollectionId); } - function removeSubmittedKnowledgeAsset( + function removeSubmittedKnowledgeCollection( address miner, bytes32 paranetId, - bytes32 knowledgeAssetId + bytes32 knowledgeCollectionId ) external onlyContracts { // 1. Move the last element to the slot of the element to remove - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId][ - knowledgeMiners[miner].submittedKnowledgeAssetsIndexes[paranetId][knowledgeAssetId] - ] = knowledgeMiners[miner].submittedKnowledgeAssets[paranetId][ - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId].length - 1 + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId][ + knowledgeMiners[miner].submittedKnowledgeCollectionsIndexes[paranetId][knowledgeCollectionId] + ] = knowledgeMiners[miner].submittedKnowledgeCollections[paranetId][ + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId].length - 1 ]; // 2. Update the index of the moved element - knowledgeMiners[miner].submittedKnowledgeAssetsIndexes[paranetId][ - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId][ - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId].length - 1 + knowledgeMiners[miner].submittedKnowledgeCollectionsIndexes[paranetId][ + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId][ + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId].length - 1 ] - ] = knowledgeMiners[miner].submittedKnowledgeAssetsIndexes[paranetId][knowledgeAssetId]; + ] = knowledgeMiners[miner].submittedKnowledgeCollectionsIndexes[paranetId][knowledgeCollectionId]; // 3. Remove the last element from the array - knowledgeMiners[miner].submittedKnowledgeAssets[paranetId].pop(); + knowledgeMiners[miner].submittedKnowledgeCollections[paranetId].pop(); // 4. Delete the index of the removed element - delete knowledgeMiners[miner].submittedKnowledgeAssetsIndexes[paranetId][knowledgeAssetId]; + delete knowledgeMiners[miner].submittedKnowledgeCollectionsIndexes[paranetId][knowledgeCollectionId]; } - function getSubmittedKnowledgeAssets(address miner, bytes32 paranetId) external view returns (bytes32[] memory) { - return knowledgeMiners[miner].submittedKnowledgeAssets[paranetId]; + function getSubmittedKnowledgeCollections( + address miner, + bytes32 paranetId + ) external view returns (bytes32[] memory) { + return knowledgeMiners[miner].submittedKnowledgeCollections[paranetId]; } - function getSubmittedKnowledgeAssets( + function getSubmittedKnowledgeCollections( address miner, bytes32 paranetId, uint256 start, @@ -148,13 +152,13 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { ) external view returns (bytes32[] memory) { require(start <= end, "Start should be <= End"); require( - end <= knowledgeMiners[miner].submittedKnowledgeAssets[paranetId].length, + end <= knowledgeMiners[miner].submittedKnowledgeCollections[paranetId].length, "End should be <= length of Array" ); bytes32[] memory slice = new bytes32[](end - start); for (uint256 i; i < slice.length; ) { - slice[i] = knowledgeMiners[miner].submittedKnowledgeAssets[paranetId][i]; + slice[i] = knowledgeMiners[miner].submittedKnowledgeCollections[paranetId][i]; unchecked { i++; @@ -164,91 +168,93 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { return slice; } - function addUpdatingKnowledgeAssetState( + // This should be called on update from KC + function addUpdatingKnowledgeCollectionState( address miner, bytes32 paranetId, - address knowledgeAssetStorageContract, - uint256 tokenId, - bytes32 assertionId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionId, + bytes32 merkleRoot, uint96 updateTokenAmount ) external onlyContracts { - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][ - keccak256(abi.encodePacked(knowledgeAssetStorageContract, tokenId, assertionId)) - ] = knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length; - - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].push( - ParanetLib.UpdatingKnowledgeAssetState({ - knowledgeAssetStorageContract: knowledgeAssetStorageContract, - tokenId: tokenId, - assertionId: assertionId, + knowledgeMiners[miner].updatingKnowledgeCollectionsStateIndexes[paranetId][ + keccak256(abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionId, merkleRoot)) + ] = knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId].length; + + knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId].push( + ParanetLib.UpdatingKnowledgeCollectionState({ + knowledgeCollectionStorageContract: knowledgeCollectionStorageContract, + knowledgeCollectionId: knowledgeCollectionId, + merkleRoot: merkleRoot, updateTokenAmount: updateTokenAmount }) ); } - function removeUpdatingKnowledgeAssetState( - address miner, - bytes32 paranetId, - bytes32 knowledgeAssetStateId - ) external onlyContracts { - // 1. Move the last element to the slot of the element to remove - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId] - ] = knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length - 1 - ]; - - // 2. Update the index of the moved element - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][ - keccak256( - abi.encodePacked( - knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length - 1 - ].knowledgeAssetStorageContract, - knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length - 1 - ].tokenId, - knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length - 1 - ].assertionId - ) - ) - ] = knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId]; - - // 3. Remove the last element from the array - knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].pop(); - - // 4. Delete the index of the removed element - delete knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId]; - } - - function getUpdatingKnowledgeAssetStates( + // If we do this on update + // function removeUpdatingKnowledgeCollectionState( + // address miner, + // bytes32 paranetId, + // bytes32 knowledgeCollectionStateId + // ) external onlyContracts { + // // 1. Move the last element to the slot of the element to remove + // knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId][ + // knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId][knowledgeCollectionStateId] + // ] = knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId][ + // knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId].length - 1 + // ]; + + // // 2. Update the index of the moved element + // knowledgeMiners[miner].updatingKnowledgeCollectionsStateIndexes[paranetId][ + // keccak256( + // abi.encodePacked( + // knowledgeMiners[miner] + // .updatingKnowledgeCollectionStates[paranetId][ + // knowledgeMiners[miner].updatingKnowledgeCollectionStates[paranetId].length - 1 + // ].knowledgeCollectionStorageContract, + // knowledgeMiners[miner] + // .updatingKnowledgeCollectionStates[paranetId][ + // knowledgeMiners[miner].updatingKnowledgeCollectionStates[paranetId].length - 1 + // ].tokenId, + // knowledgeMiners[miner] + // .updatingKnowledgeCollectionStates[paranetId][ + // knowledgeMiners[miner].updatingKnowledgeCollectionStates[paranetId].length - 1 + // ].merkleRoot + // ) + // ) + // ] = knowledgeMiners[miner].updatingKnowledgeCollectionStateIndexes[paranetId][knowledgeCollectionStateId]; + + // // 3. Remove the last element from the array + // knowledgeMiners[miner].updatingKnowledgeCollectionStates[paranetId].pop(); + + // // 4. Delete the index of the removed element + // delete knowledgeMiners[miner].updatingKnowledgeCollectionStateIndexes[paranetId][knowledgeCollectionStateId]; + // } + + function getUpdatingKnowledgeCollectionStates( address miner, bytes32 paranetId - ) external view returns (ParanetLib.UpdatingKnowledgeAssetState[] memory) { - return knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId]; + ) external view returns (ParanetLib.UpdatingKnowledgeCollectionState[] memory) { + return knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId]; } - function getUpdatingKnowledgeAssetStates( + function getUpdatingKnowledgeCollectionStates( address miner, bytes32 paranetId, uint256 start, uint256 end - ) external view returns (ParanetLib.UpdatingKnowledgeAssetState[] memory) { + ) external view returns (ParanetLib.UpdatingKnowledgeCollectionState[] memory) { require(start <= end, "Start should be <= End"); require( - end <= knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId].length, + end <= knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId].length, "End should be <= length of Array" ); - ParanetLib.UpdatingKnowledgeAssetState[] memory slice = new ParanetLib.UpdatingKnowledgeAssetState[]( + ParanetLib.UpdatingKnowledgeCollectionState[] memory slice = new ParanetLib.UpdatingKnowledgeCollectionState[]( end - start ); for (uint256 i; i < slice.length; ) { - slice[i] = knowledgeMiners[miner].updatingKnowledgeAssetStates[paranetId][i]; + slice[i] = knowledgeMiners[miner].updatingKnowledgeCollectionsStates[paranetId][i]; unchecked { i++; @@ -258,39 +264,39 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { return slice; } - function setUpdatingKnowledgeAssetUpdateTokenAmount( + function setUpdatingKnowledgeCollectionUpdateTokenAmount( address miner, bytes32 paranetId, - bytes32 knowledgeAssetStateId, + bytes32 knowledgeCollectionStateId, uint96 updateTokenAmount ) external onlyContracts { knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId] + .updatingKnowledgeCollectionsStates[paranetId][ + knowledgeMiners[miner].updatingKnowledgeCollectionsStateIndexes[paranetId][knowledgeCollectionStateId] ].updateTokenAmount = updateTokenAmount; } - function addUpdatingKnowledgeAssetUpdateTokenAmount( + function addUpdatingKnowledgeCollectionUpdateTokenAmount( address miner, bytes32 paranetId, - bytes32 knowledgeAssetStateId, + bytes32 knowledgeCollectionStateId, uint96 addedUpdateTokenAmount ) external onlyContracts { knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId] + .updatingKnowledgeCollectionsStates[paranetId][ + knowledgeMiners[miner].updatingKnowledgeCollectionsStateIndexes[paranetId][knowledgeCollectionStateId] ].updateTokenAmount += addedUpdateTokenAmount; } - function subUpdatingKnowledgeAssetUpdateTokenAmount( + function subUpdatingKnowledgeCollectionUpdateTokenAmount( address miner, bytes32 paranetId, - bytes32 knowledgeAssetStateId, + bytes32 knowledgeCollectionStateId, uint96 subtractedUpdateTokenAmount ) external onlyContracts { knowledgeMiners[miner] - .updatingKnowledgeAssetStates[paranetId][ - knowledgeMiners[miner].updatingKnowledgeAssetStateIndexes[paranetId][knowledgeAssetStateId] + .updatingKnowledgeCollectionsStates[paranetId][ + knowledgeMiners[miner].updatingKnowledgeCollectionsStateIndexes[paranetId][knowledgeCollectionStateId] ].updateTokenAmount -= subtractedUpdateTokenAmount; } @@ -346,37 +352,41 @@ contract ParanetKnowledgeMinersRegistry is INamed, IVersioned, HubDependent { knowledgeMiners[miner].unrewardedTracSpent[paranetId] -= subtractedUnrewardedTracSpent; } - function getCumulativeAwardedNeuro(address miner, bytes32 paranetId) external view returns (uint256) { - return knowledgeMiners[miner].cumulativeAwardedNeuro[paranetId]; + function getcumulativeAwardedToken(address miner, bytes32 paranetId) external view returns (uint256) { + return knowledgeMiners[miner].cumulativeAwardedToken[paranetId]; } - function setCumulativeAwardedNeuro( + function setcumulativeAwardedToken( address miner, bytes32 paranetId, - uint256 cumulativeAwardedNeuro + uint256 cumulativeAwardedToken ) external onlyContractsOrIncentivesPool(paranetId) { - knowledgeMiners[miner].cumulativeAwardedNeuro[paranetId] = cumulativeAwardedNeuro; + knowledgeMiners[miner].cumulativeAwardedToken[paranetId] = cumulativeAwardedToken; } - function addCumulativeAwardedNeuro( + function addcumulativeAwardedToken( address miner, bytes32 paranetId, - uint256 addedCumulativeAwardedNeuro + uint256 addedcumulativeAwardedToken ) external onlyContractsOrIncentivesPool(paranetId) { - knowledgeMiners[miner].cumulativeAwardedNeuro[paranetId] += addedCumulativeAwardedNeuro; + knowledgeMiners[miner].cumulativeAwardedToken[paranetId] += addedcumulativeAwardedToken; } - function subCumulativeAwardedNeuro( + function subcumulativeAwardedToken( address miner, bytes32 paranetId, - uint256 subtractedCumulativeAwardedNeuro + uint256 subtractedcumulativeAwardedToken ) external onlyContractsOrIncentivesPool(paranetId) { - knowledgeMiners[miner].cumulativeAwardedNeuro[paranetId] -= subtractedCumulativeAwardedNeuro; + knowledgeMiners[miner].cumulativeAwardedToken[paranetId] -= subtractedcumulativeAwardedToken; } function _checkSender(bytes32 paranetId) internal view virtual { require( - hub.isContract(msg.sender) || paranetsRegistry.hasIncentivesPoolByAddress(paranetId, msg.sender), + hub.isContract(msg.sender) || + paranetsRegistry.hasIncentivesPoolByStorageAddress( + paranetId, + IParanetIncentivesPool(msg.sender).getParanetIncentivesPoolStorage() + ), "Hub/IncentivesPool function" ); } diff --git a/contracts/storage/paranets/ParanetServicesRegistry.sol b/contracts/storage/paranets/ParanetServicesRegistry.sol index 60ff380a..a8c39d05 100644 --- a/contracts/storage/paranets/ParanetServicesRegistry.sol +++ b/contracts/storage/paranets/ParanetServicesRegistry.sol @@ -26,17 +26,20 @@ contract ParanetServicesRegistry is INamed, IVersioned, HubDependent { } function registerParanetService( - address paranetServiceKAStorageContract, + address paranetServiceKCStorageContract, + uint256 paranetServiceKCTokenId, uint256 paranetServiceKATokenId, string calldata paranetServiceName, string calldata paranetServiceDescription, address[] calldata paranetServiceAddresses ) external onlyContracts returns (bytes32) { - ParanetLib.ParanetService storage paranetService = paranetServices[ - keccak256(abi.encodePacked(paranetServiceKAStorageContract, paranetServiceKATokenId)) - ]; + bytes32 paranetServiceId = keccak256( + abi.encodePacked(paranetServiceKCStorageContract, paranetServiceKCTokenId, paranetServiceKATokenId) + ); + ParanetLib.ParanetService storage paranetService = paranetServices[paranetServiceId]; - paranetService.paranetServiceKAStorageContract = paranetServiceKAStorageContract; + paranetService.paranetServiceKCStorageContract = paranetServiceKCStorageContract; + paranetService.paranetServiceKCTokenId = paranetServiceKCTokenId; paranetService.paranetServiceKATokenId = paranetServiceKATokenId; paranetService.name = paranetServiceName; paranetService.description = paranetServiceDescription; @@ -50,7 +53,7 @@ contract ParanetServicesRegistry is INamed, IVersioned, HubDependent { } } - return keccak256(abi.encodePacked(paranetServiceKAStorageContract, paranetServiceKATokenId)); + return paranetServiceId; } function deleteParanetService(bytes32 paranetServiceId) external onlyContracts { @@ -61,7 +64,8 @@ contract ParanetServicesRegistry is INamed, IVersioned, HubDependent { return keccak256( abi.encodePacked( - paranetServices[paranetServiceId].paranetServiceKAStorageContract, + paranetServices[paranetServiceId].paranetServiceKCStorageContract, + paranetServices[paranetServiceId].paranetServiceKCTokenId, paranetServices[paranetServiceId].paranetServiceKATokenId ) ) == paranetServiceId; @@ -72,7 +76,8 @@ contract ParanetServicesRegistry is INamed, IVersioned, HubDependent { ) external view returns (ParanetLib.ParanetServiceMetadata memory) { return ParanetLib.ParanetServiceMetadata({ - paranetServiceKAStorageContract: paranetServices[paranetServiceId].paranetServiceKAStorageContract, + paranetServiceKCStorageContract: paranetServices[paranetServiceId].paranetServiceKCStorageContract, + paranetServiceKCTokenId: paranetServices[paranetServiceId].paranetServiceKCTokenId, paranetServiceKATokenId: paranetServices[paranetServiceId].paranetServiceKATokenId, name: paranetServices[paranetServiceId].name, description: paranetServices[paranetServiceId].description, @@ -80,9 +85,12 @@ contract ParanetServicesRegistry is INamed, IVersioned, HubDependent { }); } - function getParanetServiceKnowledgeAssetLocator(bytes32 paranetServiceId) external view returns (address, uint256) { + function getParanetServiceKnowledgeCollectionLocator( + bytes32 paranetServiceId + ) external view returns (address, uint256, uint256) { return ( - paranetServices[paranetServiceId].paranetServiceKAStorageContract, + paranetServices[paranetServiceId].paranetServiceKCStorageContract, + paranetServices[paranetServiceId].paranetServiceKCTokenId, paranetServices[paranetServiceId].paranetServiceKATokenId ); } diff --git a/contracts/storage/paranets/ParanetStagingRegistry.sol b/contracts/storage/paranets/ParanetStagingRegistry.sol new file mode 100644 index 00000000..2ebcaeed --- /dev/null +++ b/contracts/storage/paranets/ParanetStagingRegistry.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.20; + +import {INamed} from "../../interfaces/INamed.sol"; +import {IVersioned} from "../../interfaces/IVersioned.sol"; +import {HubDependent} from "../../abstract/HubDependent.sol"; +import {ParanetLib} from "../../libraries/ParanetLib.sol"; + +contract ParanetStagingRegistry is INamed, IVersioned, HubDependent { + string private constant _NAME = "ParanetStagingRegistry"; + string private constant _VERSION = "1.0.0"; + + // Events + event KnowledgeCollectionStaged( + bytes32 indexed paranetId, + bytes32 indexed knowledgeCollectionId, + address indexed submitter + ); + event KnowledgeCollectionReviewed(bytes32 indexed paranetId, bytes32 indexed knowledgeCollectionId, bool accepted); + event CuratorAdded(bytes32 indexed paranetId, address indexed curator); + event CuratorRemoved(bytes32 indexed paranetId, address indexed curator); + + struct StagedCollection { + bytes32 knowledgeCollectionId; + address submitter; + ParanetLib.RequestStatus status; + } + + // Paranet ID => Collection ID => Staging Status + mapping(bytes32 => mapping(bytes32 => ParanetLib.RequestStatus)) public stagedCollections; + + // Paranet ID => Collection ID => Submitter Address + mapping(bytes32 => mapping(bytes32 => address)) public collectionSubmitters; + + // Paranet ID => Curator Address => Is Curator + mapping(bytes32 => mapping(address => bool)) public curators; + + // Paranet ID => Curator => Index + mapping(bytes32 => mapping(address => uint256)) public paranetCuratorIndexes; + // Paranet ID => Curators + mapping(bytes32 => address[]) public paranetCurators; + + // Track collections for pagination + mapping(bytes32 => bytes32[]) private pendingknowledgeCollectionIds; + // Paranet ID => Collection ID => Index + mapping(bytes32 => mapping(bytes32 => uint256)) private pendingCollectionIndexes; + + constructor(address hubAddress) HubDependent(hubAddress) {} + + function name() external pure override returns (string memory) { + return _NAME; + } + + function version() external pure override returns (string memory) { + return _VERSION; + } + + function addCurator(bytes32 paranetId, address curator) public onlyContracts { + paranetCuratorIndexes[paranetId][curator] = paranetCurators[paranetId].length; + paranetCurators[paranetId].push(curator); + + curators[paranetId][curator] = true; + + emit CuratorAdded(paranetId, curator); + } + + function removeCurator(bytes32 paranetId, address curator) public onlyContracts { + uint256 index = paranetCuratorIndexes[paranetId][curator]; + uint256 lastIndex = paranetCurators[paranetId].length - 1; + + if (index != lastIndex) { + address lastCurator = paranetCurators[paranetId][lastIndex]; + paranetCurators[paranetId][index] = lastCurator; + paranetCuratorIndexes[paranetId][lastCurator] = index; + } + + paranetCurators[paranetId].pop(); + delete paranetCuratorIndexes[paranetId][curator]; + delete curators[paranetId][curator]; + + emit CuratorRemoved(paranetId, curator); + } + + function getAllParanetCurators(bytes32 paranetId) public view returns (address[] memory) { + return paranetCurators[paranetId]; + } + + function isCurator(bytes32 paranetId, address account) external view returns (bool) { + return curators[paranetId][account]; + } + + function stageKnowledgeCollection( + bytes32 paranetId, + bytes32 knowledgeCollectionId, + address submitter + ) external onlyContracts { + stagedCollections[paranetId][knowledgeCollectionId] = ParanetLib.RequestStatus.PENDING; + collectionSubmitters[paranetId][knowledgeCollectionId] = submitter; + emit KnowledgeCollectionStaged(paranetId, knowledgeCollectionId, submitter); + _addToPendingCollections(paranetId, knowledgeCollectionId); + } + + function reviewKnowledgeCollection( + bytes32 paranetId, + bytes32 knowledgeCollectionId, + bool accepted + ) external onlyContracts { + stagedCollections[paranetId][knowledgeCollectionId] = accepted + ? ParanetLib.RequestStatus.APPROVED + : ParanetLib.RequestStatus.REJECTED; + + emit KnowledgeCollectionReviewed(paranetId, knowledgeCollectionId, accepted); + _removeFromPendingCollections(paranetId, knowledgeCollectionId); + } + + function isKnowledgeCollectionStaged( + bytes32 paranetId, + bytes32 knowledgeCollectionId + ) external view returns (bool) { + return stagedCollections[paranetId][knowledgeCollectionId] == ParanetLib.RequestStatus.PENDING; + } + + function isKnowledgeCollectionApproved( + bytes32 paranetId, + bytes32 knowledgeCollectionId + ) external view returns (bool) { + return stagedCollections[paranetId][knowledgeCollectionId] == ParanetLib.RequestStatus.APPROVED; + } + + function getKnowledgeCollectionStatus( + bytes32 paranetId, + bytes32 knowledgeCollectionId + ) external view returns (ParanetLib.RequestStatus) { + return stagedCollections[paranetId][knowledgeCollectionId]; + } + + function getKnowledgeCollectionSubmitter( + bytes32 paranetId, + bytes32 knowledgeCollectionId + ) external view returns (address) { + return collectionSubmitters[paranetId][knowledgeCollectionId]; + } + + function _addToPendingCollections(bytes32 paranetId, bytes32 knowledgeCollectionId) internal { + pendingCollectionIndexes[paranetId][knowledgeCollectionId] = pendingknowledgeCollectionIds[paranetId].length; + pendingknowledgeCollectionIds[paranetId].push(knowledgeCollectionId); + } + + function _removeFromPendingCollections(bytes32 paranetId, bytes32 knowledgeCollectionId) internal { + uint256 index = pendingCollectionIndexes[paranetId][knowledgeCollectionId]; + uint256 lastIndex = pendingknowledgeCollectionIds[paranetId].length - 1; + + if (index != lastIndex) { + bytes32 lastknowledgeCollectionId = pendingknowledgeCollectionIds[paranetId][lastIndex]; + pendingknowledgeCollectionIds[paranetId][index] = lastknowledgeCollectionId; + pendingCollectionIndexes[paranetId][lastknowledgeCollectionId] = index; + } + + pendingknowledgeCollectionIds[paranetId].pop(); + delete pendingCollectionIndexes[paranetId][knowledgeCollectionId]; + } + + function getPendingCollections( + bytes32 paranetId, + uint256 offset, + uint256 limit + ) external view returns (StagedCollection[] memory collections, uint256 total) { + total = pendingknowledgeCollectionIds[paranetId].length; + + if (offset >= total || limit == 0) { + return (new StagedCollection[](0), total); + } + + uint256 end = offset + limit; + if (end > total) { + end = total; + } + uint256 resultLength = end - offset; + + collections = new StagedCollection[](resultLength); + for (uint256 i = 0; i < resultLength; i++) { + bytes32 knowledgeCollectionId = pendingknowledgeCollectionIds[paranetId][offset + i]; + collections[i] = StagedCollection({ + knowledgeCollectionId: knowledgeCollectionId, + submitter: collectionSubmitters[paranetId][knowledgeCollectionId], + status: stagedCollections[paranetId][knowledgeCollectionId] + }); + } + } +} diff --git a/contracts/storage/paranets/ParanetsRegistry.sol b/contracts/storage/paranets/ParanetsRegistry.sol index 558b5ae9..beb1b6dd 100644 --- a/contracts/storage/paranets/ParanetsRegistry.sol +++ b/contracts/storage/paranets/ParanetsRegistry.sol @@ -6,14 +6,16 @@ import {HubDependent} from "../../abstract/HubDependent.sol"; import {INamed} from "../../interfaces/INamed.sol"; import {IVersioned} from "../../interfaces/IVersioned.sol"; import {ParanetLib} from "../../libraries/ParanetLib.sol"; -import {UnorderedNamedContractDynamicSet} from "../../libraries/UnorderedNamedContractDynamicSet.sol"; contract ParanetsRegistry is INamed, IVersioned, HubDependent { - using UnorderedNamedContractDynamicSet for UnorderedNamedContractDynamicSet.Set; - string private constant _NAME = "ParanetsRegistry"; - string private constant _VERSION = "1.0.0"; + string private constant _VERSION = "1.0.1"; + + uint256 private constant _MAX_INCENTIVES_POOLS = 50; + bytes32[] private paranetIds; + + mapping(bytes32 => uint256) internal paranetIdsMapping; // Paranet ID => Paranet Object mapping(bytes32 => ParanetLib.Paranet) internal paranets; @@ -29,58 +31,84 @@ contract ParanetsRegistry is INamed, IVersioned, HubDependent { } function registerParanet( - address knowledgeAssetStorageContract, - uint256 tokenId, + address knowledgeCollectionStorageContract, + uint256 knowledgeCollectionTokenId, + uint256 knowledgeAssetTokenId, string calldata paranetName, string calldata paranetDescription, - ParanetLib.NodesAccessPolicy nodesAccessPolicy, - ParanetLib.MinersAccessPolicy minersAccessPolicy, - ParanetLib.KnowledgeAssetsAccessPolicy knowledgeAssetsAccessPolicy + uint8 nodesAccessPolicy, + uint8 minersAccessPolicy, + uint8 knowledgeCollectionsSubmissionPolicy ) external onlyContracts returns (bytes32) { - ParanetLib.Paranet storage paranet = paranets[ - keccak256(abi.encodePacked(knowledgeAssetStorageContract, tokenId)) - ]; + bytes32 paranetId = keccak256( + abi.encodePacked(knowledgeCollectionStorageContract, knowledgeCollectionTokenId, knowledgeAssetTokenId) + ); - paranet.paranetKAStorageContract = knowledgeAssetStorageContract; - paranet.paranetKATokenId = tokenId; + ParanetLib.Paranet storage paranet = paranets[paranetId]; + + paranet.paranetKCStorageContract = knowledgeCollectionStorageContract; + paranet.paranetKCTokenId = knowledgeCollectionTokenId; + paranet.paranetKATokenId = knowledgeAssetTokenId; paranet.name = paranetName; paranet.description = paranetDescription; paranet.nodesAccessPolicy = nodesAccessPolicy; paranet.minersAccessPolicy = minersAccessPolicy; - paranet.knowledgeAssetsAccessPolicy = knowledgeAssetsAccessPolicy; + paranet.knowledgeCollectionsSubmissionPolicy = knowledgeCollectionsSubmissionPolicy; - return keccak256(abi.encodePacked(knowledgeAssetStorageContract, tokenId)); + paranetIds.push(paranetId); + paranetIdsMapping[paranetId] = paranetIds.length - 1; + + return paranetId; } function deleteParanet(bytes32 paranetId) external onlyContracts { delete paranets[paranetId]; + uint256 indexToRemove = paranetIdsMapping[paranetId]; + uint256 lastIndex = paranetIds.length - 1; + if (indexToRemove != lastIndex) { + bytes32 lastParanetId = paranetIds[lastIndex]; + + paranetIds[indexToRemove] = lastParanetId; + paranetIdsMapping[lastParanetId] = indexToRemove; + } + + paranetIds.pop(); + delete paranetIdsMapping[paranetId]; } function paranetExists(bytes32 paranetId) external view returns (bool) { return keccak256( - abi.encodePacked(paranets[paranetId].paranetKAStorageContract, paranets[paranetId].paranetKATokenId) + abi.encodePacked( + paranets[paranetId].paranetKCStorageContract, + paranets[paranetId].paranetKCTokenId, + paranets[paranetId].paranetKATokenId + ) ) == paranetId; } function getParanetMetadata(bytes32 paranetId) external view returns (ParanetLib.ParanetMetadata memory) { ParanetLib.Paranet storage paranet = paranets[paranetId]; - return ParanetLib.ParanetMetadata({ - paranetKAStorageContract: paranet.paranetKAStorageContract, + paranetKCStorageContract: paranet.paranetKCStorageContract, + paranetKCTokenId: paranet.paranetKCTokenId, paranetKATokenId: paranet.paranetKATokenId, name: paranet.name, description: paranet.description, nodesAccessPolicy: paranet.nodesAccessPolicy, minersAccessPolicy: paranet.minersAccessPolicy, - knowledgeAssetsAccessPolicy: paranet.knowledgeAssetsAccessPolicy, + knowledgeCollectionsSubmissionPolicy: paranet.knowledgeCollectionsSubmissionPolicy, cumulativeKnowledgeValue: paranet.cumulativeKnowledgeValue }); } - function getParanetKnowledgeAssetLocator(bytes32 paranetId) external view returns (address, uint256) { - return (paranets[paranetId].paranetKAStorageContract, paranets[paranetId].paranetKATokenId); + function getParanetKnowledgeAssetLocator(bytes32 paranetId) external view returns (address, uint256, uint256) { + return ( + paranets[paranetId].paranetKCStorageContract, + paranets[paranetId].paranetKCTokenId, + paranets[paranetId].paranetKATokenId + ); } function getName(bytes32 paranetId) external view returns (string memory) { @@ -99,39 +127,31 @@ contract ParanetsRegistry is INamed, IVersioned, HubDependent { paranets[paranetId].description = description; } - function getNodesAccessPolicy(bytes32 paranetId) external view returns (ParanetLib.NodesAccessPolicy) { + function getNodesAccessPolicy(bytes32 paranetId) external view returns (uint8) { return paranets[paranetId].nodesAccessPolicy; } - function setNodesAccessPolicy( - bytes32 paranetId, - ParanetLib.NodesAccessPolicy nodesAccessPolicy - ) external onlyContracts { + function setNodesAccessPolicy(bytes32 paranetId, uint8 nodesAccessPolicy) external onlyContracts { paranets[paranetId].nodesAccessPolicy = nodesAccessPolicy; } - function getMinersAccessPolicy(bytes32 paranetId) external view returns (ParanetLib.MinersAccessPolicy) { + function getMinersAccessPolicy(bytes32 paranetId) external view returns (uint8) { return paranets[paranetId].minersAccessPolicy; } - function setMinersAccessPolicy( - bytes32 paranetId, - ParanetLib.MinersAccessPolicy minersAccessPolicy - ) external onlyContracts { + function setMinersAccessPolicy(bytes32 paranetId, uint8 minersAccessPolicy) external onlyContracts { paranets[paranetId].minersAccessPolicy = minersAccessPolicy; } - function getKnowledgeAssetsAccessPolicy( - bytes32 paranetId - ) external view returns (ParanetLib.KnowledgeAssetsAccessPolicy) { - return paranets[paranetId].knowledgeAssetsAccessPolicy; + function getKnowledgeCollectionsSubmissionPolicy(bytes32 paranetId) external view returns (uint8) { + return paranets[paranetId].knowledgeCollectionsSubmissionPolicy; } - function setKnowledgeAssetsAccessPolicy( + function setKnowledgeCollectionsSubmissionPolicy( bytes32 paranetId, - ParanetLib.KnowledgeAssetsAccessPolicy knowledgeAssetsAccessPolicy + uint8 knowledgeCollectionsSubmissionPolicy ) external onlyContracts { - paranets[paranetId].knowledgeAssetsAccessPolicy = knowledgeAssetsAccessPolicy; + paranets[paranetId].knowledgeCollectionsSubmissionPolicy = knowledgeCollectionsSubmissionPolicy; } function addNodeJoinRequest( @@ -192,82 +212,114 @@ contract ParanetsRegistry is INamed, IVersioned, HubDependent { return paranets[paranetId].paranetNodeJoinRequests[identityId].length; } - function addCuratedNode(bytes32 paranetId, uint72 identityId, bytes calldata nodeId) external onlyContracts { - paranets[paranetId].curatedNodesIndexes[identityId] = paranets[paranetId].curatedNodes.length; - paranets[paranetId].curatedNodes.push(ParanetLib.Node({identityId: identityId, nodeId: nodeId})); + function addPermissionedNode(bytes32 paranetId, uint72 identityId, bytes calldata nodeId) external onlyContracts { + paranets[paranetId].permissionedNodesIndexes[identityId] = paranets[paranetId].permissionedNodes.length; + paranets[paranetId].permissionedNodes.push(ParanetLib.Node({identityId: identityId, nodeId: nodeId})); } - function removeCuratedNode(bytes32 paranetId, uint72 identityId) external onlyContracts { - paranets[paranetId].curatedNodes[paranets[paranetId].curatedNodesIndexes[identityId]] = paranets[paranetId] - .curatedNodes[paranets[paranetId].curatedNodes.length - 1]; - paranets[paranetId].curatedNodesIndexes[ - paranets[paranetId].curatedNodes[paranets[paranetId].curatedNodes.length - 1].identityId - ] = paranets[paranetId].curatedNodesIndexes[identityId]; + function removePermissionedNode(bytes32 paranetId, uint72 identityId) external onlyContracts { + paranets[paranetId].permissionedNodes[paranets[paranetId].permissionedNodesIndexes[identityId]] = paranets[ + paranetId + ].permissionedNodes[paranets[paranetId].permissionedNodes.length - 1]; + paranets[paranetId].permissionedNodesIndexes[ + paranets[paranetId].permissionedNodes[paranets[paranetId].permissionedNodes.length - 1].identityId + ] = paranets[paranetId].permissionedNodesIndexes[identityId]; - delete paranets[paranetId].curatedNodesIndexes[identityId]; - paranets[paranetId].curatedNodes.pop(); + delete paranets[paranetId].permissionedNodesIndexes[identityId]; + paranets[paranetId].permissionedNodes.pop(); } - function getCuratedNodes(bytes32 paranetId) external view returns (ParanetLib.Node[] memory) { - return paranets[paranetId].curatedNodes; + function getPermissionedNodes(bytes32 paranetId) external view returns (ParanetLib.Node[] memory) { + return paranets[paranetId].permissionedNodes; } - function getCuratedNodesCount(bytes32 paranetId) external view returns (uint256) { - return paranets[paranetId].curatedNodes.length; + function getPermissionedNodesCount(bytes32 paranetId) external view returns (uint256) { + return paranets[paranetId].permissionedNodes.length; } - function isCuratedNode(bytes32 paranetId, uint72 identityId) external view returns (bool) { - return (paranets[paranetId].curatedNodes.length != 0 && - paranets[paranetId].curatedNodes[paranets[paranetId].curatedNodesIndexes[identityId]].identityId == + function isPermissionedNode(bytes32 paranetId, uint72 identityId) external view returns (bool) { + return (paranets[paranetId].permissionedNodes.length != 0 && + paranets[paranetId] + .permissionedNodes[paranets[paranetId].permissionedNodesIndexes[identityId]] + .identityId == identityId); } - function getIncentivesPoolAddress( + function addIncentivesPool( bytes32 paranetId, - string calldata incentivesPoolType - ) external view returns (address) { - return paranets[paranetId].incentivesPools.get(incentivesPoolType).addr; + string calldata incentivesPoolName, + address storageAddress, + address rewardTokenAddress + ) external onlyContracts { + require(paranets[paranetId].incentivesPools.length < _MAX_INCENTIVES_POOLS, "Max incentives pools reached"); + ParanetLib.IncentivesPool memory incentivesPool = ParanetLib.IncentivesPool({ + name: incentivesPoolName, + storageAddr: storageAddress, + rewardTokenAddress: rewardTokenAddress + }); + paranets[paranetId].incentivesPoolsByNameIndexes[incentivesPoolName] = paranets[paranetId] + .incentivesPools + .length; + paranets[paranetId].incentivesPoolsByStorageAddressIndexes[storageAddress] = paranets[paranetId] + .incentivesPools + .length; + paranets[paranetId].incentivesPools.push(incentivesPool); } - function setIncentivesPoolAddress( + function getIncentivesPoolByPoolName( bytes32 paranetId, - string calldata incentivesPoolType, - address incentivesPoolAddress - ) external onlyContracts { - paranets[paranetId].incentivesPools.append(incentivesPoolType, incentivesPoolAddress); + string calldata poolName + ) external view returns (ParanetLib.IncentivesPool memory) { + return paranets[paranetId].incentivesPools[paranets[paranetId].incentivesPoolsByNameIndexes[poolName]]; } - function updateIncentivesPoolAddress( + function getIncentivesPoolByStorageAddress( bytes32 paranetId, - string calldata incentivesPoolType, - address incentivesPoolAddress - ) external onlyContracts { - paranets[paranetId].incentivesPools.update(incentivesPoolType, incentivesPoolAddress); + address storageAddr + ) external view returns (ParanetLib.IncentivesPool memory) { + return + paranets[paranetId].incentivesPools[ + paranets[paranetId].incentivesPoolsByStorageAddressIndexes[storageAddr] + ]; } - function removeIncentivesPool(bytes32 paranetId, string calldata incentivesPoolType) external onlyContracts { - paranets[paranetId].incentivesPools.remove(incentivesPoolType); + function getAllIncentivesPools(bytes32 paranetId) external view returns (ParanetLib.IncentivesPool[] memory) { + return paranets[paranetId].incentivesPools; } - function removeIncentivesPool(bytes32 paranetId, address incentivesPoolAddress) external onlyContracts { - paranets[paranetId].incentivesPools.remove(incentivesPoolAddress); - } + function hasIncentivesPoolByName(bytes32 paranetId, string calldata poolName) external view returns (bool) { + ParanetLib.Paranet storage paranet = paranets[paranetId]; - function getAllIncentivesPools( - bytes32 paranetId - ) external view returns (UnorderedNamedContractDynamicSet.Contract[] memory) { - return paranets[paranetId].incentivesPools.getAll(); - } + if (paranet.incentivesPools.length == 0) { + return false; + } - function hasIncentivesPoolByType( - bytes32 paranetId, - string calldata incentivesPoolType - ) external view returns (bool) { - return paranets[paranetId].incentivesPools.exists(incentivesPoolType); + uint256 index = paranet.incentivesPoolsByNameIndexes[poolName]; + + if (index >= paranet.incentivesPools.length) { + return false; + } + + return + keccak256(abi.encodePacked(paranet.incentivesPools[index].name)) == keccak256(abi.encodePacked(poolName)); } - function hasIncentivesPoolByAddress(bytes32 paranetId, address incentivesPoolAddress) external view returns (bool) { - return paranets[paranetId].incentivesPools.exists(incentivesPoolAddress); + function hasIncentivesPoolByStorageAddress(bytes32 paranetId, address storageAddr) external view returns (bool) { + ParanetLib.Paranet storage paranet = paranets[paranetId]; + + if (paranet.incentivesPools.length == 0) { + return false; + } + + uint256 index = paranet.incentivesPoolsByStorageAddressIndexes[storageAddr]; + + if (index >= paranet.incentivesPools.length) { + return false; + } + + address incentivesPoolStorageAddress = paranet.incentivesPools[index].storageAddr; + + return incentivesPoolStorageAddress != address(0) && incentivesPoolStorageAddress == storageAddr; } function getCumulativeKnowledgeValue(bytes32 paranetId) external view returns (uint96) { @@ -408,95 +460,146 @@ contract ParanetsRegistry is INamed, IVersioned, HubDependent { return paranets[paranetId].paranetKnowledgeMinerAccessRequests[miner].length; } - function addKnowledgeAsset(bytes32 paranetId, bytes32 knowledgeAssetId) external onlyContracts { - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] = paranets[paranetId] - .knowledgeAssets + function addKnowledgeCollecton(bytes32 paranetId, bytes32 knowledgeCollectionId) external onlyContracts { + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] = paranets[paranetId] + .knowledgeCollections .length; - paranets[paranetId].knowledgeAssets.push(knowledgeAssetId); + paranets[paranetId].knowledgeCollections.push(knowledgeCollectionId); } - function removeKnowledgeAsset(bytes32 paranetId, bytes32 knowledgeAssetId) external onlyContracts { - paranets[paranetId].knowledgeAssets[ - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] - ] = paranets[paranetId].knowledgeAssets[paranets[paranetId].knowledgeAssets.length - 1]; - paranets[paranetId].registeredKnowledgeAssetsIndexes[ - paranets[paranetId].knowledgeAssets[paranets[paranetId].knowledgeAssets.length - 1] - ] = paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId]; + function removeKnowledgeCollection(bytes32 paranetId, bytes32 knowledgeCollectionId) external onlyContracts { + paranets[paranetId].knowledgeCollections[ + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] + ] = paranets[paranetId].knowledgeCollections[paranets[paranetId].knowledgeCollections.length - 1]; + paranets[paranetId].registeredKnowledgeCollectionsIndexes[ + paranets[paranetId].knowledgeCollections[paranets[paranetId].knowledgeCollections.length - 1] + ] = paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId]; - delete paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId]; - paranets[paranetId].knowledgeAssets.pop(); + delete paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId]; + paranets[paranetId].knowledgeCollections.pop(); } - function getKnowledgeAssets(bytes32 paranetId) external view returns (bytes32[] memory) { - return paranets[paranetId].knowledgeAssets; + function getKnowledgeCollections(bytes32 paranetId) external view returns (bytes32[] memory) { + return paranets[paranetId].knowledgeCollections; } - function getKnowledgeAssetsWithPagination( + function getKnowledgeCollectionsWithPagination( bytes32 paranetId, uint256 offset, uint256 limit ) external view returns (bytes32[] memory) { - if (offset >= paranets[paranetId].knowledgeAssets.length) { + if (offset >= paranets[paranetId].knowledgeCollections.length) { return new bytes32[](0); } - uint256 fetchCount = (offset + limit > paranets[paranetId].knowledgeAssets.length) - ? paranets[paranetId].knowledgeAssets.length - offset + uint256 fetchCount = (offset + limit > paranets[paranetId].knowledgeCollections.length) + ? paranets[paranetId].knowledgeCollections.length - offset : limit; - bytes32[] memory knowledgeAssets = new bytes32[](fetchCount); + bytes32[] memory knowledgeCollections = new bytes32[](fetchCount); for (uint256 i = 0; i < fetchCount; i++) { - knowledgeAssets[i] = paranets[paranetId].knowledgeAssets[offset + i]; + knowledgeCollections[i] = paranets[paranetId].knowledgeCollections[offset + i]; } - return knowledgeAssets; + return knowledgeCollections; } - function getKnowledgeAssetsStartingFromKnowledgeAssetId( + function getKnowledgeCollectionsStartingFromKnowlCollectionId( bytes32 paranetId, - bytes32 knowledgeAssetId, + bytes32 knowledgeCollectionId, uint256 limit ) external view returns (bytes32[] memory) { if ( - paranets[paranetId].knowledgeAssets[ - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] - ] != knowledgeAssetId + paranets[paranetId].knowledgeCollections[ + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] + ] != knowledgeCollectionId ) { - revert("Invalid starting KA"); + revert("Invalid starting KC"); } if ( - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] >= - paranets[paranetId].knowledgeAssets.length + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] >= + paranets[paranetId].knowledgeCollections.length ) { return new bytes32[](0); } - uint256 fetchCount = (paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] + limit > - paranets[paranetId].knowledgeAssets.length) - ? paranets[paranetId].knowledgeAssets.length - - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] + uint256 fetchCount = (paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] + limit > + paranets[paranetId].knowledgeCollections.length) + ? paranets[paranetId].knowledgeCollections.length - + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] : limit; - bytes32[] memory knowledgeAssets = new bytes32[](fetchCount); + bytes32[] memory knowledgeCollections = new bytes32[](fetchCount); for (uint256 i = 0; i < fetchCount; i++) { - knowledgeAssets[i] = paranets[paranetId].knowledgeAssets[ - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] + i + knowledgeCollections[i] = paranets[paranetId].knowledgeCollections[ + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] + i ]; } - return knowledgeAssets; + return knowledgeCollections; } - function getKnowledgeAssetsCount(bytes32 paranetId) external view returns (uint256) { - return paranets[paranetId].knowledgeAssets.length; + function getKnowledgeCollectionsCount(bytes32 paranetId) external view returns (uint256) { + return paranets[paranetId].knowledgeCollections.length; } - function isKnowledgeAssetRegistered(bytes32 paranetId, bytes32 knowledgeAssetId) external view returns (bool) { - return (paranets[paranetId].knowledgeAssets.length != 0 && - paranets[paranetId].knowledgeAssets[ - paranets[paranetId].registeredKnowledgeAssetsIndexes[knowledgeAssetId] + function isKnowledgeCollectionRegistered( + bytes32 paranetId, + bytes32 knowledgeCollectionId + ) external view returns (bool) { + return (paranets[paranetId].knowledgeCollections.length != 0 && + paranets[paranetId].knowledgeCollections[ + paranets[paranetId].registeredKnowledgeCollectionsIndexes[knowledgeCollectionId] ] == - knowledgeAssetId); + knowledgeCollectionId); + } + + function getParanetsCount() external view returns (uint256) { + return paranetIds.length; + } + + function getParanetIdAtIndex(uint256 index) external view returns (bytes32) { + require(index < paranetIds.length, "Index out of range"); + return paranetIds[index]; + } + + function getAllParanetIds() external view returns (bytes32[] memory) { + return paranetIds; + } + + function getParanetIds(uint256 offset, uint256 limit) external view returns (bytes32[] memory) { + // If offset is past the end of the array, return an empty array + if (offset >= paranetIds.length) { + return new bytes32[](0); + } + + uint256 end = offset + limit; + if (end > paranetIds.length) { + end = paranetIds.length; + } + + bytes32[] memory ids = new bytes32[](end - offset); + + for (uint256 i = offset; i < end; i++) { + ids[i - offset] = paranetIds[i]; + } + + return ids; + } + + function getParanetIdsMapping(bytes32 paranetId) external view returns (uint256) { + require( + keccak256( + abi.encodePacked( + paranets[paranetId].paranetKCStorageContract, + paranets[paranetId].paranetKCTokenId, + paranets[paranetId].paranetKATokenId + ) + ) == paranetId, + "Paranet not found" + ); + + return paranetIdsMapping[paranetId]; } } diff --git a/contracts/tokens/ERC1155Delta.sol b/contracts/tokens/ERC1155Delta.sol index efaa2184..ab46c156 100644 --- a/contracts/tokens/ERC1155Delta.sol +++ b/contracts/tokens/ERC1155Delta.sol @@ -325,7 +325,7 @@ contract ERC1155Delta is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC115 uint256 startTokenId = _nextTokenId(); unchecked { - require(type(uint256).max - amount >= startTokenId); + require(type(uint256).max - amount >= startTokenId, "Minting more tokens than the maximum allowed"); for (uint256 i = 0; i < amount; i++) { ids[i] = startTokenId + i; amounts[i] = 1; diff --git a/deploy/012_deploy_paranets_registry.ts b/deploy/012_deploy_paranets_registry.ts index 21b2721d..fad5d8cd 100644 --- a/deploy/012_deploy_paranets_registry.ts +++ b/deploy/012_deploy_paranets_registry.ts @@ -2,9 +2,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - await hre.helpers.deploy({ newContractName: 'ParanetsRegistry', }); diff --git a/deploy/013_deploy_paranet_services_registry.ts b/deploy/013_deploy_paranet_services_registry.ts index 516a0d9d..f2ae3e20 100644 --- a/deploy/013_deploy_paranet_services_registry.ts +++ b/deploy/013_deploy_paranet_services_registry.ts @@ -2,9 +2,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - await hre.helpers.deploy({ newContractName: 'ParanetServicesRegistry', }); diff --git a/deploy/014_deploy_paranet_knowledge_assets_registry.ts b/deploy/014_deploy_paranet_knowledge_collections_registry.ts similarity index 63% rename from deploy/014_deploy_paranet_knowledge_assets_registry.ts rename to deploy/014_deploy_paranet_knowledge_collections_registry.ts index 131d080a..b3cc14d8 100644 --- a/deploy/014_deploy_paranet_knowledge_assets_registry.ts +++ b/deploy/014_deploy_paranet_knowledge_collections_registry.ts @@ -2,14 +2,11 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - await hre.helpers.deploy({ - newContractName: 'ParanetKnowledgeAssetsRegistry', + newContractName: 'ParanetKnowledgeCollectionsRegistry', }); }; export default func; -func.tags = ['ParanetKnowledgeAssetsRegistry']; +func.tags = ['ParanetKnowledgeCollectionsRegistry']; func.dependencies = ['Hub']; diff --git a/deploy/015_deploy_paranet_knowledge_miners_registry.ts b/deploy/015_deploy_paranet_knowledge_miners_registry.ts index 7341c9e9..3c72d735 100644 --- a/deploy/015_deploy_paranet_knowledge_miners_registry.ts +++ b/deploy/015_deploy_paranet_knowledge_miners_registry.ts @@ -2,9 +2,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - await hre.helpers.deploy({ newContractName: 'ParanetKnowledgeMinersRegistry', }); diff --git a/deploy/024_deploy_paranet_staging_registry.ts b/deploy/024_deploy_paranet_staging_registry.ts new file mode 100644 index 00000000..238b5de8 --- /dev/null +++ b/deploy/024_deploy_paranet_staging_registry.ts @@ -0,0 +1,12 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'ParanetStagingRegistry', + }); +}; + +export default func; +func.tags = ['ParanetStagingRegistry']; +func.dependencies = ['Hub']; diff --git a/deploy/024_deploy_paranet.ts b/deploy/025_deploy_paranet.ts similarity index 83% rename from deploy/024_deploy_paranet.ts rename to deploy/025_deploy_paranet.ts index b82a5272..ffeaa802 100644 --- a/deploy/024_deploy_paranet.ts +++ b/deploy/025_deploy_paranet.ts @@ -2,9 +2,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - await hre.helpers.deploy({ newContractName: 'Paranet', }); @@ -14,10 +11,11 @@ export default func; func.tags = ['Paranet']; func.dependencies = [ 'Hub', - 'ParanetKnowledgeAssetsRegistry', + 'ParanetKnowledgeCollectionsRegistry', 'ParanetKnowledgeMinersRegistry', 'ParanetsRegistry', 'ParanetServicesRegistry', + 'ParanetStagingRegistry', 'ProfileStorage', 'IdentityStorage', ]; diff --git a/deploy/026_deploy_paranet_Incentives_pool_factory_helper.ts b/deploy/026_deploy_paranet_Incentives_pool_factory_helper.ts new file mode 100644 index 00000000..6bdba8ba --- /dev/null +++ b/deploy/026_deploy_paranet_Incentives_pool_factory_helper.ts @@ -0,0 +1,12 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + await hre.helpers.deploy({ + newContractName: 'ParanetIncentivesPoolFactoryHelper', + }); +}; + +export default func; +func.tags = ['ParanetIncentivesPoolFactoryHelper']; +func.dependencies = ['Hub']; diff --git a/deploy/025_deploy_paranet_incentives_pool_factory.ts b/deploy/027_deploy_paranet_incentives_pool_factory.ts similarity index 60% rename from deploy/025_deploy_paranet_incentives_pool_factory.ts rename to deploy/027_deploy_paranet_incentives_pool_factory.ts index 907175f8..13284857 100644 --- a/deploy/025_deploy_paranet_incentives_pool_factory.ts +++ b/deploy/027_deploy_paranet_incentives_pool_factory.ts @@ -2,16 +2,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - // TODO: Remove when paranets V2 are finished - return; - - if ( - !hre.network.name.startsWith('neuroweb') && - !hre.network.name.startsWith('hardhat') - ) { - return; - } - await hre.helpers.deploy({ newContractName: 'ParanetIncentivesPoolFactory', }); @@ -19,4 +9,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['ParanetIncentivesPoolFactory']; -func.dependencies = ['Hub', 'ParanetsRegistry']; +func.dependencies = [ + 'Hub', + 'ParanetsRegistry', + 'ParanetIncentivesPoolFactoryHelper', +]; diff --git a/deploy/026_deploy_migrator.ts b/deploy/028_deploy_migrator.ts similarity index 100% rename from deploy/026_deploy_migrator.ts rename to deploy/028_deploy_migrator.ts diff --git a/deployments/base_sepolia_test_contracts.json b/deployments/base_sepolia_test_contracts.json index d1ed8c08..b52e33a8 100644 --- a/deployments/base_sepolia_test_contracts.json +++ b/deployments/base_sepolia_test_contracts.json @@ -189,6 +189,78 @@ "deploymentBlock": 20972223, "deploymentTimestamp": 1737712739031, "deployed": true + }, + "ParanetsRegistry": { + "evmAddress": "0xB9668A4321b8a6900289E6aE98D4388570C5935C", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191895, + "deploymentTimestamp": 1740152083167, + "deployed": true + }, + "ParanetServicesRegistry": { + "evmAddress": "0x9aE0D8929546931913e591a8Df44F9E99b38B5c9", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191899, + "deploymentTimestamp": 1740152090458, + "deployed": true + }, + "ParanetKnowledgeCollectionsRegistry": { + "evmAddress": "0x558c8C7998fFF417485DD9105834569d3FC84c02", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191902, + "deploymentTimestamp": 1740152097326, + "deployed": true + }, + "ParanetKnowledgeMinersRegistry": { + "evmAddress": "0x8E806Cc918438B8eF1fFC02FACCa8B0Da1D9D23A", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191906, + "deploymentTimestamp": 1740152103788, + "deployed": true + }, + "ParanetStagingRegistry": { + "evmAddress": "0xc7A45b5DA7CdC36462Fe6E86A73e741C2fd56FDE", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191909, + "deploymentTimestamp": 1740152110301, + "deployed": true + }, + "Paranet": { + "evmAddress": "0x6C9146151ffe049c3AC93A06382495Ef6DDA8833", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191912, + "deploymentTimestamp": 1740152117023, + "deployed": true + }, + "ParanetIncentivesPoolFactoryHelper": { + "evmAddress": "0x707d6853BdCA6278592F52314a47E62376B5973B", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191916, + "deploymentTimestamp": 1740152123528, + "deployed": true + }, + "ParanetIncentivesPoolFactory": { + "evmAddress": "0x6C62F6DFE2Fe65009527956d24E25dDC0ea1F32b", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 22191919, + "deploymentTimestamp": 1740152129897, + "deployed": true } } } diff --git a/deployments/gnosis_chiado_test_contracts.json b/deployments/gnosis_chiado_test_contracts.json index fd7b30b4..2d3de17f 100644 --- a/deployments/gnosis_chiado_test_contracts.json +++ b/deployments/gnosis_chiado_test_contracts.json @@ -173,12 +173,12 @@ "deployed": true }, "KnowledgeCollection": { - "evmAddress": "0xdd2B900b121CD258Fbfd05C1C0482C0E25582f2C", + "evmAddress": "0x27F52c0d7289A982D76F74De1adeFBFCB1EB8ed8", "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "ddb3f9e1ef7c2f9f7ca8554b19fae7d521a5507f", - "deploymentBlock": 13494299, - "deploymentTimestamp": 1735137979260, + "gitBranch": "v8-paranet-update", + "gitCommitHash": "b3ce1bc042e3c5f28f93758e750f9f0e1cbcdee5", + "deploymentBlock": 13947424, + "deploymentTimestamp": 1737552337932, "deployed": true }, "Migrator": { @@ -189,6 +189,78 @@ "deploymentBlock": 13494301, "deploymentTimestamp": 1735137986444, "deployed": true + }, + "ParanetsRegistry": { + "evmAddress": "0x086830B0b77e7da1CB203cDd53211eDE4f63cbB7", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435277, + "deploymentTimestamp": 1740152326034, + "deployed": true + }, + "ParanetServicesRegistry": { + "evmAddress": "0xA9786795A83ca13341418F200A63A55c6a8f869A", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435278, + "deploymentTimestamp": 1740152330970, + "deployed": true + }, + "ParanetKnowledgeCollectionsRegistry": { + "evmAddress": "0x3dE88f6fBe7C6A393d2341f4a668D2d67d9b8a82", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435279, + "deploymentTimestamp": 1740152336117, + "deployed": true + }, + "ParanetKnowledgeMinersRegistry": { + "evmAddress": "0x094cE4666839224Af7caFb4Ec1F313b8cb3E0caB", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435280, + "deploymentTimestamp": 1740152341495, + "deployed": true + }, + "ParanetStagingRegistry": { + "evmAddress": "0x429b460986aFeCB70c5c3a7148ae4df66064F842", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435282, + "deploymentTimestamp": 1740152350739, + "deployed": true + }, + "Paranet": { + "evmAddress": "0xe63903D1263e41F44754a0d006bC11997F9C0086", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435283, + "deploymentTimestamp": 1740152355817, + "deployed": true + }, + "ParanetIncentivesPoolFactoryHelper": { + "evmAddress": "0x4e51168Fc4132Dda57A5cA274ce6D56027aF7Cc3", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435284, + "deploymentTimestamp": 1740152361177, + "deployed": true + }, + "ParanetIncentivesPoolFactory": { + "evmAddress": "0x438ff472D5D8332386640E0a5B172219ffE0159f", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 14435285, + "deploymentTimestamp": 1740152366258, + "deployed": true } } } diff --git a/deployments/neuroweb_testnet_contracts.json b/deployments/neuroweb_testnet_contracts.json index 74566147..4cc9c864 100644 --- a/deployments/neuroweb_testnet_contracts.json +++ b/deployments/neuroweb_testnet_contracts.json @@ -192,13 +192,13 @@ "deployed": true }, "KnowledgeCollection": { - "evmAddress": "0x731227B9B8eB9B1ad209585cec3e0568278a0C89", - "substrateAddress": "5EMjsczjESbn133kUVssGUuA55dCkRtKK1LTYeoULYqASafv", + "evmAddress": "0xD842553c0a6C1C1427db6B56Ab3299F575a726F2", + "substrateAddress": "5EMjsd15WQzb9JzKqn2kvi2PXHnfL8JFjn9Gi1bH29FGE2WU", "version": "1.0.0", - "gitBranch": "v8-contracts", - "gitCommitHash": "ddb3f9e1ef7c2f9f7ca8554b19fae7d521a5507f", - "deploymentBlock": 5706407, - "deploymentTimestamp": 1735137995151, + "gitBranch": "v8-paranet-update", + "gitCommitHash": "b3ce1bc042e3c5f28f93758e750f9f0e1cbcdee5", + "deploymentBlock": 6086206, + "deploymentTimestamp": 1737551947852, "deployed": true }, "Migrator": { @@ -210,6 +210,86 @@ "deploymentBlock": 5706409, "deploymentTimestamp": 1735138003714, "deployed": true + }, + "ParanetsRegistry": { + "evmAddress": "0x657B2306de3525341b64902E2aDfA40dCc89B836", + "substrateAddress": "5EMjsczgWWFZsa6iWELCZs6mnduCti8Wo7BZYKYspHot6pUs", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478927, + "deploymentTimestamp": 1740152415440, + "deployed": true + }, + "ParanetServicesRegistry": { + "evmAddress": "0xbF87DF84B631D6CDB828D2cB5fDFD7F400f24E43", + "substrateAddress": "5EMjsczzZ2ivNSpVZdhco6CC9aYijjQsgU2DxwECbPkx9T43", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478929, + "deploymentTimestamp": 1740152420674, + "deployed": true + }, + "ParanetKnowledgeCollectionsRegistry": { + "evmAddress": "0x91dc076999887AA167aa8b4b1e04329EC431bF82", + "substrateAddress": "5EMjsczqQFnUyR7uBwrKBpTCtN1LbTYjqjitJx6bSuEbAYHg", + "version": "1.0.1", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478930, + "deploymentTimestamp": 1740152425820, + "deployed": true + }, + "ParanetKnowledgeMinersRegistry": { + "evmAddress": "0x6Da31cC3D46414065b184756a61Fe45fbBC55F79", + "substrateAddress": "5EMjsczi9HwuT47F1mPseXgiXfKgizaDRmgNDLpyfSu1tNYW", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478932, + "deploymentTimestamp": 1740152439065, + "deployed": true + }, + "ParanetStagingRegistry": { + "evmAddress": "0x16c3F5395Dd6b1476b99B94F18781ed9BbF98014", + "substrateAddress": "5EMjsczQjhf9joUyCzDPUg29gvdnRQC43NEPx5AwFYqBRD1h", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478933, + "deploymentTimestamp": 1740152444365, + "deployed": true + }, + "Paranet": { + "evmAddress": "0x14a294001A723eED7DB8e094DE10E0B166bFACA7", + "substrateAddress": "5EMjsczQJwfBEMAwFJtZFgVBGdLytpJnMKimHXqVHqUhF73Y", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478934, + "deploymentTimestamp": 1740152449574, + "deployed": true + }, + "ParanetIncentivesPoolFactoryHelper": { + "evmAddress": "0x97A17bDE06a458aF633AE04A5a235F25a4FE420d", + "substrateAddress": "5EMjsczrZKxpm5YTBA7RUHRfVZ3REmTMdfKiKRjAL1s7YSdi", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478935, + "deploymentTimestamp": 1740152458924, + "deployed": true + }, + "ParanetIncentivesPoolFactory": { + "evmAddress": "0x4809159221E1ed07Ea0a99B6D335A683A45018Cf", + "substrateAddress": "5EMjsczacJMi1VhsFL9Z1oUCbcqz5XHV3U9zFMqyzDYuZsos", + "version": "1.0.0", + "gitBranch": "v8/unit-tests", + "gitCommitHash": "8d80da87bb433669bc4a03aacb0bb5054b3b375c", + "deploymentBlock": 6478936, + "deploymentTimestamp": 1740152464238, + "deployed": true } } } diff --git a/index.ts b/index.ts index 84179306..882ff8c5 100644 --- a/index.ts +++ b/index.ts @@ -21,7 +21,7 @@ import Migrator from './abi/Migrator.json'; import ParametersStorage from './abi/ParametersStorage.json'; import Paranet from './abi/Paranet.json'; import ParanetIncentivesPoolFactory from './abi/ParanetIncentivesPoolFactory.json'; -import ParanetKnowledgeAssetsRegistry from './abi/ParanetKnowledgeAssetsRegistry.json'; +import ParanetKnowledgeCollectionsRegistry from './abi/ParanetKnowledgeCollectionsRegistry.json'; import ParanetKnowledgeMinersRegistry from './abi/ParanetKnowledgeMinersRegistry.json'; import ParanetNeuroIncentivesPool from './abi/ParanetNeuroIncentivesPool.json'; import ParanetServicesRegistry from './abi/ParanetServicesRegistry.json'; @@ -49,7 +49,7 @@ export { Identity as IdentityABI, Paranet as ParanetABI, ParanetIncentivesPoolFactory as ParanetIncentivesPoolFactoryABI, - ParanetKnowledgeAssetsRegistry as ParanetKnowledgeAssetsRegistryABI, + ParanetKnowledgeCollectionsRegistry as ParanetKnowledgeCollectionsRegistryABI, ParanetKnowledgeMinersRegistry as ParanetKnowledgeMinersRegistryABI, ParanetServicesRegistry as ParanetServicesRegistryABI, ParanetsRegistry as ParanetsRegistryABI, diff --git a/package-lock.json b/package-lock.json index 1b336b86..06ab018c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dkg-evm-module", - "version": "8.0.0", + "version": "8.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "dkg-evm-module", - "version": "8.0.0", + "version": "8.0.1", "license": "Apache-2.0", "dependencies": { "@openzeppelin/contracts": "^5.1.0", diff --git a/package.json b/package.json index 1c880046..a74e9527 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dkg-evm-module", - "version": "8.0.0", + "version": "8.0.2", "description": "Smart contracts for OriginTrail V8", "main": "index.ts", "files": [ diff --git a/test/helpers/constants.ts b/test/helpers/constants.ts index 4b51c90c..850e1561 100644 --- a/test/helpers/constants.ts +++ b/test/helpers/constants.ts @@ -5,4 +5,16 @@ const ADMIN_KEY = 1; const OPERATIONAL_KEY = 2; const ECDSA = 1; -export { ADMIN_KEY, ECDSA, OPERATIONAL_KEY, ZERO_ADDRESS, ZERO_BYTES32 }; +const ACCESS_POLICIES = { + OPEN: 0, + CURATED: 1, +}; + +export { + ADMIN_KEY, + ECDSA, + OPERATIONAL_KEY, + ZERO_ADDRESS, + ZERO_BYTES32, + ACCESS_POLICIES, +}; diff --git a/test/helpers/kc-helpers.ts b/test/helpers/kc-helpers.ts new file mode 100644 index 00000000..dc6c41de --- /dev/null +++ b/test/helpers/kc-helpers.ts @@ -0,0 +1,148 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { ethers, getBytes } from 'ethers'; + +import { createProfile, createProfiles } from './profile-helpers'; +import { KCSignaturesData, NodeAccounts } from './types'; +import { KnowledgeCollection, Token, Profile } from '../../typechain'; + +export async function signMessage( + signer: SignerWithAddress, + messageHash: string | Uint8Array, +) { + const packedMessage = getBytes(messageHash); + const signature = await signer.signMessage(packedMessage); + const { v, r, s } = ethers.Signature.from(signature); + const vsValue = BigInt(s) | ((BigInt(v) - BigInt(27)) << BigInt(255)); + const vs = ethers.zeroPadValue(ethers.toBeHex(vsValue), 32); + return { r, vs }; +} + +export async function getKCSignaturesData( + publishingNode: NodeAccounts, + publisherIdentityId: number, + receivingNodes: NodeAccounts[], +): Promise { + const merkleRoot = ethers.keccak256(ethers.toUtf8Bytes('test-merkle-root')); + const publisherMessageHash = ethers.solidityPackedKeccak256( + ['uint72', 'bytes32'], + [publisherIdentityId, merkleRoot], + ); + + const { r: publisherR, vs: publisherVS } = await signMessage( + publishingNode.operational, + publisherMessageHash, + ); + const { r: receiverR1, vs: receiverVS1 } = await signMessage( + receivingNodes[0].operational, + merkleRoot, + ); + const { r: receiverR2, vs: receiverVS2 } = await signMessage( + receivingNodes[1].operational, + merkleRoot, + ); + const { r: receiverR3, vs: receiverVS3 } = await signMessage( + receivingNodes[2].operational, + merkleRoot, + ); + const receiverRs = [receiverR1, receiverR2, receiverR3]; + const receiverVSs = [receiverVS1, receiverVS2, receiverVS3]; + + return { + merkleRoot, + publisherR, + publisherVS, + receiverRs, + receiverVSs, + }; +} + +export async function createKnowledgeCollection( + kcCreator: SignerWithAddress, + publisherIdentityId: number, + receiversIdentityIds: number[], + signaturesData: KCSignaturesData, + contracts: { + KnowledgeCollection: KnowledgeCollection; + Token: Token; + }, + publishOperationId: string = 'test-operation-id', + knowledgeAssetsAmount: number = 10, + byteSize: number = 1000, + epochs: number = 2, + tokenAmount: bigint = ethers.parseEther('100'), + isImmutable: boolean = false, + paymaster: string = ethers.ZeroAddress, +) { + // Approve tokens + await contracts.Token.connect(kcCreator).increaseAllowance( + contracts.KnowledgeCollection.getAddress(), + tokenAmount, + ); + + // Create knowledge collection + const tx = await contracts.KnowledgeCollection.connect( + kcCreator, + ).createKnowledgeCollection( + publishOperationId, + signaturesData.merkleRoot, + knowledgeAssetsAmount, + byteSize, + epochs, + tokenAmount, + isImmutable, + paymaster, + publisherIdentityId, + signaturesData.publisherR, + signaturesData.publisherVS, + receiversIdentityIds, + signaturesData.receiverRs, + signaturesData.receiverVSs, + ); + + const receipt = await tx.wait(); + const collectionId = Number(receipt!.logs[2].topics[1]); + + return { tx, receipt, collectionId }; +} + +export async function createProfilesAndKC( + kcCreator: SignerWithAddress, + publishingNode: NodeAccounts, + receivingNodes: NodeAccounts[], + contracts: { + Profile: Profile; + KnowledgeCollection: KnowledgeCollection; + Token: Token; + }, +) { + const { identityId: publishingNodeIdentityId } = await createProfile( + contracts.Profile, + publishingNode, + ); + const receivingNodesIdentityIds = ( + await createProfiles(contracts.Profile, receivingNodes) + ).map((p) => p.identityId); + + // Create knowledge collection + const signaturesData = await getKCSignaturesData( + publishingNode, + publishingNodeIdentityId, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + contracts, + ); + + return { + publishingNode, + publishingNodeIdentityId, + receivingNodes, + receivingNodesIdentityIds, + kcCreator, + collectionId, + }; +} diff --git a/test/helpers/paranet-helpers.ts b/test/helpers/paranet-helpers.ts new file mode 100644 index 00000000..d90b8c5d --- /dev/null +++ b/test/helpers/paranet-helpers.ts @@ -0,0 +1,76 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { ethers } from 'ethers'; + +import { ACCESS_POLICIES } from './constants'; +import { createProfilesAndKC } from './kc-helpers'; +import { NodeAccounts } from './types'; +import { + Profile, + KnowledgeCollection, + Token, + KnowledgeCollectionStorage, + Paranet, +} from '../../typechain'; + +export async function setupParanet( + kcCreator: SignerWithAddress, + publishingNode: NodeAccounts, + receivingNodes: NodeAccounts[], + contracts: { + Paranet: Paranet; + Profile: Profile; + Token: Token; + KnowledgeCollection: KnowledgeCollection; + KnowledgeCollectionStorage: KnowledgeCollectionStorage; + }, + paranetName: string = 'Test Paranet', + paranetDescription: string = 'Test Paranet Description', + nodesAccessPolicy: number = ACCESS_POLICIES.OPEN, + minersAccessPolicy: number = ACCESS_POLICIES.OPEN, + knowledgeCollectionsSubmissionPolicy: number = ACCESS_POLICIES.OPEN, +) { + const { publishingNodeIdentityId, receivingNodesIdentityIds, collectionId } = + await createProfilesAndKC( + kcCreator, + publishingNode, + receivingNodes, + contracts, + ); + + // Register paranet + const paranetKCStorageContract = + await contracts.KnowledgeCollectionStorage.getAddress(); + const paranetKATokenId = 1; + + await contracts.Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + collectionId, + paranetKATokenId, + paranetName, + paranetDescription, + nodesAccessPolicy, + minersAccessPolicy, + knowledgeCollectionsSubmissionPolicy, + ); + + return { + publishingNode, + receivingNodes, + publishingNodeIdentityId, + receivingNodesIdentityIds, + paranetOwner: kcCreator, + paranetKCStorageContract, + paranetKCTokenId: collectionId, + paranetKATokenId, + paranetName, + paranetDescription, + nodesAccessPolicy, + minersAccessPolicy, + paranetId: ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [paranetKCStorageContract, collectionId, paranetKATokenId], + ), + ), + }; +} diff --git a/test/helpers/profile-helpers.ts b/test/helpers/profile-helpers.ts new file mode 100644 index 00000000..c262a39f --- /dev/null +++ b/test/helpers/profile-helpers.ts @@ -0,0 +1,36 @@ +import { randomBytes } from 'crypto'; + +import { NodeAccounts } from './types'; +import { Profile } from '../../typechain'; + +export async function createProfile( + Profile: Profile, + nodeAccounts: NodeAccounts, +) { + const nodeId = '0x' + randomBytes(32).toString('hex'); + const tx = await Profile.connect(nodeAccounts.operational).createProfile( + nodeAccounts.admin.address, + [], + `Node ${Math.floor(Math.random() * 1000)}`, + nodeId, + 0, + ); + const receipt = await tx.wait(); + const identityId = Number(receipt!.logs[0].topics[1]); + return { nodeId, identityId }; +} + +export async function createProfiles( + Profile: Profile, + nodeAccounts: NodeAccounts[], +): Promise<{ nodeId: string; identityId: number }[]> { + const profiles: { nodeId: string; identityId: number }[] = []; + for (let i = 0; i < nodeAccounts.length; i++) { + const { nodeId, identityId } = await createProfile( + Profile, + nodeAccounts[i], + ); + profiles.push({ nodeId, identityId }); + } + return profiles; +} diff --git a/test/helpers/setup-helpers.ts b/test/helpers/setup-helpers.ts new file mode 100644 index 00000000..7cf5c109 --- /dev/null +++ b/test/helpers/setup-helpers.ts @@ -0,0 +1,22 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; + +export function getDefaultPublishingNode(accounts: SignerWithAddress[]) { + return { + admin: accounts[1], + operational: accounts[2], + }; +} + +export function getDefaultReceivingNodes( + accounts: SignerWithAddress[], + receivingNodesNumber: number = 3, +) { + return Array.from({ length: receivingNodesNumber }, (_, i) => ({ + admin: accounts[3 + i], + operational: accounts[4 + i], + })); +} + +export function getDefaultKCCreator(accounts: SignerWithAddress[]) { + return accounts[9]; +} diff --git a/test/helpers/types.ts b/test/helpers/types.ts new file mode 100644 index 00000000..5ec02048 --- /dev/null +++ b/test/helpers/types.ts @@ -0,0 +1,14 @@ +import { HardhatEthersSigner as SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; + +export type NodeAccounts = { + admin: SignerWithAddress; + operational: SignerWithAddress; +}; + +export type KCSignaturesData = { + merkleRoot: string; + publisherR: string; + publisherVS: string; + receiverRs: string[]; + receiverVSs: string[]; +}; diff --git a/test/integration/OldParanet.test.ts b/test/integration/OldParanet.test.ts new file mode 100644 index 00000000..4127bb49 --- /dev/null +++ b/test/integration/OldParanet.test.ts @@ -0,0 +1,494 @@ +// import { randomBytes } from 'crypto'; + +// import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +// import { expect } from 'chai'; +// import { BigNumberish, BytesLike } from 'ethers'; +// import hre from 'hardhat'; +// import { SignerWithAddress } from 'hardhat-deploy-ethers/signers'; +// import { Address } from 'hardhat-deploy/types'; + +// import { +// Paranet, +// ParanetsRegistry, +// ParanetKnowledgeMinersRegistry, +// HubController, +// ContentAssetV2, +// ServiceAgreementV1, +// ContentAssetStorageV2, +// Token, +// ParanetIncentivesPoolFactory, +// } from '../../typechain'; +// import { ContentAssetStructs } from '../../../typechain/contracts/v2/assets/ContentAsset.sol/ContentAssetV2'; + +// type ParanetNeuroIncentivesPoolFixture = { +// accounts: SignerWithAddress[]; +// ContentAsset: ContentAssetV2; +// ContentAssetStorage: ContentAssetStorageV2; +// ServiceAgreementV1: ServiceAgreementV1; +// Token: Token; +// ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; +// ParanetsRegistry: ParanetsRegistry; +// Paranet: Paranet; +// ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; +// }; + +// describe('@v2 @integration Paranet', function () { +// let accounts: SignerWithAddress[]; +// let operator: SignerWithAddress; +// let miner: SignerWithAddress; +// let HubController: HubController; +// let ContentAsset: ContentAssetV2; +// let ServiceAgreementV1: ServiceAgreementV1; +// let ContentAssetStorage: ContentAssetStorageV2; +// let Token: Token; +// let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; +// let ParanetsRegistry: ParanetsRegistry; +// let Paranet: Paranet; +// let ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + +// async function deployParanetNeuroIncentivesPoolFixture(): Promise { +// await hre.deployments.fixture([ +// 'Token', +// 'ServiceAgreementV1', +// 'ContentAssetStorageV2', +// 'ContentAssetV2', +// 'Paranet', +// 'ParanetIncentivesPoolFactory', +// ]); + +// ContentAssetStorage = await hre.ethers.getContract('ContentAssetStorage'); +// ContentAsset = await hre.ethers.getContract('ContentAsset'); +// ServiceAgreementV1 = await hre.ethers.getContract('ServiceAgreementV1'); +// Token = await hre.ethers.getContract('Token'); + +// ParanetKnowledgeMinersRegistry = await hre.ethers.getContract( +// 'ParanetKnowledgeMinersRegistry', +// ); +// ParanetsRegistry = await hre.ethers.getContract('ParanetsRegistry'); +// Paranet = await hre.ethers.getContract('Paranet'); +// ParanetIncentivesPoolFactory = await hre.ethers.getContract( +// 'ParanetIncentivesPoolFactory', +// ); + +// accounts = await hre.ethers.getSigners(); + +// HubController = await hre.ethers.getContract('HubController'); +// await HubController.setContractAddress('HubOwner', accounts[0].address); + +// return { +// accounts, +// ContentAssetStorage, +// ContentAsset, +// ServiceAgreementV1, +// Token, +// ParanetKnowledgeMinersRegistry, +// ParanetsRegistry, +// Paranet, +// ParanetIncentivesPoolFactory, +// }; +// } + +// async function createAsset( +// assetInputStruct: ContentAssetStructs.AssetInputArgsStruct, +// ): Promise<{ tokenId: number; keyword: BytesLike; agreementId: BytesLike }> { +// await Token.connect(operator).increaseAllowance(ServiceAgreementV1.address, assetInputStruct.tokenAmount); +// const receipt = await (await ContentAsset.connect(operator).createAsset(assetInputStruct)).wait(); +// const tokenId = Number(receipt.logs[0].topics[3]); + +// const keyword = hre.ethers.utils.solidityPack( +// ['address', 'bytes32'], +// [ContentAssetStorage.address, assetInputStruct.assertionId], +// ); +// const agreementId = hre.ethers.utils.soliditySha256( +// ['address', 'uint256', 'bytes'], +// [ContentAssetStorage.address, tokenId, keyword], +// ); +// return { tokenId, keyword, agreementId }; +// } + +// async function registerParanet( +// paranetKATokenId: BigNumberish, +// paranetName: string, +// paranetDescription: string, +// tracToNeuroEmissionMultiplier: BigNumberish, +// paranetOperatorRewardPercentage: BigNumberish, +// paranetIncentivizationProposalVotersRewardPercentage: BigNumberish, +// ): Promise<{ paranetId: BytesLike; ParanetNeuroIncentivesPoolAddress: Address }> { +// const tx1 = await Paranet.connect(operator).registerParanet( +// ContentAssetStorage.address, +// paranetKATokenId, +// paranetName, +// paranetDescription, +// 0, +// 0, +// ); +// await tx1.wait(); + +// const tx2 = await ParanetIncentivesPoolFactory.connect(operator).deployNeuroIncentivesPool( +// ContentAssetStorage.address, +// paranetKATokenId, +// tracToNeuroEmissionMultiplier, +// paranetOperatorRewardPercentage, +// paranetIncentivizationProposalVotersRewardPercentage, +// ); +// await tx2.wait(); + +// const paranetId = hre.ethers.utils.keccak256( +// hre.ethers.utils.solidityPack(['address', 'uint256'], [ContentAssetStorage.address, paranetKATokenId]), +// ); +// const ParanetNeuroIncentivesPoolAddress = await ParanetsRegistry.getIncentivesPoolAddress(paranetId, 'Neuroweb'); + +// return { +// paranetId, +// ParanetNeuroIncentivesPoolAddress, +// }; +// } + +// beforeEach(async function () { +// hre.helpers.resetDeploymentsJson(); +// ({ +// accounts, +// ContentAssetStorage, +// ContentAsset, +// ServiceAgreementV1, +// Token, +// ParanetKnowledgeMinersRegistry, +// ParanetsRegistry, +// Paranet, +// ParanetIncentivesPoolFactory, +// } = await loadFixture(deployParanetNeuroIncentivesPoolFixture)); + +// operator = accounts[1]; +// miner = accounts[2]; +// }); + +// it('Should accept native tokens, update balance and variable successfully', async function () { +// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('250'), +// scoreFunctionId: 2, +// immutable_: false, +// }; +// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); + +// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( +// paranetTokenId, +// 'Paranet1', +// 'Test Paranet', +// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 +// 1000, // paranetOperatorRewardPercentage -- 10% +// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% +// ); + +// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( +// 'ParanetNeuroIncentivesPool', +// ParanetNeuroIncentivesPoolAddress, +// ); + +// const initialBalance = await ParanetNeuroIncentivesPool.getNeuroBalance(); + +// expect(initialBalance).to.be.equal(0); + +// const value = hre.ethers.utils.parseEther('100'); +// const tx = await operator.sendTransaction({ +// to: ParanetNeuroIncentivesPool.address, +// value, +// }); +// await tx.wait(); + +// const finalBalance = await ParanetNeuroIncentivesPool.getNeuroBalance(); +// const totalNeuroReceived = await ParanetNeuroIncentivesPool.totalNeuroReceived(); + +// expect(finalBalance).to.be.equal(totalNeuroReceived).to.be.equal(value); +// }); + +// // it('Should revert while getting operator reward before miner', async function () { +// // const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// // assertionId: '0x' + randomBytes(32).toString('hex'), +// // size: 1000, +// // triplesNumber: 10, +// // chunksNumber: 10, +// // epochsNumber: 5, +// // tokenAmount: hre.ethers.utils.parseEther('250'), +// // scoreFunctionId: 2, +// // immutable_: false, +// // }; +// // const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); + +// // const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( +// // paranetTokenId, +// // 'Paranet1', +// // 'Test Paranet', +// // hre.ethers.utils.parseEther('1'), // tracToNeuroRatio - 1:1 +// // 1000, // operatorRewardPercentage -- 10% +// // 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% +// // ); + +// // const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( +// // 'ParanetNeuroIncentivesPool', +// // ParanetNeuroIncentivesPoolAddress, +// // ); + +// // const value = hre.ethers.utils.parseEther('1000'); +// // const tx = await operator.sendTransaction({ +// // to: ParanetNeuroIncentivesPool.address, +// // value, +// // }); +// // await tx.wait(); + +// // await expect( +// // ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(), +// // ).to.be.revertedWithCustomError(ParanetNeuroIncentivesPool, 'NoRewardAvailable'); +// // }); + +// it('Should correctly calculate miner reward', async function () { +// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('250'), +// scoreFunctionId: 2, +// immutable_: false, +// }; +// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); + +// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( +// paranetTokenId, +// 'Paranet1', +// 'Test Paranet', +// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 +// 1000, // operatorRewardPercentage -- 10% +// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% +// ); + +// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( +// 'ParanetNeuroIncentivesPool', +// ParanetNeuroIncentivesPoolAddress, +// ); + +// const value = hre.ethers.utils.parseEther('5000'); +// const tx1 = await operator.sendTransaction({ +// to: ParanetNeuroIncentivesPool.address, +// value, +// }); +// await tx1.wait(); + +// // Simulate some miner activity +// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('2500'), // Miner spent 2500 TRAC +// scoreFunctionId: 2, +// immutable_: false, +// }; +// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); +// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); + +// const initialMinerBalance = await miner.getBalance(); +// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); +// const tx2Receipt = await tx2.wait(); +// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); +// const finalMinerBalance = await miner.getBalance(); + +// const expectedMinerReward = value.div(2).mul(85).div(100); +// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( +// expectedMinerReward, +// ); +// }); + +// it('Should correctly calculate and send operator reward after miners reward', async function () { +// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('250'), +// scoreFunctionId: 2, +// immutable_: false, +// }; +// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); + +// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( +// paranetTokenId, +// 'Paranet1', +// 'Test Paranet', +// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 +// 1000, // operatorRewardPercentage -- 10% +// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% +// ); + +// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( +// 'ParanetNeuroIncentivesPool', +// ParanetNeuroIncentivesPoolAddress, +// ); + +// const value = hre.ethers.utils.parseEther('6783'); +// const tx1 = await operator.sendTransaction({ +// to: ParanetNeuroIncentivesPool.address, +// value, +// }); +// await tx1.wait(); + +// // Simulate some miner activity +// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('2000'), // Miner spent 5000 TRAC +// scoreFunctionId: 2, +// immutable_: false, +// }; +// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); +// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); + +// const initialMinerBalance = await miner.getBalance(); +// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); +// const tx2Receipt = await tx2.wait(); +// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); +// const finalMinerBalance = await miner.getBalance(); + +// const expectedMinerReward = hre.ethers.utils.parseEther('2000').mul(85).div(100); +// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( +// expectedMinerReward, +// ); + +// const initialOperatorBalance = await operator.getBalance(); +// const tx3 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); +// const tx3Receipt = await tx3.wait(); +// const tx3Details = await hre.ethers.provider.getTransaction(tx3Receipt.transactionHash); +// const finalOperatorBalance = await operator.getBalance(); + +// const expectedOperatorReward = hre.ethers.utils.parseEther('2000').mul(10).div(100); +// expect(finalOperatorBalance.sub(initialOperatorBalance).add(tx3Receipt.gasUsed.mul(tx3Details.gasPrice))).to.equal( +// expectedOperatorReward, +// ); +// }); + +// it('Should correctly handle additional Neuro deposit and reward claims', async function () { +// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('250'), +// scoreFunctionId: 2, +// immutable_: false, +// }; +// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); + +// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( +// paranetTokenId, +// 'Paranet1', +// 'Test Paranet', +// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 +// 1000, // operatorRewardPercentage -- 10% +// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% +// ); + +// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( +// 'ParanetNeuroIncentivesPool', +// ParanetNeuroIncentivesPoolAddress, +// ); + +// const value = hre.ethers.utils.parseEther('6783'); +// const tx1 = await operator.sendTransaction({ +// to: ParanetNeuroIncentivesPool.address, +// value, +// }); +// await tx1.wait(); + +// // Simulate some miner activity +// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('2000'), // Miner spent 2000 TRAC +// scoreFunctionId: 2, +// immutable_: false, +// }; +// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); +// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); + +// const initialMinerBalance = await miner.getBalance(); +// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); +// const tx2Receipt = await tx2.wait(); +// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); +// const finalMinerBalance = await miner.getBalance(); + +// const expectedMinerReward = hre.ethers.utils.parseEther('2000').mul(85).div(100); +// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( +// expectedMinerReward, +// ); + +// const initialOperatorBalance = await operator.getBalance(); +// const tx3 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); +// const tx3Receipt = await tx3.wait(); +// const tx3Details = await hre.ethers.provider.getTransaction(tx3Receipt.transactionHash); +// const finalOperatorBalance = await operator.getBalance(); + +// const expectedOperatorReward = hre.ethers.utils.parseEther('2000').mul(10).div(100); +// expect(finalOperatorBalance.sub(initialOperatorBalance).add(tx3Receipt.gasUsed.mul(tx3Details.gasPrice))).to.equal( +// expectedOperatorReward, +// ); + +// // Send additional Neuro to the contract +// const additionalValue = hre.ethers.utils.parseEther('3000'); +// const tx4 = await operator.sendTransaction({ +// to: ParanetNeuroIncentivesPool.address, +// value: additionalValue, +// }); +// await tx4.wait(); + +// // Mint another Knowledge asset from miner address +// const additionalKAStruct: ContentAssetStructs.AssetInputArgsStruct = { +// assertionId: '0x' + randomBytes(32).toString('hex'), +// size: 1000, +// triplesNumber: 10, +// chunksNumber: 10, +// epochsNumber: 5, +// tokenAmount: hre.ethers.utils.parseEther('1500'), // Miner spent 1500 TRAC +// scoreFunctionId: 2, +// immutable_: false, +// }; +// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, additionalKAStruct.tokenAmount); +// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, additionalKAStruct); + +// // Claim rewards for miner and operator again +// const initialMinerBalance2 = await miner.getBalance(); +// const tx5 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); +// const tx5Receipt = await tx5.wait(); +// const tx5Details = await hre.ethers.provider.getTransaction(tx5Receipt.transactionHash); +// const finalMinerBalance2 = await miner.getBalance(); + +// const expectedMinerReward2 = hre.ethers.utils.parseEther('1500').mul(85).div(100); +// expect(finalMinerBalance2.sub(initialMinerBalance2).add(tx5Receipt.gasUsed.mul(tx5Details.gasPrice))).to.equal( +// expectedMinerReward2, +// ); + +// const initialOperatorBalance2 = await operator.getBalance(); +// const tx6 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); +// const tx6Receipt = await tx6.wait(); +// const tx6Details = await hre.ethers.provider.getTransaction(tx6Receipt.transactionHash); +// const finalOperatorBalance2 = await operator.getBalance(); + +// const expectedOperatorReward2 = hre.ethers.utils.parseEther('1500').mul(10).div(100); +// expect( +// finalOperatorBalance2.sub(initialOperatorBalance2).add(tx6Receipt.gasUsed.mul(tx6Details.gasPrice)), +// ).to.equal(expectedOperatorReward2); +// }); +// }); diff --git a/test/integration/Paranet.test.ts b/test/integration/Paranet.test.ts index 4127bb49..f605ed99 100644 --- a/test/integration/Paranet.test.ts +++ b/test/integration/Paranet.test.ts @@ -1,494 +1,5801 @@ // import { randomBytes } from 'crypto'; -// import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; -// import { expect } from 'chai'; -// import { BigNumberish, BytesLike } from 'ethers'; -// import hre from 'hardhat'; -// import { SignerWithAddress } from 'hardhat-deploy-ethers/signers'; -// import { Address } from 'hardhat-deploy/types'; - -// import { -// Paranet, -// ParanetsRegistry, -// ParanetKnowledgeMinersRegistry, -// HubController, -// ContentAssetV2, -// ServiceAgreementV1, -// ContentAssetStorageV2, -// Token, -// ParanetIncentivesPoolFactory, -// } from '../../typechain'; -// import { ContentAssetStructs } from '../../../typechain/contracts/v2/assets/ContentAsset.sol/ContentAssetV2'; - -// type ParanetNeuroIncentivesPoolFixture = { -// accounts: SignerWithAddress[]; -// ContentAsset: ContentAssetV2; -// ContentAssetStorage: ContentAssetStorageV2; -// ServiceAgreementV1: ServiceAgreementV1; -// Token: Token; -// ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; -// ParanetsRegistry: ParanetsRegistry; -// Paranet: Paranet; -// ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; -// }; - -// describe('@v2 @integration Paranet', function () { -// let accounts: SignerWithAddress[]; -// let operator: SignerWithAddress; -// let miner: SignerWithAddress; -// let HubController: HubController; -// let ContentAsset: ContentAssetV2; -// let ServiceAgreementV1: ServiceAgreementV1; -// let ContentAssetStorage: ContentAssetStorageV2; -// let Token: Token; -// let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; -// let ParanetsRegistry: ParanetsRegistry; -// let Paranet: Paranet; -// let ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; - -// async function deployParanetNeuroIncentivesPoolFixture(): Promise { -// await hre.deployments.fixture([ -// 'Token', -// 'ServiceAgreementV1', -// 'ContentAssetStorageV2', -// 'ContentAssetV2', -// 'Paranet', -// 'ParanetIncentivesPoolFactory', -// ]); - -// ContentAssetStorage = await hre.ethers.getContract('ContentAssetStorage'); -// ContentAsset = await hre.ethers.getContract('ContentAsset'); -// ServiceAgreementV1 = await hre.ethers.getContract('ServiceAgreementV1'); -// Token = await hre.ethers.getContract('Token'); - -// ParanetKnowledgeMinersRegistry = await hre.ethers.getContract( -// 'ParanetKnowledgeMinersRegistry', -// ); -// ParanetsRegistry = await hre.ethers.getContract('ParanetsRegistry'); -// Paranet = await hre.ethers.getContract('Paranet'); -// ParanetIncentivesPoolFactory = await hre.ethers.getContract( -// 'ParanetIncentivesPoolFactory', -// ); - -// accounts = await hre.ethers.getSigners(); - -// HubController = await hre.ethers.getContract('HubController'); -// await HubController.setContractAddress('HubOwner', accounts[0].address); - -// return { -// accounts, -// ContentAssetStorage, -// ContentAsset, -// ServiceAgreementV1, -// Token, -// ParanetKnowledgeMinersRegistry, -// ParanetsRegistry, -// Paranet, -// ParanetIncentivesPoolFactory, -// }; -// } - -// async function createAsset( -// assetInputStruct: ContentAssetStructs.AssetInputArgsStruct, -// ): Promise<{ tokenId: number; keyword: BytesLike; agreementId: BytesLike }> { -// await Token.connect(operator).increaseAllowance(ServiceAgreementV1.address, assetInputStruct.tokenAmount); -// const receipt = await (await ContentAsset.connect(operator).createAsset(assetInputStruct)).wait(); -// const tokenId = Number(receipt.logs[0].topics[3]); - -// const keyword = hre.ethers.utils.solidityPack( -// ['address', 'bytes32'], -// [ContentAssetStorage.address, assetInputStruct.assertionId], -// ); -// const agreementId = hre.ethers.utils.soliditySha256( -// ['address', 'uint256', 'bytes'], -// [ContentAssetStorage.address, tokenId, keyword], -// ); -// return { tokenId, keyword, agreementId }; -// } - -// async function registerParanet( -// paranetKATokenId: BigNumberish, -// paranetName: string, -// paranetDescription: string, -// tracToNeuroEmissionMultiplier: BigNumberish, -// paranetOperatorRewardPercentage: BigNumberish, -// paranetIncentivizationProposalVotersRewardPercentage: BigNumberish, -// ): Promise<{ paranetId: BytesLike; ParanetNeuroIncentivesPoolAddress: Address }> { -// const tx1 = await Paranet.connect(operator).registerParanet( -// ContentAssetStorage.address, -// paranetKATokenId, -// paranetName, -// paranetDescription, -// 0, -// 0, -// ); -// await tx1.wait(); - -// const tx2 = await ParanetIncentivesPoolFactory.connect(operator).deployNeuroIncentivesPool( -// ContentAssetStorage.address, -// paranetKATokenId, -// tracToNeuroEmissionMultiplier, -// paranetOperatorRewardPercentage, -// paranetIncentivizationProposalVotersRewardPercentage, -// ); -// await tx2.wait(); - -// const paranetId = hre.ethers.utils.keccak256( -// hre.ethers.utils.solidityPack(['address', 'uint256'], [ContentAssetStorage.address, paranetKATokenId]), -// ); -// const ParanetNeuroIncentivesPoolAddress = await ParanetsRegistry.getIncentivesPoolAddress(paranetId, 'Neuroweb'); - -// return { -// paranetId, -// ParanetNeuroIncentivesPoolAddress, -// }; -// } - -// beforeEach(async function () { -// hre.helpers.resetDeploymentsJson(); -// ({ -// accounts, -// ContentAssetStorage, -// ContentAsset, -// ServiceAgreementV1, -// Token, -// ParanetKnowledgeMinersRegistry, -// ParanetsRegistry, -// Paranet, -// ParanetIncentivesPoolFactory, -// } = await loadFixture(deployParanetNeuroIncentivesPoolFixture)); - -// operator = accounts[1]; -// miner = accounts[2]; -// }); - -// it('Should accept native tokens, update balance and variable successfully', async function () { -// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('250'), -// scoreFunctionId: 2, -// immutable_: false, -// }; -// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); - -// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( -// paranetTokenId, -// 'Paranet1', -// 'Test Paranet', -// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 -// 1000, // paranetOperatorRewardPercentage -- 10% -// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% -// ); - -// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( -// 'ParanetNeuroIncentivesPool', -// ParanetNeuroIncentivesPoolAddress, -// ); - -// const initialBalance = await ParanetNeuroIncentivesPool.getNeuroBalance(); - -// expect(initialBalance).to.be.equal(0); - -// const value = hre.ethers.utils.parseEther('100'); -// const tx = await operator.sendTransaction({ -// to: ParanetNeuroIncentivesPool.address, -// value, -// }); -// await tx.wait(); - -// const finalBalance = await ParanetNeuroIncentivesPool.getNeuroBalance(); -// const totalNeuroReceived = await ParanetNeuroIncentivesPool.totalNeuroReceived(); - -// expect(finalBalance).to.be.equal(totalNeuroReceived).to.be.equal(value); -// }); - -// // it('Should revert while getting operator reward before miner', async function () { -// // const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// // assertionId: '0x' + randomBytes(32).toString('hex'), -// // size: 1000, -// // triplesNumber: 10, -// // chunksNumber: 10, -// // epochsNumber: 5, -// // tokenAmount: hre.ethers.utils.parseEther('250'), -// // scoreFunctionId: 2, -// // immutable_: false, -// // }; -// // const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); - -// // const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( -// // paranetTokenId, -// // 'Paranet1', -// // 'Test Paranet', -// // hre.ethers.utils.parseEther('1'), // tracToNeuroRatio - 1:1 -// // 1000, // operatorRewardPercentage -- 10% -// // 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% -// // ); - -// // const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( -// // 'ParanetNeuroIncentivesPool', -// // ParanetNeuroIncentivesPoolAddress, -// // ); - -// // const value = hre.ethers.utils.parseEther('1000'); -// // const tx = await operator.sendTransaction({ -// // to: ParanetNeuroIncentivesPool.address, -// // value, -// // }); -// // await tx.wait(); - -// // await expect( -// // ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(), -// // ).to.be.revertedWithCustomError(ParanetNeuroIncentivesPool, 'NoRewardAvailable'); -// // }); - -// it('Should correctly calculate miner reward', async function () { -// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('250'), -// scoreFunctionId: 2, -// immutable_: false, -// }; -// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); - -// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( -// paranetTokenId, -// 'Paranet1', -// 'Test Paranet', -// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 -// 1000, // operatorRewardPercentage -- 10% -// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% -// ); - -// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( -// 'ParanetNeuroIncentivesPool', -// ParanetNeuroIncentivesPoolAddress, -// ); - -// const value = hre.ethers.utils.parseEther('5000'); -// const tx1 = await operator.sendTransaction({ -// to: ParanetNeuroIncentivesPool.address, -// value, -// }); -// await tx1.wait(); - -// // Simulate some miner activity -// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('2500'), // Miner spent 2500 TRAC -// scoreFunctionId: 2, -// immutable_: false, -// }; -// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); -// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); - -// const initialMinerBalance = await miner.getBalance(); -// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); -// const tx2Receipt = await tx2.wait(); -// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); -// const finalMinerBalance = await miner.getBalance(); - -// const expectedMinerReward = value.div(2).mul(85).div(100); -// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( -// expectedMinerReward, -// ); -// }); - -// it('Should correctly calculate and send operator reward after miners reward', async function () { -// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('250'), -// scoreFunctionId: 2, -// immutable_: false, -// }; -// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); - -// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( -// paranetTokenId, -// 'Paranet1', -// 'Test Paranet', -// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 -// 1000, // operatorRewardPercentage -- 10% -// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% -// ); - -// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( -// 'ParanetNeuroIncentivesPool', -// ParanetNeuroIncentivesPoolAddress, -// ); - -// const value = hre.ethers.utils.parseEther('6783'); -// const tx1 = await operator.sendTransaction({ -// to: ParanetNeuroIncentivesPool.address, -// value, -// }); -// await tx1.wait(); - -// // Simulate some miner activity -// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('2000'), // Miner spent 5000 TRAC -// scoreFunctionId: 2, -// immutable_: false, -// }; -// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); -// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); - -// const initialMinerBalance = await miner.getBalance(); -// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); -// const tx2Receipt = await tx2.wait(); -// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); -// const finalMinerBalance = await miner.getBalance(); - -// const expectedMinerReward = hre.ethers.utils.parseEther('2000').mul(85).div(100); -// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( -// expectedMinerReward, -// ); - -// const initialOperatorBalance = await operator.getBalance(); -// const tx3 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); -// const tx3Receipt = await tx3.wait(); -// const tx3Details = await hre.ethers.provider.getTransaction(tx3Receipt.transactionHash); -// const finalOperatorBalance = await operator.getBalance(); - -// const expectedOperatorReward = hre.ethers.utils.parseEther('2000').mul(10).div(100); -// expect(finalOperatorBalance.sub(initialOperatorBalance).add(tx3Receipt.gasUsed.mul(tx3Details.gasPrice))).to.equal( -// expectedOperatorReward, -// ); -// }); - -// it('Should correctly handle additional Neuro deposit and reward claims', async function () { -// const paranetKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('250'), -// scoreFunctionId: 2, -// immutable_: false, -// }; -// const { tokenId: paranetTokenId } = await createAsset(paranetKAStruct); - -// const { ParanetNeuroIncentivesPoolAddress } = await registerParanet( -// paranetTokenId, -// 'Paranet1', -// 'Test Paranet', -// hre.ethers.utils.parseEther('1'), // tracToNeuroRatio -- 1:1 -// 1000, // operatorRewardPercentage -- 10% -// 500, // paranetIncentivizationProposalVotersRewardPercentage -- 5% -// ); - -// const ParanetNeuroIncentivesPool = await hre.ethers.getContractAt( -// 'ParanetNeuroIncentivesPool', -// ParanetNeuroIncentivesPoolAddress, -// ); - -// const value = hre.ethers.utils.parseEther('6783'); -// const tx1 = await operator.sendTransaction({ -// to: ParanetNeuroIncentivesPool.address, -// value, -// }); -// await tx1.wait(); - -// // Simulate some miner activity -// const testKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('2000'), // Miner spent 2000 TRAC -// scoreFunctionId: 2, -// immutable_: false, -// }; -// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, testKAStruct.tokenAmount); -// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, testKAStruct); - -// const initialMinerBalance = await miner.getBalance(); -// const tx2 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); -// const tx2Receipt = await tx2.wait(); -// const tx2Details = await hre.ethers.provider.getTransaction(tx2Receipt.transactionHash); -// const finalMinerBalance = await miner.getBalance(); - -// const expectedMinerReward = hre.ethers.utils.parseEther('2000').mul(85).div(100); -// expect(finalMinerBalance.sub(initialMinerBalance).add(tx2Receipt.gasUsed.mul(tx2Details.gasPrice))).to.equal( -// expectedMinerReward, -// ); - -// const initialOperatorBalance = await operator.getBalance(); -// const tx3 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); -// const tx3Receipt = await tx3.wait(); -// const tx3Details = await hre.ethers.provider.getTransaction(tx3Receipt.transactionHash); -// const finalOperatorBalance = await operator.getBalance(); - -// const expectedOperatorReward = hre.ethers.utils.parseEther('2000').mul(10).div(100); -// expect(finalOperatorBalance.sub(initialOperatorBalance).add(tx3Receipt.gasUsed.mul(tx3Details.gasPrice))).to.equal( -// expectedOperatorReward, -// ); - -// // Send additional Neuro to the contract -// const additionalValue = hre.ethers.utils.parseEther('3000'); -// const tx4 = await operator.sendTransaction({ -// to: ParanetNeuroIncentivesPool.address, -// value: additionalValue, -// }); -// await tx4.wait(); - -// // Mint another Knowledge asset from miner address -// const additionalKAStruct: ContentAssetStructs.AssetInputArgsStruct = { -// assertionId: '0x' + randomBytes(32).toString('hex'), -// size: 1000, -// triplesNumber: 10, -// chunksNumber: 10, -// epochsNumber: 5, -// tokenAmount: hre.ethers.utils.parseEther('1500'), // Miner spent 1500 TRAC -// scoreFunctionId: 2, -// immutable_: false, -// }; -// await Token.connect(miner).increaseAllowance(ServiceAgreementV1.address, additionalKAStruct.tokenAmount); -// await Paranet.connect(miner).mintKnowledgeAsset(ContentAssetStorage.address, paranetTokenId, additionalKAStruct); - -// // Claim rewards for miner and operator again -// const initialMinerBalance2 = await miner.getBalance(); -// const tx5 = await ParanetNeuroIncentivesPool.connect(miner).claimKnowledgeMinerReward(); -// const tx5Receipt = await tx5.wait(); -// const tx5Details = await hre.ethers.provider.getTransaction(tx5Receipt.transactionHash); -// const finalMinerBalance2 = await miner.getBalance(); - -// const expectedMinerReward2 = hre.ethers.utils.parseEther('1500').mul(85).div(100); -// expect(finalMinerBalance2.sub(initialMinerBalance2).add(tx5Receipt.gasUsed.mul(tx5Details.gasPrice))).to.equal( -// expectedMinerReward2, -// ); - -// const initialOperatorBalance2 = await operator.getBalance(); -// const tx6 = await ParanetNeuroIncentivesPool.connect(operator).claimParanetOperatorReward(); -// const tx6Receipt = await tx6.wait(); -// const tx6Details = await hre.ethers.provider.getTransaction(tx6Receipt.transactionHash); -// const finalOperatorBalance2 = await operator.getBalance(); - -// const expectedOperatorReward2 = hre.ethers.utils.parseEther('1500').mul(10).div(100); -// expect( -// finalOperatorBalance2.sub(initialOperatorBalance2).add(tx6Receipt.gasUsed.mul(tx6Details.gasPrice)), -// ).to.equal(expectedOperatorReward2); -// }); -// }); +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { ethers, EventLog } from 'ethers'; +import hre from 'hardhat'; + +import { + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + Hub, + EpochStorage, + ParanetIncentivesPoolStorage, + ParanetIncentivesPoolFactoryHelper, + ParanetStagingRegistry, + IdentityStorage, + HubLib, + ParanetLib, +} from '../../typechain'; +import { ACCESS_POLICIES } from '../helpers/constants'; +import { + createProfilesAndKC, + getKCSignaturesData, + createKnowledgeCollection, +} from '../helpers/kc-helpers'; +import { setupParanet } from '../helpers/paranet-helpers'; +import { + getDefaultPublishingNode, + getDefaultReceivingNodes, + getDefaultKCCreator, +} from '../helpers/setup-helpers'; + +// Fixture containing all contracts and accounts needed to test Paranet +type ParanetFixture = { + accounts: SignerWithAddress[]; + Paranet: Paranet; + ParanetsRegistry: ParanetsRegistry; + ParanetServicesRegistry: ParanetServicesRegistry; + ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + ParanetKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + ParanetIncentivesPoolFactoryHelper: ParanetIncentivesPoolFactoryHelper; + ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + KnowledgeCollection: KnowledgeCollection; + KnowledgeCollectionStorage: KnowledgeCollectionStorage; + Profile: Profile; + Token: Token; + EpochStorage: EpochStorage; + ParanetStagingRegistry: ParanetStagingRegistry; + IdentityStorage: IdentityStorage; + HubLib: HubLib; + ParanetLib: ParanetLib; +}; + +describe('@unit Paranet', () => { + let accounts: SignerWithAddress[]; + let Paranet: Paranet; + let ParanetsRegistry: ParanetsRegistry; + let ParanetServicesRegistry: ParanetServicesRegistry; + let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + let ParanetKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + let ParanetIncentivesPoolFactoryHelper: ParanetIncentivesPoolFactoryHelper; + let ParanetIncentivesPoolFactory: ParanetIncentivesPoolFactory; + let KnowledgeCollection: KnowledgeCollection; + let KnowledgeCollectionStorage: KnowledgeCollectionStorage; + let Profile: Profile; + let Token: Token; + let EpochStorage: EpochStorage; + let ParanetStagingRegistry: ParanetStagingRegistry; + let IdentityStorage: IdentityStorage; + let HubLib: HubLib; + let ParanetLib: ParanetLib; + + // Deploy all contracts, set the HubOwner and necessary accounts. Returns the ParanetFixture + async function deployParanetFixture(): Promise { + await hre.deployments.fixture([ + 'Paranet', + 'ParanetsRegistry', + 'ParanetServicesRegistry', + 'ParanetKnowledgeMinersRegistry', + 'ParanetKnowledgeCollectionsRegistry', + 'ParanetIncentivesPoolFactoryHelper', + 'ParanetIncentivesPoolFactory', + 'KnowledgeCollection', + 'Profile', + 'Token', + 'EpochStorage', + 'ParanetStagingRegistry', + 'IdentityStorage', + ]); + + accounts = await hre.ethers.getSigners(); + const Hub = await hre.ethers.getContract('Hub'); + await Hub.setContractAddress('HubOwner', accounts[0].address); + + EpochStorage = await hre.ethers.getContract('EpochStorageV8'); + Paranet = await hre.ethers.getContract('Paranet'); + ParanetsRegistry = + await hre.ethers.getContract('ParanetsRegistry'); + ParanetServicesRegistry = + await hre.ethers.getContract( + 'ParanetServicesRegistry', + ); + ParanetKnowledgeMinersRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeMinersRegistry', + ); + ParanetKnowledgeCollectionsRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeCollectionsRegistry', + ); + ParanetIncentivesPoolFactoryHelper = + await hre.ethers.getContract( + 'ParanetIncentivesPoolFactoryHelper', + ); + ParanetIncentivesPoolFactory = + await hre.ethers.getContract( + 'ParanetIncentivesPoolFactory', + ); + KnowledgeCollection = await hre.ethers.getContract( + 'KnowledgeCollection', + ); + KnowledgeCollectionStorage = + await hre.ethers.getContract( + 'KnowledgeCollectionStorage', + ); + Profile = await hre.ethers.getContract('Profile'); + // await hre.deployments.deploy('Token', { + // from: accounts[0].address, + // args: ['Neuro', 'NEURO'], + // log: true, + // }); + Token = await hre.ethers.getContract('Token'); + ParanetStagingRegistry = + await hre.ethers.getContract( + 'ParanetStagingRegistry', + ); + IdentityStorage = + await hre.ethers.getContract('IdentityStorage'); + + const hubLibDeployment = await hre.deployments.deploy('HubLib', { + from: accounts[0].address, + log: true, + }); + HubLib = await hre.ethers.getContract( + 'HubLib', + hubLibDeployment.address, + ); + + const paranetLibDeployment = await hre.deployments.deploy('ParanetLib', { + from: accounts[0].address, + log: true, + }); + ParanetLib = await hre.ethers.getContract( + 'ParanetLib', + paranetLibDeployment.address, + ); + + return { + accounts, + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactoryHelper, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + EpochStorage, + ParanetStagingRegistry, + IdentityStorage, + HubLib, + ParanetLib, + }; + } + + // Before each test, deploy all contracts and necessary accounts. These variables can be used in the tests + beforeEach(async () => { + ({ + accounts, + Paranet, + ParanetsRegistry, + ParanetServicesRegistry, + ParanetKnowledgeMinersRegistry, + ParanetKnowledgeCollectionsRegistry, + ParanetIncentivesPoolFactoryHelper, + ParanetIncentivesPoolFactory, + KnowledgeCollection, + KnowledgeCollectionStorage, + Profile, + Token, + ParanetStagingRegistry, + HubLib, + ParanetLib, + } = await loadFixture(deployParanetFixture)); + }); + + describe('Paranet Registration', () => { + it('Should return the correct name and version of the paranet', async () => { + const name = await Paranet.name(); + const version = await Paranet.version(); + expect(name).to.equal('Paranet'); + expect(version).to.equal('1.0.0'); + }); + + it('Should register a paranet successfully', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetName, + paranetDescription, + nodesAccessPolicy, + minersAccessPolicy, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(await ParanetsRegistry.paranetExists(paranetId)).to.be.true; + + // Check paranet owner + const startTokenId = + (paranetKCTokenId - 1) * + Number( + await KnowledgeCollectionStorage.knowledgeCollectionMaxSize(), + ) + + paranetKATokenId; + + const ownedCountInRange = await KnowledgeCollectionStorage[ + 'balanceOf(address,uint256,uint256)' + ](paranetOwner.address, startTokenId, startTokenId + 1); + + expect(ownedCountInRange).to.equal(1); + + // Check paranet metadata + const paranetMetadata = + await ParanetsRegistry.getParanetMetadata(paranetId); + expect(paranetMetadata.paranetKCStorageContract).to.equal( + paranetKCStorageContract, + ); + expect(paranetMetadata.paranetKCTokenId).to.equal(paranetKCTokenId); + expect(paranetMetadata.paranetKATokenId).to.equal(paranetKATokenId); + expect(paranetMetadata.name).to.equal(paranetName); + expect(paranetMetadata.description).to.equal(paranetDescription); + expect(paranetMetadata.nodesAccessPolicy).to.equal(nodesAccessPolicy); + expect(paranetMetadata.minersAccessPolicy).to.equal(minersAccessPolicy); + }); + + it('Should revert when registering the same paranet twice', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const paranetName = 'Test Paranet'; + const paranetDescription = 'Test Paranet Description'; + const nodesAccessPolicy = ACCESS_POLICIES.OPEN; + const minersAccessPolicy = ACCESS_POLICIES.OPEN; + + const { paranetKCStorageContract, paranetKCTokenId, paranetKATokenId } = + await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + paranetName, + paranetDescription, + nodesAccessPolicy, + minersAccessPolicy, + ); + + await expect( + Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetName, + paranetDescription, + nodesAccessPolicy, + minersAccessPolicy, + ACCESS_POLICIES.OPEN, + ), + ).to.be.revertedWithCustomError( + Paranet, + 'ParanetHasAlreadyBeenRegistered', + ); + }); + + it('Should revert when non-owner tries to register paranet', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonOwner = accounts[10]; + + const paranetKCStorageContract = + await KnowledgeCollectionStorage.getAddress(); + + const { collectionId } = await createProfilesAndKC( + kcCreator, + publishingNode, + receivingNodes, + { + Profile, + KnowledgeCollection, + Token, + }, + ); + + await expect( + Paranet.connect(nonOwner).registerParanet( + paranetKCStorageContract, + collectionId, + 1, + 'paranetName', + 'paranetDescription', + ACCESS_POLICIES.OPEN, + ACCESS_POLICIES.OPEN, + ACCESS_POLICIES.OPEN, + ), + ).to.be.revertedWith("Caller isn't the owner of the KA"); + }); + + it('Should revert when registering paranet with invalid access policy values', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const paranetKCStorageContract = + await KnowledgeCollectionStorage.getAddress(); + + // Create profiles and KC first + const { collectionId } = await createProfilesAndKC( + kcCreator, + publishingNode, + receivingNodes, + { + Profile, + KnowledgeCollection, + Token, + }, + ); + + // Try with invalid nodes access policy (2) + await expect( + Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + collectionId, + 1, // kaTokenId + 'Test Paranet', + 'Test Description', + 2, // invalid nodes access policy + 0, // valid miners access policy + 0, // valid submission policy + ), + ).to.be.revertedWith('Invalid policy'); + + // Try with invalid miners access policy (3) + await expect( + Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + collectionId, + 1, // kaTokenId + 'Test Paranet', + 'Test Description', + 0, // valid nodes access policy + 3, // invalid miners access policy + 0, // valid submission policy + ), + ).to.be.revertedWith('Invalid policy'); + + // Try with invalid submission policy (2) + await expect( + Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + collectionId, + 1, // kaTokenId + 'Test Paranet', + 'Test Description', + 0, // valid nodes access policy + 0, // valid miners access policy + 2, // invalid submission policy + ), + ).to.be.revertedWith('Invalid policy'); + + // Try with all invalid policies + await expect( + Paranet.connect(kcCreator).registerParanet( + paranetKCStorageContract, + collectionId, + 1, // kaTokenId + 'Test Paranet', + 'Test Description', + 2, // invalid nodes access policy + 3, // invalid miners access policy + 2, // invalid submission policy + ), + ).to.be.revertedWith('Invalid policy'); + }); + }); + + describe('Paranet Metadata', () => { + it('Should update paranet metadata successfully', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const newName = 'New Name'; + const newDescription = 'New Description'; + + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + newName, + newDescription, + ); + + const updatedName = await ParanetsRegistry.getName(paranetId); + const updatedDescription = + await ParanetsRegistry.getDescription(paranetId); + expect(updatedName).to.equal(newName); + expect(updatedDescription).to.equal(newDescription); + }); + + it('Should emit ParanetMetadataUpdated event with correct parameters', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const newName = 'New Name'; + const newDescription = 'New Description'; + + await expect( + Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + newName, + newDescription, + ), + ) + .to.emit(Paranet, 'ParanetMetadataUpdated') + .withArgs( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + newName, + newDescription, + ); + }); + + it('Should handle empty strings for name and description', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + '', + '', + ); + + expect(await ParanetsRegistry.getName(paranetId)).to.equal(''); + expect(await ParanetsRegistry.getDescription(paranetId)).to.equal(''); + }); + + it('Should handle very long name and description', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const longName = 'a'.repeat(100); + const longDescription = 'b'.repeat(1000); + + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + longName, + longDescription, + ); + + expect(await ParanetsRegistry.getName(paranetId)).to.equal(longName); + expect(await ParanetsRegistry.getDescription(paranetId)).to.equal( + longDescription, + ); + }); + + it('Should allow multiple updates to the same paranet', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // First update + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + 'First Name', + 'First Description', + ); + + expect(await ParanetsRegistry.getName(paranetId)).to.equal('First Name'); + expect(await ParanetsRegistry.getDescription(paranetId)).to.equal( + 'First Description', + ); + + // Second update + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + 'Second Name', + 'Second Description', + ); + + expect(await ParanetsRegistry.getName(paranetId)).to.equal('Second Name'); + expect(await ParanetsRegistry.getDescription(paranetId)).to.equal( + 'Second Description', + ); + }); + + it('Should handle special characters in name and description', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const specialName = '!@#$%^&*()_+-=[]{}|;:,.<>?`~'; + const specialDescription = '¡™£¢∞§¶•ªº–≠œ∑´®†¥¨ˆøπ"åß∂ƒ©˙∆˚¬…æ'; + + await Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + specialName, + specialDescription, + ); + + expect(await ParanetsRegistry.getName(paranetId)).to.equal(specialName); + expect(await ParanetsRegistry.getDescription(paranetId)).to.equal( + specialDescription, + ); + }); + + it('Should revert when non-owner tries to update paranet metadata', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonOwner = accounts[10]; + + const { paranetKCStorageContract, paranetKCTokenId, paranetKATokenId } = + await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const newName = 'New Name'; + const newDescription = 'New Description'; + + await expect( + Paranet.connect(nonOwner).updateParanetMetadata( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + newName, + newDescription, + ), + ).to.be.revertedWith("Caller isn't the owner of the KA"); + }); + + it('updateParanetMetadata should revert when paranet does not exist', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + // Create a KC first + const { collectionId } = await createProfilesAndKC( + kcCreator, + publishingNode, + receivingNodes, + { Profile, KnowledgeCollection, Token }, + ); + + // Try to update metadata for non-existent paranet + await expect( + Paranet.connect(kcCreator).updateParanetMetadata( + await KnowledgeCollectionStorage.getAddress(), + collectionId, + 1, + 'New Name', + 'New Description', + ), + ).to.be.revertedWithCustomError(ParanetLib, 'ParanetDoesntExist'); + }); + + it('Should revert when trying to update with extremely large token IDs', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { paranetKCStorageContract, paranetOwner } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + ); + + const maxUint256 = ethers.MaxUint256; + + await expect( + Paranet.connect(paranetOwner).updateParanetMetadata( + paranetKCStorageContract, + maxUint256, + maxUint256, + 'New Name', + 'New Description', + ), + ).to.be.reverted; // Should revert with token ID out of range or similar + }); + }); + + describe('Paranet Incentives Pool', () => { + it('Should return correct name and version of factory, incentives pool and storage', async () => { + // Incentives pool factory + const poolFactoryName = await ParanetIncentivesPoolFactory.name(); + const poolFactoryVersion = await ParanetIncentivesPoolFactory.version(); + expect(poolFactoryName).to.equal('ParanetIncentivesPoolFactory'); + expect(poolFactoryVersion).to.equal('1.0.0'); + + // Incentives pool factory helper + const poolFactoryHelperName = + await ParanetIncentivesPoolFactoryHelper.name(); + const poolFactoryHelperVersion = + await ParanetIncentivesPoolFactoryHelper.version(); + expect(poolFactoryHelperName).to.equal( + 'ParanetIncentivesPoolFactoryHelper', + ); + expect(poolFactoryHelperVersion).to.equal('1.0.0'); + + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const tracToNeuroEmissionMultiplier = ethers.parseUnits('1', 12); // 1 NEURO per 1 TRAC + const operatorRewardPercentage = 1000; // 10% + const votersRewardPercentage = 2000; // 20% + + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + tracToNeuroEmissionMultiplier, + operatorRewardPercentage, + votersRewardPercentage, + 'Neuroweb', + await Token.getAddress(), + ); + + // get incentives pool name and version + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + const incentivesPoolName = await incentivesPool.name(); + const incentivesPoolVersion = await incentivesPool.version(); + expect(incentivesPoolName).to.equal('ParanetIncentivesPool'); + expect(incentivesPoolVersion).to.equal('1.0.0'); + + const incentivesPoolStorage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event?.args[3], + ); + const incentivesPoolStorageName = await incentivesPoolStorage.name(); + const incentivesPoolStorageVersion = + await incentivesPoolStorage.version(); + expect(incentivesPoolStorageName).to.equal( + 'ParanetIncentivesPoolStorage', + ); + expect(incentivesPoolStorageVersion).to.equal('1.0.0'); + }); + + it('Should deploy incentives pool successfully', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const tracToNeuroEmissionMultiplier = ethers.parseUnits('1', 12); // 1 NEURO per 1 TRAC + const operatorRewardPercentage = 1000; // 10% + const votersRewardPercentage = 2000; // 20% + + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + tracToNeuroEmissionMultiplier, + operatorRewardPercentage, + votersRewardPercentage, + 'Neuroweb', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + expect(event?.args[0]).to.equal(paranetKCStorageContract); + expect(event?.args[1]).to.equal(paranetKCTokenId); + expect(event?.args[2]).to.equal(paranetKATokenId); + expect(event?.args[5]).to.equal(await Token.getAddress()); + + let incentivesPool = await ParanetsRegistry.getIncentivesPoolByPoolName( + paranetId, + 'Neuroweb', + ); + expect(incentivesPool.storageAddr).to.equal(event?.args[3]); + expect(incentivesPool.rewardTokenAddress).to.equal( + await Token.getAddress(), + ); + + incentivesPool = await ParanetsRegistry.getIncentivesPoolByStorageAddress( + paranetId, + event?.args[3], + ); + expect(incentivesPool.name).to.equal('Neuroweb'); + expect(incentivesPool.rewardTokenAddress).to.equal( + await Token.getAddress(), + ); + + // validate incentives pool address + const incentivesPoolStorage = (await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event?.args[3], + )) as ParanetIncentivesPoolStorage; + expect( + await incentivesPoolStorage.paranetIncentivesPoolAddress(), + ).to.equal(event?.args[4]); + expect(await incentivesPoolStorage.paranetId()).to.equal(paranetId); + }); + + it('Should handle multiple incentives pools for same paranet', async () => { + // 1. Setup paranet first + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy first incentives pool + const tx1 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), // 1 NEURO per 1 TRAC + 1000, // 10% operator + 2000, // 20% voters + 'Pool1', + await Token.getAddress(), + ); + + const receipt1 = await tx1.wait(); + const event1 = receipt1!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 3. Deploy second incentives pool with different parameters + const tx2 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('2', 12), // 2 NEURO per 1 TRAC + 1500, // 15% operator + 2500, // 25% voters + 'Pool2', + await Token.getAddress(), + ); + + const receipt2 = await tx2.wait(); + const event2 = receipt2!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 4. Verify both pools exist and have correct parameters + const pool1 = await ParanetsRegistry.getIncentivesPoolByPoolName( + paranetId, + 'Pool1', + ); + const pool2 = await ParanetsRegistry.getIncentivesPoolByPoolName( + paranetId, + 'Pool2', + ); + + expect(pool1.storageAddr).to.equal(event1?.args[3]); + expect(pool2.storageAddr).to.equal(event2?.args[3]); + expect(pool1.storageAddr).to.not.equal(pool2.storageAddr); + + // 5. Verify pool parameters through storage contracts + const pool1Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event1?.args[3], + ); + const pool2Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event2?.args[3], + ); + + expect(await pool1Storage.paranetId()).to.equal(paranetId); + expect(await pool2Storage.paranetId()).to.equal(paranetId); + + // TODO: Fund the pools and check rewards + }); + + it('Should fail to deploy incentives pool with invalid parameters', async () => { + // 1. Setup paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Try to deploy with operator + voters percentage > 100% + await expect( + ParanetIncentivesPoolFactory.connect(paranetOwner).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), + 5000, // 50% operator + 6000, // 60% voters (total 110%) + 'InvalidPool', + await Token.getAddress(), + ), + ).to.be.revertedWith('Invalid rewards ratio'); + + // 3. Try to deploy with zero emission multiplier + await expect( + ParanetIncentivesPoolFactory.connect(paranetOwner).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + 0, // zero multiplier + 1000, + 2000, + 'InvalidPool', + await Token.getAddress(), + ), + ).to.be.revertedWith('Emission multiplier must be greater than 0'); + + // 4. Try to deploy with empty pool name + await expect( + ParanetIncentivesPoolFactory.connect(paranetOwner).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), + 1000, + 2000, + '', // empty name + await Token.getAddress(), + ), + ).to.be.revertedWith('Pool name cannot be empty'); + + // 5. Try to deploy with invalid paranet + await expect( + ParanetIncentivesPoolFactory.connect(paranetOwner).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId + 1, // invalid paranet token id + ethers.parseUnits('1', 12), + 1000, + 2000, + 'InvalidPool', // empty name + await Token.getAddress(), + ), + ).to.be.revertedWith('Paranet does not exist'); + }); + + it('Should handle voter management correctly', async () => { + // 1. Setup paranet and pool + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // Deploy pool + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), + 1000, + 2000, + 'TestPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const incentivesPoolStorage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event?.args[3], + ); + + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + + // Test non-existent voter + expect( + await incentivesPool.voterclaimedToken(accounts[5].address), + ).to.equal(0); + + // Get registrar + const registrar = await incentivesPoolStorage.votersRegistrar(); + const registrarSigner = await hre.ethers.getSigner(registrar); + + // Add voters + const voters = [ + { addr: accounts[5].address, weight: 5000 }, // 50% + { addr: accounts[6].address, weight: 3000 }, // 30% + { addr: accounts[7].address, weight: 2000 }, // 20% + ]; + + await incentivesPoolStorage.connect(registrarSigner).addVoters(voters); + + // Verify voters were added correctly + expect(await incentivesPoolStorage.getVotersCount()).to.equal(3); + expect(await incentivesPoolStorage.cumulativeVotersWeight()).to.equal( + 10000, + ); + + // Now voter exists but hasn't claimed anything yet + expect( + await incentivesPool.voterclaimedToken(accounts[5].address), + ).to.equal(0); + expect( + await incentivesPool.isProposalVoter(accounts[5].address), + ).to.be.eq(true); + + // Update voter weight + await incentivesPoolStorage + .connect(registrarSigner) + .updateVoterWeight(accounts[5].address, 4000); + + const updatedVoter = await incentivesPoolStorage.getVoter( + accounts[5].address, + ); + expect(updatedVoter.weight).to.equal(4000); + expect(await incentivesPoolStorage.cumulativeVotersWeight()).to.equal( + 9000, + ); + + // Remove voter + await incentivesPoolStorage + .connect(registrarSigner) + .removeVoter(accounts[6].address); + + expect(await incentivesPoolStorage.getVotersCount()).to.equal(2); + expect(await incentivesPoolStorage.cumulativeVotersWeight()).to.equal( + 6000, + ); + + // Try to add voter that would exceed max weight + const overweightVoter = [{ addr: accounts[8].address, weight: 5000 }]; + await expect( + incentivesPoolStorage + .connect(registrarSigner) + .addVoters(overweightVoter), + ).to.be.revertedWith('Cumulative weight is too big'); + + // Additional voter management checks + // Try to update non-existent voter + await expect( + incentivesPoolStorage + .connect(registrarSigner) + .updateVoterWeight(accounts[9].address, 1000), + ).to.be.revertedWith('Voter not found'); + + // Try to remove non-existent voter + await expect( + incentivesPoolStorage + .connect(registrarSigner) + .removeVoter(accounts[9].address), + ).to.be.revertedWith('Voter not found'); + + // Check voter rewards calculation + const voterReward = + await incentivesPool.getTotalProposalVoterIncentiveEstimation(); + expect(voterReward).to.be.eq(0); // storage contract was not funded + + // Verify voter weight affects reward calculation + const voter = await incentivesPoolStorage.getVoter(accounts[5].address); + const voterShare = (voterReward * BigInt(voter.weight)) / BigInt(10000); + const claimableVoterReward = await incentivesPool + .connect(accounts[5]) + .getClaimableProposalVoterRewardAmount(); + expect(claimableVoterReward).to.equal(voterShare); + + // Transfer registrar role to new address + await expect( + incentivesPoolStorage + .connect(registrarSigner) + .transferVotersRegistrarRole(accounts[6].address), + ) + .to.emit(incentivesPoolStorage, 'VotersRegistrarTransferred') + .withArgs(registrarSigner.address, accounts[6].address); + + // Verify new registrar + expect(await incentivesPoolStorage.votersRegistrar()).to.equal( + accounts[6].address, + ); + + // Test zero address revert + await expect( + incentivesPoolStorage + .connect(accounts[6]) + .transferVotersRegistrarRole(ethers.ZeroAddress), + ).to.be.revertedWith('New registrar cannot be zero address'); + }); + + it('Should handle incentives pool redeployment', async () => { + // 1. Setup paranet and initial pool + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + paranetId, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const originalEmissionMultiplier = ethers.parseUnits('1', 12); + + // Deploy initial pool + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + originalEmissionMultiplier, + 1000, + 2000, + 'TestPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const originalStorageAddress = event?.args[3]; + const originalPoolAddress = event?.args[4]; + + // Fund the pool + const fundingAmount = ethers.parseUnits('1000', 12); + await Token.connect(accounts[0]).mint( + originalStorageAddress, + fundingAmount, + ); + + const initialPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + originalPoolAddress, + ); + + const minerRewardBeforeRedeploy = await initialPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + + // Redeploy pool + const redeployTx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).redeployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + originalStorageAddress, + ); + + const redeployReceipt = await redeployTx.wait(); + const redeployEvent = redeployReceipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolRedeployed', + ).topicHash, + ) as EventLog; + + // Verify new pool address is different + const newPoolAddress = redeployEvent?.args[4]; + expect(newPoolAddress).to.not.equal(originalPoolAddress); + + // Verify storage address remains the same + expect(redeployEvent?.args[3]).to.equal(originalStorageAddress); + + // Verify storage contract points to the same paranet + const incentivesPoolStorage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + originalStorageAddress, + ); + expect(await incentivesPoolStorage.paranetId()).to.equal(paranetId); + + // Verify storage contract points to the new pool and not the old one + expect( + await incentivesPoolStorage.paranetIncentivesPoolAddress(), + ).to.equal(newPoolAddress); + expect( + await incentivesPoolStorage.paranetIncentivesPoolAddress(), + ).to.not.equal(originalPoolAddress); + + // Verify funds are preserved + expect(await incentivesPoolStorage.getBalance()).to.equal(fundingAmount); + + // Verify new pool has the same emission multiplier + const newPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + newPoolAddress, + ); + expect( + await newPool.getEffectiveTokenEmissionMultiplier(await time.latest()), + ).to.equal(originalEmissionMultiplier); + + const minerRewardAfterRedeploy = await newPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + + expect(minerRewardAfterRedeploy).to.equal(minerRewardBeforeRedeploy); + + // Try to redeploy non-existent pool + await expect( + ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).redeployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + '0x0000000000000000000000000000000000000000', + ), + ).to.be.revertedWith( + 'Cannot redeploy an incentives pool that does not exist', + ); + + // Verify storage contract permissions + await expect( + incentivesPoolStorage + .connect(paranetOwner) + .setParanetIncentivesPool(ethers.ZeroAddress), + ).to.be.revertedWithCustomError(HubLib, 'UnauthorizedAccess'); + }); + + it('Should handle token emission multiplier updates correctly', async () => { + // 1. Setup paranet and pool + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const initialEmissionMultiplier = ethers.parseUnits('1', 12); + + // Deploy pool + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + initialEmissionMultiplier, + 1000, // 10% operator + 2000, // 20% voters + 'TestPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + const incentivesPoolStorage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event?.args[3], + ); + + // Get registrar (hub owner) + const registrar = await incentivesPoolStorage.votersRegistrar(); + const registrarSigner = await hre.ethers.getSigner(registrar); + + // Initiate multiplier update + const secondMultiplier = ethers.parseUnits('2', 12); // 2 NEURO per 1 TRAC + await expect( + incentivesPool + .connect(registrarSigner) + .initiateTokenEmissionMultiplierUpdate(secondMultiplier), + ) + .to.emit(incentivesPool, 'TokenEmissionMultiplierUpdateInitiated') + .withArgs( + initialEmissionMultiplier, + secondMultiplier, + (await time.latest()) + 7 * 24 * 3600 + 1, // Add 1 to account for the block being mined + ); + + // Try to finalize too early - should fail + await expect( + incentivesPool + .connect(registrarSigner) + .finalizeTokenEmissionMultiplierUpdate(), + ).to.be.revertedWith('Delay period not yet passed'); + + // Move time forward 7 days + await time.increase(7 * 24 * 3600); + + // Finalize update + await expect( + incentivesPool + .connect(registrarSigner) + .finalizeTokenEmissionMultiplierUpdate(), + ) + .to.emit(incentivesPool, 'TokenEmissionMultiplierUpdateFinalized') + .withArgs(initialEmissionMultiplier, secondMultiplier); + + // Verify new multiplier is active + expect( + await incentivesPool.getEffectiveTokenEmissionMultiplier( + await time.latest(), + ), + ).to.equal(secondMultiplier); + + // Additional checks for emission multiplier updates + // Check multiple updates in sequence + const thirdMultiplier = ethers.parseUnits('3', 12); + await incentivesPool + .connect(registrarSigner) + .initiateTokenEmissionMultiplierUpdate(thirdMultiplier); + + // Verify pending update state + const multipliers = await incentivesPool.gettokenEmissionMultipliers(); + expect(multipliers[multipliers.length - 1].multiplier).to.equal( + thirdMultiplier, + ); + expect(multipliers[multipliers.length - 1].finalized).to.equal(false); + + // Try to initiate another update while one is pending - should update the pending one + const fourthMultiplier = ethers.parseUnits('4', 12); + await incentivesPool + .connect(registrarSigner) + .initiateTokenEmissionMultiplierUpdate(fourthMultiplier); + const updatedMultipliers = + await incentivesPool.gettokenEmissionMultipliers(); + expect( + updatedMultipliers[updatedMultipliers.length - 1].multiplier, + ).to.equal(fourthMultiplier); + + // Move time forward 7 days + await time.increase(7 * 24 * 3600); + + // Finalize update + await expect( + incentivesPool + .connect(registrarSigner) + .finalizeTokenEmissionMultiplierUpdate(), + ) + .to.emit(incentivesPool, 'TokenEmissionMultiplierUpdateFinalized') + .withArgs(secondMultiplier, fourthMultiplier); // Didn't finalize the third multiplier + + // Verify new multiplier is active + expect( + await incentivesPool.getEffectiveTokenEmissionMultiplier( + await time.latest(), + ), + ).to.equal(fourthMultiplier); + + // Should allow hub owner to update token emission multiplier delay + const hubContract = await hre.ethers.getContractAt( + 'Hub', + await incentivesPool.hub(), + ); + const hubOwner = await hubContract.owner(); + const hubOwnerSigner = await hre.ethers.getSigner(hubOwner); + + // Update delay as hub owner + const newDelay = 14 * 24 * 60 * 60; // 14 days + await incentivesPool + .connect(hubOwnerSigner) + .updatetokenEmissionMultiplierUpdateDelay(newDelay); + expect( + await incentivesPool.tokenEmissionMultiplierUpdateDelay(), + ).to.equal(newDelay); + + // Try to update delay as non-hub owner + const nonOwner = accounts[9]; + await expect( + incentivesPool + .connect(nonOwner) + .updatetokenEmissionMultiplierUpdateDelay(newDelay), + ).to.be.revertedWith('Fn can only be used by hub owner'); + }); + }); + + describe('Paranet Incentives Pool Rewards', () => { + it('Should deploy pool, add KCs, fund pool and claim rewards correctly', async () => { + // 1. Setup paranet first + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + paranetId, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy incentives pool + const tracToNeuroEmissionMultiplier = ethers.parseUnits('5', 12); // 5 NEURO per 1 TRAC + const operatorRewardPercentage = 1000; // 10% + const votersRewardPercentage = 2000; // 20% + + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + tracToNeuroEmissionMultiplier, + operatorRewardPercentage, + votersRewardPercentage, + 'TestPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const poolStorageAddress = event?.args[3]; + const poolAddress = event?.args[4]; + + // 3. Create and submit knowledge collections to paranet + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Fund the incentives pool with tokens + const fundAmount = ethers.parseUnits('1000', 12); // 1000 tokens + await Token.connect(accounts[0]).mint(poolStorageAddress, fundAmount); + + // 5. Get pool contracts + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + poolAddress, + ); + const incentivesPoolStorage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + poolStorageAddress, + ); + + // 6. Check initial balances + expect(await incentivesPoolStorage.getBalance()).to.equal(fundAmount); + expect(await incentivesPoolStorage.totalMinersclaimedToken()).to.equal(0); + expect(await incentivesPoolStorage.totalOperatorsclaimedToken()).to.equal( + 0, + ); + expect(await incentivesPoolStorage.totalVotersclaimedToken()).to.equal(0); + + // 6.1. Check claim subfunctions - Not necessary for the claim flow + // Check all percentage getters + const operatorPercentage = + await incentivesPoolStorage.paranetOperatorRewardPercentage(); + const votersPercentage = + await incentivesPoolStorage.paranetIncentivizationProposalVotersRewardPercentage(); + + expect(operatorPercentage).to.equal(operatorRewardPercentage); // 10% + expect(votersPercentage).to.equal(votersRewardPercentage); // 20% + + // Calculate miners percentage (should be 70%) + const minersPercentage = + BigInt(10 ** 4) - operatorPercentage - votersPercentage; + expect(minersPercentage).to.equal(7000); // 70% + + expect(await incentivesPoolStorage.paranetId()).to.equal(paranetId); + + // Check effective token emission multiplier + const effectiveMultiplier = + await incentivesPool.getEffectiveTokenEmissionMultiplier( + await hre.ethers.provider + .getBlock('latest') + .then((b) => b!.timestamp), + ); + expect(effectiveMultiplier).to.equal(ethers.parseUnits('5', 12)); + + // Check unrewarded TRAC spent + const unrewardedTracSpent = + await ParanetKnowledgeMinersRegistry.getUnrewardedTracSpent( + kcCreator.address, + await incentivesPoolStorage.paranetId(), + ); + expect(unrewardedTracSpent).to.be.gt(10 ** 6); + + // Calculate expected reward manually + const expectedReward = + (((BigInt(unrewardedTracSpent) * effectiveMultiplier) / + BigInt(10 ** 18)) * + BigInt(minersPercentage)) / + BigInt(10 ** 4); + + // Compare with contract calculation + const actualReward = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(actualReward).to.equal(expectedReward); + expect(actualReward).to.be.gt(0); + + const claimableMinerRewardAmount = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + + const newUnrewardedTracSpent = + claimableMinerRewardAmount == actualReward + ? 0 + : (BigInt(actualReward - claimableMinerRewardAmount) * + BigInt(10 ** 18)) / + BigInt(await incentivesPoolStorage.getClaimedMinerRewardsLength()); + + expect(newUnrewardedTracSpent).to.equal(0); + + expect( + await incentivesPoolStorage.getClaimedMinerRewardsLength(), + ).to.equal(0); + + // 7. Claim miner rewards + const minerRewardEstimate = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate).to.be.gt(0); + + const claimableMinerReward = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + + await incentivesPool + .connect(kcCreator) + .claimKnowledgeMinerReward(claimableMinerReward); + + // 8. Verify miner rewards claimed correctly + // Test getting all rewarded miners/operators + const allMiners = await incentivesPoolStorage.getAllRewardedMiners(); + expect(allMiners.length).to.equal(1); + + // Test claimed token queries + expect( + await incentivesPoolStorage.minerclaimedToken(kcCreator.address), + ).to.equal(claimableMinerReward); + + expect( + await incentivesPoolStorage + .connect(kcCreator) + .totalMinersclaimedToken(), + ).to.equal(claimableMinerReward); + const minerProfile = + await incentivesPoolStorage.getClaimedMinerRewardsAtIndex( + await incentivesPoolStorage.claimedMinerRewardsIndexes( + kcCreator.address, + ), + ); + expect(minerProfile.addr).to.equal(kcCreator.address); + expect(minerProfile.claimedToken).to.equal(claimableMinerReward); + + // 9. Claim operator rewards + const operatorRewardEstimate = + await incentivesPool.getTotalParanetOperatorIncentiveEstimation(); + expect(operatorRewardEstimate).to.be.gt(0); + + const claimableOperatorReward = + await incentivesPool.getClaimableParanetOperatorRewardAmount(); + await incentivesPool.connect(paranetOwner).claimParanetOperatorReward(); + + // 10. Verify operator rewards claimed correctly + const allOperators = + await incentivesPoolStorage.getAllRewardedOperators(); + expect(allOperators.length).to.equal(1); + + expect( + await incentivesPoolStorage.operatorclaimedToken(paranetOwner.address), + ).to.equal(claimableOperatorReward); + + expect(await incentivesPoolStorage.totalOperatorsclaimedToken()).to.equal( + claimableOperatorReward, + ); + const operatorProfile = + await incentivesPoolStorage.getClaimedOperatorRewardsAtIndex( + await incentivesPoolStorage.claimedOperatorRewardsIndexes( + paranetOwner.address, + ), + ); + expect(operatorProfile.addr).to.equal(paranetOwner.address); + expect(operatorProfile.claimedToken).to.equal(claimableOperatorReward); + + // 11. Verify final pool balance + const expectedRemainingBalance = + fundAmount - claimableMinerReward - claimableOperatorReward; + expect(await incentivesPoolStorage.getBalance()).to.equal( + expectedRemainingBalance, + ); + }); + + it('Should fail to claim rewards when pool has no funds', async () => { + // 1. Setup paranet first + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + paranetId, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // Deploy pool without funding it + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), // 1 NEURO per 1 TRAC + 1000, // 10% operatorRewardPercentage + 2000, // 20% votersRewardPercentage + 'TestPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + + // Create and submit knowledge collections to paranet + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + expect( + await ParanetsRegistry.isKnowledgeMinerRegistered( + paranetId, + kcCreator.address, + ), + ).to.be.equal(true); + + // Verify rewards are calculated for the miner + const minerRewardEstimate = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate).to.be.gt(0); + + const claimableMinerReward = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + expect(claimableMinerReward).to.equal(0); + + // Try to claim miner rewards + await expect( + incentivesPool.connect(kcCreator).claimKnowledgeMinerReward(0), + ).to.be.revertedWithCustomError(incentivesPool, 'NoRewardAvailable'); + + // Verify rewards are calculated for the operator + const operatorRewardEstimate = await incentivesPool + .connect(paranetOwner) + .getTotalParanetOperatorIncentiveEstimation(); + expect(operatorRewardEstimate).to.be.gt(0); + + const claimableOperatorReward = + await incentivesPool.getClaimableParanetOperatorRewardAmount(); + expect(claimableOperatorReward).to.equal(0); + + // Try to claim operator rewards + await expect( + incentivesPool.connect(paranetOwner).claimParanetOperatorReward(), + ).to.be.revertedWithCustomError(incentivesPool, 'NoRewardAvailable'); + }); + + it('Should handle incentives pool with zero rewards available', async () => { + // 1. Setup paranet and create KC + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy incentives pool (but don't fund it) + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), + 1000, + 2000, + 'EmptyPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 3. Create and submit KC to generate some rewards + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Try to claim rewards from empty pool + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + + // Verify rewards are calculated but not claimable + const minerRewardEstimate = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate).to.be.gt(0); + + const claimableMinerReward = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + expect(claimableMinerReward).to.equal(0); + + // Try to claim - should fail + await expect( + incentivesPool.connect(kcCreator).claimKnowledgeMinerReward(0), + ).to.be.revertedWithCustomError(incentivesPool, 'NoRewardAvailable'); + }); + + it('Should handle incentives pool with partial funding', async () => { + // 1. Setup paranet and create KC + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy incentives pool + const tx = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), + 1000, + 2000, + 'PartialPool', + await Token.getAddress(), + ); + + const receipt = await tx.wait(); + const event = receipt!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 3. Create and submit KC to generate rewards + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + const incentivesPool = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event?.args[4], + ); + + // 4. Fund pool with partial miner reward amount + const minerRewardEstimate = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + const partialFunding = minerRewardEstimate / BigInt(2); // Fund 50% of estimated rewards + + await Token.connect(accounts[0]).mint(event?.args[3], partialFunding); + + // 5. Verify partial rewards are claimable + const claimableMinerReward = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + expect(claimableMinerReward).to.be.lessThanOrEqual(partialFunding); + + // 6. Claim partial rewards + await incentivesPool + .connect(kcCreator) + .claimKnowledgeMinerReward(claimableMinerReward); + + // 7. Verify remaining rewards are still tracked but not claimable + const remainingEstimate = await incentivesPool + .connect(kcCreator) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(remainingEstimate).to.be.lessThanOrEqual( + minerRewardEstimate - partialFunding, + ); + + const remainingClaimable = await incentivesPool + .connect(kcCreator) + .getClaimableKnowledgeMinerRewardAmount(); + expect(remainingClaimable).to.equal(0); + }); + + it('Should handle claiming rewards from multiple incentives pools', async () => { + // 1. Setup paranet with initial configuration + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + // paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy two incentive pools with different parameters + const tx1 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), // 1 NEURO per 1 TRAC + 1000, // 10% operator + 2000, // 20% voters + 'Pool1', + await Token.getAddress(), + ); + const receipt1 = await tx1.wait(); + const event1 = receipt1!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const tx2 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('2', 12), // 2 NEURO per 1 TRAC + 1500, // 15% operator + 2500, // 25% voters + 'Pool2', + await Token.getAddress(), + ); + const receipt2 = await tx2.wait(); + const event2 = receipt2!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 3. Fund both pools + const pool1Amount = ethers.parseUnits('100', 18); // 100 NEURO + const pool2Amount = ethers.parseUnits('200', 18); // 200 NEURO + await Token.connect(paranetOwner).approve(event1?.args[3], pool1Amount); + await Token.connect(paranetOwner).approve(event2?.args[3], pool2Amount); + + const pool1Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event1?.args[3], + ); + const pool1 = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event1?.args[4], + ); + + const pool2Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event2?.args[3], + ); + const pool2 = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event2?.args[4], + ); + + await Token.connect(accounts[0]).mint(pool1Storage, pool1Amount); + await Token.connect(accounts[0]).mint(pool2Storage, pool2Amount); + + expect(await pool1Storage.getBalance()).to.equal(pool1Amount); + expect(await pool1Storage.totalMinersclaimedToken()).to.equal(0); + expect(await pool1Storage.totalOperatorsclaimedToken()).to.equal(0); + expect(await pool1Storage.totalVotersclaimedToken()).to.equal(0); + + expect(await pool2Storage.getBalance()).to.equal(pool2Amount); + expect(await pool2Storage.totalMinersclaimedToken()).to.equal(0); + expect(await pool2Storage.totalOperatorsclaimedToken()).to.equal(0); + expect(await pool2Storage.totalVotersclaimedToken()).to.equal(0); + + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + + const miner = accounts[100]; + const { collectionId } = await createKnowledgeCollection( + miner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(miner).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 5. Claim rewards from first pool + const initialMinerBalance = await Token.balanceOf( + await miner.getAddress(), + ); + const minerRewardEstimate1 = await pool1 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate1).to.be.gt(0); + + await pool1 + .connect(miner) + .claimKnowledgeMinerReward(minerRewardEstimate1); + // 6. Verify first pool is empty and second pool still has funds + expect(await Token.balanceOf(await miner.getAddress())).to.be.equal( + initialMinerBalance + minerRewardEstimate1, + ); + expect(await Token.balanceOf(event1?.args[3])).to.equal( + pool1Amount - minerRewardEstimate1, + ); + expect(await Token.balanceOf(event2?.args[3])).to.equal(pool2Amount); + + // 7. Claim rewards from second pool + const minerRewardEstimate0 = await pool2 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate0).to.be.equal(0); + + await expect( + pool2.connect(miner).claimKnowledgeMinerReward(minerRewardEstimate0), + ).to.be.revertedWithCustomError(pool2, 'NoRewardAvailable'); + + expect(await Token.balanceOf(await miner.getAddress())).to.equal( + initialMinerBalance + minerRewardEstimate1, + ); + expect(await Token.balanceOf(pool2Storage)).to.equal(pool2Amount); + + const { collectionId: collectionId2 } = await createKnowledgeCollection( + miner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(miner).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId2, + ); + const initialMinerBalance2 = await Token.balanceOf( + await miner.getAddress(), + ); + const minerRewardEstimate2 = await pool1 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate2).to.be.gt(0); + + await pool2 + .connect(miner) + .claimKnowledgeMinerReward(minerRewardEstimate2); + + // 8. Verify balances is updated + expect(await Token.balanceOf(pool1Storage)).to.equal( + pool1Amount - minerRewardEstimate1, + ); + expect(await Token.balanceOf(pool2Storage)).to.equal( + pool2Amount - minerRewardEstimate2, + ); + expect(await Token.balanceOf(await miner.getAddress())).to.equal( + initialMinerBalance2 + minerRewardEstimate2, + ); + }); + + it('Should handle claiming rewards from multiple incentives pools with native token', async () => { + // 1. Setup paranet with initial configuration + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Deploy two incentive pools with different parameters using address(0) for native token + const tx1 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('1', 12), // 1 ETH per 1 TRAC + 1000, // 10% operator + 2000, // 20% voters + 'Pool1', + ethers.ZeroAddress, // Use address(0) for native token + ); + const receipt1 = await tx1.wait(); + const event1 = receipt1!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + const tx2 = await ParanetIncentivesPoolFactory.connect( + paranetOwner, + ).deployIncentivesPool( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + ethers.parseUnits('2', 12), // 2 ETH per 1 TRAC + 1500, // 15% operator + 2500, // 25% voters + 'Pool2', + ethers.ZeroAddress, // Use address(0) for native token + ); + const receipt2 = await tx2.wait(); + const event2 = receipt2!.logs.find( + (log) => + log.topics[0] === + ParanetIncentivesPoolFactory.interface.getEvent( + 'ParanetIncentivesPoolDeployed', + ).topicHash, + ) as EventLog; + + // 3. Fund both pools with ETH + const pool1Amount = ethers.parseEther('100'); // 100 ETH + const pool2Amount = ethers.parseEther('200'); // 200 ETH + + const pool1Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event1?.args[3], + ); + const pool1 = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event1?.args[4], + ); + + const pool2Storage = await hre.ethers.getContractAt( + 'ParanetIncentivesPoolStorage', + event2?.args[3], + ); + const pool2 = await hre.ethers.getContractAt( + 'ParanetIncentivesPool', + event2?.args[4], + ); + + // Send ETH to pools + await paranetOwner.sendTransaction({ + to: pool1Storage.target, + value: pool1Amount, + }); + await paranetOwner.sendTransaction({ + to: pool2Storage.target, + value: pool2Amount, + }); + + // Verify initial balances + expect( + await hre.ethers.provider.getBalance(pool1Storage.target), + ).to.equal(pool1Amount); + expect(await pool1Storage.totalMinersclaimedToken()).to.equal(0); + expect(await pool1Storage.totalOperatorsclaimedToken()).to.equal(0); + expect(await pool1Storage.totalVotersclaimedToken()).to.equal(0); + + expect( + await hre.ethers.provider.getBalance(pool2Storage.target), + ).to.equal(pool2Amount); + expect(await pool2Storage.totalMinersclaimedToken()).to.equal(0); + expect(await pool2Storage.totalOperatorsclaimedToken()).to.equal(0); + expect(await pool2Storage.totalVotersclaimedToken()).to.equal(0); + + // 4. Create and submit knowledge collection + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + + const miner = accounts[100]; + const { collectionId } = await createKnowledgeCollection( + miner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(miner).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 5. Claim rewards from first pool + const initialMinerBalance = await hre.ethers.provider.getBalance( + miner.address, + ); + const minerRewardEstimate1 = await pool1 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate1).to.be.gt(0); + + const claimTx1 = await pool1 + .connect(miner) + .claimKnowledgeMinerReward(minerRewardEstimate1); + const receipt3 = await claimTx1.wait(); + const gasCost1 = receipt3!.gasUsed * receipt3!.gasPrice; + + // 6. Verify first pool balance and miner received ETH + const newMinerBalance = await hre.ethers.provider.getBalance( + miner.address, + ); + expect(newMinerBalance).to.be.equal( + initialMinerBalance + minerRewardEstimate1 - gasCost1, + ); + expect( + await hre.ethers.provider.getBalance(pool1Storage.target), + ).to.equal(pool1Amount - minerRewardEstimate1); + expect( + await hre.ethers.provider.getBalance(pool2Storage.target), + ).to.equal(pool2Amount); + + // 7. Try claiming from second pool (should fail as no rewards available yet) + const minerRewardEstimate0 = await pool2 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate0).to.be.equal(0); + + await expect( + pool2.connect(miner).claimKnowledgeMinerReward(minerRewardEstimate0), + ).to.be.revertedWithCustomError(pool2, 'NoRewardAvailable'); + + await expect( + pool1.connect(miner).claimKnowledgeMinerReward(minerRewardEstimate0), + ).to.be.revertedWithCustomError(pool1, 'NoRewardAvailable'); + + // 8. Submit second knowledge collection + const { collectionId: collectionId2 } = await createKnowledgeCollection( + miner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection, + Token, + }, + ); + + await Paranet.connect(miner).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId2, + ); + + // 9. Claim rewards from second pool + const initialMinerBalance2 = await hre.ethers.provider.getBalance( + miner.address, + ); + const minerRewardEstimate2 = await pool2 + .connect(miner) + .getTotalKnowledgeMinerIncentiveEstimation(); + expect(minerRewardEstimate2).to.be.gt(0); + + const claimTx2 = await pool2 + .connect(miner) + .claimKnowledgeMinerReward(minerRewardEstimate2); + const receipt4 = await claimTx2.wait(); + const gasCost2 = receipt4!.gasUsed * receipt4!.gasPrice; + + // 10. Verify final balances + expect( + await hre.ethers.provider.getBalance(pool1Storage.target), + ).to.equal(pool1Amount - minerRewardEstimate1); + expect( + await hre.ethers.provider.getBalance(pool2Storage.target), + ).to.equal(pool2Amount - minerRewardEstimate2); + expect(await hre.ethers.provider.getBalance(miner.address)).to.equal( + initialMinerBalance2 + minerRewardEstimate2 - gasCost2, + ); + }); + }); + + describe('Submit Knowledge Collection to Paranet', () => { + it('Should submit knowledge collection to paranet successfully', async () => { + // 1. Setup paranet first + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + // paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + // 3. Submit knowledge collection to paranet + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Verify submission + // Check if KC is registered in paranet + const isRegistered = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + paranetId, + ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ), + ); + + expect(isRegistered).to.be.equal(true); + + // Check if KC is in miner's submitted collections + const submittedCollections = + await ParanetKnowledgeMinersRegistry.getSubmittedKnowledgeCollections( + kcCreator.address, + paranetId, + ); + expect(submittedCollections).to.include( + ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ), + ); + + // Check miner's stats were updated + const minerMetadata = + await ParanetKnowledgeMinersRegistry.getKnowledgeMinerMetadata( + kcCreator.address, + ); + expect(minerMetadata.totalSubmittedKnowledgeCollectionsCount).to.equal(1); + + // Check if TRAC amounts were tracked correctly + const remainingTokenAmount = + await KnowledgeCollectionStorage.getTokenAmount(collectionId); + + // Check cumulative TRAC spent + const cumulativeTracSpent = + await ParanetKnowledgeMinersRegistry.getCumulativeTracSpent( + kcCreator.address, + paranetId, + ); + expect(cumulativeTracSpent).to.equal(remainingTokenAmount); + + // Check unrewarded TRAC spent + const unrewardedTracSpent = + await ParanetKnowledgeMinersRegistry.getUnrewardedTracSpent( + kcCreator.address, + paranetId, + ); + expect(unrewardedTracSpent).to.equal(remainingTokenAmount); + + // Check total TRAC spent + const totalTracSpent = + await ParanetKnowledgeMinersRegistry.getTotalTracSpent( + kcCreator.address, + ); + expect(totalTracSpent).to.equal(remainingTokenAmount); + + // Check paranet's cumulative knowledge value + const paranetMetadata = + await ParanetsRegistry.getParanetMetadata(paranetId); + expect(paranetMetadata.cumulativeKnowledgeValue).to.equal( + remainingTokenAmount, + ); + + const signaturesData2 = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: collectionId2 } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData2, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // Verify event emission + await expect( + Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId2, + ), + ) + .to.emit(Paranet, 'KnowledgeCollectionSubmittedToParanet') + .withArgs( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId2, + ); + }); + + it('Should revert when non-owner tries to submit knowledge collection', async () => { + // 1. Setup paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonOwner = accounts[10]; + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a knowledge collection + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Try to submit KC with non-owner account + await expect( + Paranet.connect(nonOwner).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ), + ).to.be.revertedWith("Caller isn't the owner of the KC"); + }); + + it('Should revert when registering the same knowledge collection twice', async () => { + // 1. Setup paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a knowledge collection + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + await Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Try to submit the same knowledge collection again + await expect( + Paranet.connect(kcCreator).submitKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ), + ).to.be.revertedWithCustomError( + Paranet, + 'KnowledgeCollectionIsAPartOfOtherParanet', + ); + }); + + it('Should not allow submitting KC to second paranet after first', async () => { + // 1. Setup first paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const firstParanet = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'First Paranet', + 'First Paranet Description', + ); + + // 2. Setup second paranet + + const signaturesData2 = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + + const { collectionId: secondParanetCollectionId } = + await createKnowledgeCollection( + kcCreator, + firstParanet.publishingNodeIdentityId, + firstParanet.receivingNodesIdentityIds, + signaturesData2, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + await Paranet.connect(kcCreator).registerParanet( + await KnowledgeCollectionStorage.getAddress(), + secondParanetCollectionId, + 1, + 'Second Paranet', + 'Second Paranet Description', + 0, + 0, + 0, + ); + + // 3. Create a knowledge collection + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + firstParanet.publishingNodeIdentityId, + firstParanet.receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 4. Submit KC to first paranet + await Paranet.connect(kcCreator).submitKnowledgeCollection( + firstParanet.paranetKCStorageContract, + firstParanet.paranetKCTokenId, + firstParanet.paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 5. Try to submit the same KC to second paranet + await expect( + Paranet.connect(kcCreator).submitKnowledgeCollection( + await KnowledgeCollectionStorage.getAddress(), + secondParanetCollectionId, + 1, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ), + ).to.be.revertedWithCustomError( + Paranet, + 'KnowledgeCollectionIsAPartOfOtherParanet', + ); + + // 6. Verify KC is only registered in first paranet + const knowledgeCollectionId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ); + + const isRegisteredInFirstParanet = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + firstParanet.paranetId, + knowledgeCollectionId, + ); + expect(isRegisteredInFirstParanet).to.be.equal(true); + + const secondParanetId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + secondParanetCollectionId, + 1, + ], + ), + ); + const isRegisteredInSecondParanet = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + secondParanetId, + knowledgeCollectionId, + ); + expect(isRegisteredInSecondParanet).to.be.equal(false); + }); + }); + + describe('Paranet with Staged Knowledge Collections', () => { + const KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING = 1; + + it('Should create paranet with staging policy and add curator successfully', async () => { + // 1. Setup initial accounts + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + // 2. Create paranet with staging policy + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 3. Add paranet owner as curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner.address, + ); + + // 4. Verify curator was added correctly + const isCurator = await ParanetStagingRegistry.isCurator( + paranetId, + paranetOwner.address, + ); + expect(isCurator).to.be.equal(true); + + // 5. Verify paranet metadata + const paranetMetadata = + await ParanetsRegistry.getParanetMetadata(paranetId); + expect(paranetMetadata.knowledgeCollectionsSubmissionPolicy).to.equal( + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 6. Get all curators and verify + const curators = + await ParanetStagingRegistry.getAllParanetCurators(paranetId); + expect(curators).to.have.lengthOf(1); + expect(curators[0]).to.equal(paranetOwner.address); + }); + + it('Should revert when non-owner tries to add curator', async () => { + // 1. Setup initial accounts + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonOwner = accounts[10]; + + // 2. Create paranet with staging policy + const { paranetKCStorageContract, paranetKCTokenId, paranetKATokenId } = + await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 3. Try to add curator with non-owner account + await expect( + Paranet.connect(nonOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + nonOwner.address, + ), + ).to.be.revertedWith("Caller isn't the owner of the KA"); + }); + + it('Should submit KC to staging and have curator approve it', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 2. Add paranet owner as curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner.address, + ); + + // 4. Submit KC to paranet (goes to staging) + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + // 3. Submit knowledge collection to paranet + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 5. Verify KC is in staging + const knowledgeCollectionId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ); + + const stagedStatus = + await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(stagedStatus).to.equal(1); // PENDING + + const isStaged = await ParanetStagingRegistry.isKnowledgeCollectionStaged( + paranetId, + knowledgeCollectionId, + ); + expect(isStaged).to.be.equal(true); + + // 6. Have curator approve the KC + await Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetKCStorageContract, + collectionId, + true, // approve + ); + + // 7. Verify KC is now registered in paranet + const isRegistered = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + paranetId, + knowledgeCollectionId, + ); + expect(isRegistered).to.be.equal(true); + + const stagedStatusAfterApproval = + await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(stagedStatusAfterApproval).to.equal(2); // APPROVED + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [pendingCollections, total] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(total).to.be.equal(0); + }); + + it('Should submit KC to staging and have curator reject it', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 2. Add paranet owner as curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner.address, + ); + + // 3. Create and submit KC to paranet staging + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Get knowledge collection ID + const knowledgeCollectionId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ); + + // 5. Have curator reject the KC + await Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + false, // reject + ); + + // 6. Verify KC was rejected and not registered in paranet + const isRegistered = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + paranetId, + knowledgeCollectionId, + ); + expect(isRegistered).to.be.equal(false); + + const stagedStatusAfterRejection = + await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(stagedStatusAfterRejection).to.equal(3); // REJECTED + + // 7. Verify KC is still staged but marked as rejected + const isStaged = await ParanetStagingRegistry.isKnowledgeCollectionStaged( + paranetId, + knowledgeCollectionId, + ); + expect(isStaged).to.be.equal(false); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [pendingCollectionsAfterRejection, totalAfterRejection] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(totalAfterRejection).to.be.equal(0); + }); + + it('Should allow resubmission of previously rejected Knowledge Collection', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + KNOWLEDGE_COLLECTIONS_SUBMISSION_POLICY_STAGING, + ); + + // 2. Add paranet owner as curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner.address, + ); + + // 3. Create and submit KC to paranet staging + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 4. First submission - Stage the Knowledge Collection + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + const knowledgeCollectionId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ); + + // 5. Verify it's in pending state + let [pendingCollections, total] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(total).to.equal(1); + expect(pendingCollections[0].knowledgeCollectionId).to.equal( + knowledgeCollectionId, + ); + expect(pendingCollections[0].status).to.equal(1); // PENDING + + // 6. Have curator reject the KC + await Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + false, // reject + ); + + // 7. Verify rejection status (???) + let status = await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(status).to.equal(3); // REJECTED + + [pendingCollections, total] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(total).to.equal(0); + expect(pendingCollections).to.have.lengthOf(0); + + // 8. Resubmit the Knowledge Collection + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 9. Verify it's back in pending state + [pendingCollections, total] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(total).to.equal(1); + expect(pendingCollections[0].knowledgeCollectionId).to.equal( + knowledgeCollectionId, + ); + expect(pendingCollections[0].status).to.equal(1); // PENDING + + // 10. Have curator approve the KC this time + await Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + true, // approve + ); + + // 11. Verify final approval status + status = await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(status).to.equal(2); // APPROVED + + // 12. Verify KC is now registered in paranet + const isRegistered = + await ParanetsRegistry.isKnowledgeCollectionRegistered( + paranetId, + knowledgeCollectionId, + ); + expect(isRegistered).to.be.equal(true); + + // 13. Verify no more pending collections + [pendingCollections, total] = + await ParanetStagingRegistry.getPendingCollections(paranetId, 0, 10); + expect(total).to.equal(0); + expect(pendingCollections).to.have.lengthOf(0); + }); + + it('Should not allow approving a rejected knowledge collection', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + 1, // STAGING policy + ); + + // 2. Add paranet owner as curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetOwner.address, + ); + + // 3. Create and submit KC to paranet staging + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 4. Stage the Knowledge Collection + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + const knowledgeCollectionId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256'], + [await KnowledgeCollectionStorage.getAddress(), collectionId], + ), + ); + + // 5. Reject the KC + await Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + false, // reject + ); + + // 6. Verify rejection status + let status = await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(status).to.equal(3); // Still REJECTED + + // 7. Try to approve the rejected KC (should fail) + await expect( + Paranet.connect(paranetOwner).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + true, // try to approve + ), + ).to.be.revertedWith('Knowledge collection is not staged'); + + // 8. Verify status remains rejected + status = await ParanetStagingRegistry.getKnowledgeCollectionStatus( + paranetId, + knowledgeCollectionId, + ); + expect(status).to.equal(3); // Still REJECTED + }); + + it('Should revert when non-curator tries to review collection', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator = accounts[10]; + const nonCurator = accounts[11]; + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + 1, // STAGING policy + ); + + // 2. Add curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ); + + // 3. Create and submit KC to paranet staging + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. Try to review with non-curator account + await expect( + Paranet.connect(nonCurator).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + true, // approve + ), + ).to.be.revertedWith('Not authorized curator'); + }); + + it('Should revert when reviewing already reviewed collection', async () => { + // 1. Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator = accounts[10]; + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + 1, // STAGING policy + ); + + // 2. Add curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ); + + // 3. Create and submit KC to paranet staging + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId } = await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + await Paranet.connect(kcCreator).stageKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + ); + + // 4. First review (approve) + await Paranet.connect(curator).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + false, // rejected + ); + + // 5. Try to review again + await expect( + Paranet.connect(curator).reviewKnowledgeCollection( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + collectionId, + true, // approve + ), + ).to.be.revertedWith('Knowledge collection is not staged'); + }); + }); + + describe('Paranet Curator Management', () => { + it('Should add and remove a single curator', async () => { + // Setup paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator = accounts[10]; + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + 1, // STAGING policy + ); + + // Add curator + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ); + + // Verify curator was added + expect( + await ParanetStagingRegistry.isCurator(paranetId, curator.address), + ).to.be.equal(true); + let curators = + await ParanetStagingRegistry.getAllParanetCurators(paranetId); + expect(curators).to.have.lengthOf(1); + expect(curators[0]).to.equal(curator.address); + + // Remove curator + await Paranet.connect(paranetOwner).removeCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ); + + // Verify curator was removed + expect( + await ParanetStagingRegistry.isCurator(paranetId, curator.address), + ).to.be.equal(false); + curators = await ParanetStagingRegistry.getAllParanetCurators(paranetId); + expect(curators).to.have.lengthOf(0); + }); + + it('Should handle multiple curators and removal of middle curator', async () => { + // Setup paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator1 = accounts[10]; + const curator2 = accounts[11]; + const curator3 = accounts[12]; + + const { + paranetOwner, + paranetId, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, + 0, + 1, // STAGING policy + ); + + // Add three curators + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator1.address, + ); + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator2.address, + ); + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator3.address, + ); + + // Verify all curators were added + let curators = + await ParanetStagingRegistry.getAllParanetCurators(paranetId); + expect(curators).to.have.lengthOf(3); + expect(curators).to.include(curator1.address); + expect(curators).to.include(curator2.address); + expect(curators).to.include(curator3.address); + + // Remove middle curator + await Paranet.connect(paranetOwner).removeCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator2.address, + ); + + // Verify correct curator was removed and others remain + expect( + await ParanetStagingRegistry.isCurator(paranetId, curator1.address), + ).to.be.equal(true); + expect( + await ParanetStagingRegistry.isCurator(paranetId, curator2.address), + ).to.be.equal(false); + expect( + await ParanetStagingRegistry.isCurator(paranetId, curator3.address), + ).to.be.equal(true); + + curators = await ParanetStagingRegistry.getAllParanetCurators(paranetId); + expect(curators).to.have.lengthOf(2); + expect(curators).to.include(curator1.address); + expect(curators).to.include(curator3.address); + expect(curators).to.not.include(curator2.address); + }); + + it('Should revert when adding curator to non-staging paranet', async () => { + // Setup paranet with OPEN submission policy (0) + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator = accounts[10]; + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, // nodesAccessPolicy + 0, // minersAccessPolicy + 0, // OPEN submission policy + ); + + // Attempt to add curator - should fail + await expect( + Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ), + ).to.be.revertedWith('Paranet does not allow adding curators'); + }); + + it('Should revert when adding same curator twice', async () => { + // Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const curator = accounts[10]; + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, // nodesAccessPolicy + 0, // minersAccessPolicy + 1, // STAGING policy + ); + + // Add curator first time + await Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ); + + // Try to add same curator again - should fail + await expect( + Paranet.connect(paranetOwner).addCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + curator.address, + ), + ).to.be.revertedWith('Existing curator'); + }); + + it('Should revert when removing non-existent curator', async () => { + // Setup paranet with staging policy + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonExistentCurator = accounts[10]; + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + } = await setupParanet( + kcCreator, + publishingNode, + receivingNodes, + { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }, + 'Test Paranet', + 'Test Paranet Description', + 0, // nodesAccessPolicy + 0, // minersAccessPolicy + 1, // STAGING policy + ); + + // Try to remove curator that was never added - should fail + await expect( + Paranet.connect(paranetOwner).removeCurator( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + nonExistentCurator.address, + ), + ).to.be.revertedWith('Address is not a curator'); + }); + }); + + describe('Paranet Service Registration', () => { + it('Should register a paranet service successfully', async () => { + // 1. Setup initial paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + paranetId, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + const tx = await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + // 5. Verify service registration + const serviceId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ], + ), + ); + + // Check if service exists + const serviceExists = + await ParanetServicesRegistry.paranetServiceExists(serviceId); + expect(serviceExists).to.be.equal(true); + + // Verify service metadata + const serviceMetadata = + await ParanetServicesRegistry.getParanetServiceMetadata(serviceId); + expect(serviceMetadata.name).to.equal(serviceName); + expect(serviceMetadata.description).to.equal(serviceDescription); + expect(serviceMetadata.paranetServiceAddresses).to.deep.equal( + serviceAddresses, + ); + expect(serviceMetadata.paranetServiceKCStorageContract).to.equal( + await KnowledgeCollectionStorage.getAddress(), + ); + expect(serviceMetadata.paranetServiceKCTokenId).to.equal( + serviceCollectionId, + ); + expect(serviceMetadata.paranetServiceKATokenId).to.equal(1); + + // Verify service addresses + const registeredAddresses = + await ParanetServicesRegistry.getParanetServiceAddresses(serviceId); + expect(registeredAddresses).to.deep.equal(serviceAddresses); + + // Verify individual service addresses are registered + for (const addr of serviceAddresses) { + const isRegistered = + await ParanetServicesRegistry.isParanetServiceAddressRegistered( + serviceId, + addr, + ); + expect(isRegistered).to.be.equal(true); + } + + // 6. Verify event emission + await expect(tx) + .to.emit(Paranet, 'ParanetServiceRegistered') + .withArgs( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + serviceName, + serviceDescription, + serviceAddresses, + ); + + // 7. Add paranet service to paranet + const addServiceTx = await Paranet.connect( + paranetOwner, + ).addParanetServices( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + [ + { + knowledgeCollectionStorageContract: + await KnowledgeCollectionStorage.getAddress(), + knowledgeCollectionTokenId: serviceCollectionId, + knowledgeAssetTokenId: 1, + }, + ], + ); + + // Verify service is implemented in paranet + const isImplemented = await ParanetsRegistry.isServiceImplemented( + paranetId, + serviceId, + ); + expect(isImplemented).to.be.equal(true); + + // Verify paranet services list + const paranetServices = await ParanetsRegistry.getServices(paranetId); + expect(paranetServices).to.include(serviceId); + expect(paranetServices).to.have.lengthOf(1); + + // Verify ParanetServiceAdded event + await expect(addServiceTx) + .to.emit(Paranet, 'ParanetServiceAdded') + .withArgs( + paranetKCStorageContract, + paranetKCTokenId, + paranetKATokenId, + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ); + }); + + it('Should revert when trying to register the same paranet service twice', async () => { + // 1. Setup initial paranet + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service first time + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + // 5. Attempt to register the same service again + await expect( + Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ), + ).to.be.revertedWithCustomError( + Paranet, + 'ParanetServiceHasAlreadyBeenRegistered', + ); + }); + }); + + describe('Paranet Service Metadata', () => { + it('Should update paranet service metadata successfully', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + const serviceId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ], + ), + ); + + const newName = 'New Service Name'; + const newDescription = 'New Service Description'; + const newServiceAddresses = [accounts[1].address, accounts[2].address]; + + await Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + newName, + newDescription, + newServiceAddresses, + ); + + expect(await ParanetServicesRegistry.getName(serviceId)).to.equal( + newName, + ); + expect(await ParanetServicesRegistry.getDescription(serviceId)).to.equal( + newDescription, + ); + expect( + await ParanetServicesRegistry.getParanetServiceAddresses(serviceId), + ).to.deep.equal(newServiceAddresses); + }); + + it('Should emit ParanetServiceMetadataUpdated event with correct parameters', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + const newName = 'New Service Name'; + const newDescription = 'New Service Description'; + const newServiceAddresses = [accounts[1].address, accounts[2].address]; + + await expect( + Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + newName, + newDescription, + newServiceAddresses, + ), + ) + .to.emit(Paranet, 'ParanetServiceMetadataUpdated') + .withArgs( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + newName, + newDescription, + newServiceAddresses, + ); + }); + + it('Should handle empty strings and empty address array', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + const serviceId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ], + ), + ); + + await Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + '', + '', + [], + ); + + expect(await ParanetServicesRegistry.getName(serviceId)).to.equal(''); + expect(await ParanetServicesRegistry.getDescription(serviceId)).to.equal( + '', + ); + expect( + await ParanetServicesRegistry.getParanetServiceAddresses(serviceId), + ).to.deep.equal([]); + }); + + it('Should handle very long name, description and large address array', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + const serviceId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ], + ), + ); + + const longName = 'a'.repeat(100); + const longDescription = 'b'.repeat(1000); + const manyAddresses = accounts + .slice(0, 20) + .map((account) => account.address); // 20 addresses + + await Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + longName, + longDescription, + manyAddresses, + ); + + expect(await ParanetServicesRegistry.getName(serviceId)).to.equal( + longName, + ); + expect(await ParanetServicesRegistry.getDescription(serviceId)).to.equal( + longDescription, + ); + expect( + await ParanetServicesRegistry.getParanetServiceAddresses(serviceId), + ).to.deep.equal(manyAddresses); + }); + + it('Should allow multiple updates to the same service', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + const serviceId = ethers.keccak256( + ethers.solidityPacked( + ['address', 'uint256', 'uint256'], + [ + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + ], + ), + ); + + // First update + await Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + 'First Name', + 'First Description', + [accounts[1].address], + ); + + // Second update + await Paranet.connect(paranetOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + 'Second Name', + 'Second Description', + [accounts[2].address, accounts[3].address], + ); + + expect(await ParanetServicesRegistry.getName(serviceId)).to.equal( + 'Second Name', + ); + expect(await ParanetServicesRegistry.getDescription(serviceId)).to.equal( + 'Second Description', + ); + expect( + await ParanetServicesRegistry.getParanetServiceAddresses(serviceId), + ).to.deep.equal([accounts[2].address, accounts[3].address]); + }); + + // Error cases + it('Should revert when non-owner tries to update service metadata', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + const nonOwner = accounts[10]; + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + await expect( + Paranet.connect(nonOwner).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, + 'New Name', + 'New Description', + [accounts[1].address], + ), + ).to.be.revertedWith("Caller isn't the owner of the KA"); + }); + + it('Should revert when service does not exist', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { + paranetOwner, + publishingNodeIdentityId, + receivingNodesIdentityIds, + } = await setupParanet(kcCreator, publishingNode, receivingNodes, { + Paranet, + Profile, + Token, + KnowledgeCollection, + KnowledgeCollectionStorage, + }); + + // 2. Create a new knowledge collection for the service + const signaturesData = await getKCSignaturesData( + publishingNode, + 1, + receivingNodes, + ); + const { collectionId: serviceCollectionId } = + await createKnowledgeCollection( + kcCreator, + publishingNodeIdentityId, + receivingNodesIdentityIds, + signaturesData, + { + KnowledgeCollection: KnowledgeCollection, + Token: Token, + }, + ); + + // 3. Setup service parameters + const serviceName = 'Test Service'; + const serviceDescription = 'Test Service Description'; + const serviceAddresses = [accounts[10].address, accounts[11].address]; + + // 4. Register the service + await Paranet.connect(paranetOwner).registerParanetService( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 1, // serviceKATokenId + serviceName, + serviceDescription, + serviceAddresses, + ); + + await expect( + Paranet.connect(kcCreator).updateParanetServiceMetadata( + await KnowledgeCollectionStorage.getAddress(), + serviceCollectionId, + 2, + 'New Name', + 'New Description', + [accounts[1].address], + ), + ).to.be.revertedWithCustomError(ParanetLib, 'ParanetServiceDoesntExist'); + }); + }); + + // describe('Paranet Permissioned Miners', () => { + // it('Should allow owner to add and remove curated miners', async () => { + // // Setup paranet with permissioned miners policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner1 = accounts[10]; + // const miner2 = accounts[11]; + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, // Add this to destructuring + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, // nodes policy + // 1, // miners policy + // ACCESS_POLICIES.OPEN, // submission policy + // ); + + // // Verify initial state + // expect( + // await ParanetsRegistry.getKnowledgeMinersCount(paranetId), + // ).to.equal(0); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner1.address, + // ), + // ).to.be.equal(false); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner2.address, + // ), + // ).to.be.equal(false); + + // // Add miners + // await expect( + // Paranet.connect(kcCreator).addParanetCuratedMiners( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [miner1.address, miner2.address], + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner1.address, + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner2.address, + // ); + + // // Verify miners were added + // expect( + // await ParanetsRegistry.getKnowledgeMinersCount(paranetId), + // ).to.equal(2); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner1.address, + // ), + // ).to.be.equal(true); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner2.address, + // ), + // ).to.be.equal(true); + + // const registeredMiners = + // await ParanetsRegistry.getKnowledgeMiners(paranetId); + // expect(registeredMiners[0]).to.be.equal(miner1.address); + // expect(registeredMiners[1]).to.be.equal(miner2.address); + + // // Remove miners + // await expect( + // Paranet.connect(kcCreator).removeParanetCuratedMiners( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [miner1.address], + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerRemoved') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner1.address, + // ); + + // // Verify miner1 was removed but miner2 remains + // expect( + // await ParanetsRegistry.getKnowledgeMinersCount(paranetId), + // ).to.equal(1); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner1.address, + // ), + // ).to.be.equal(false); + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner2.address, + // ), + // ).to.be.equal(true); + + // const remainingMiners = + // await ParanetsRegistry.getKnowledgeMiners(paranetId); + // expect(remainingMiners).to.have.lengthOf(1); + // expect(remainingMiners[0]).to.equal(miner2.address); + // }); + + // it('Should handle miner access requests correctly', async () => { + // // Setup paranet with permissioned miners policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner = accounts[10]; + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, // nodes policy + // 1, // miners policy + // ACCESS_POLICIES.OPEN, // submission policy + // ); + + // // Request access + // await expect( + // Paranet.connect(miner).requestParanetCuratedMinerAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAccessRequestCreated') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ); + + // // Verify request state + // const latestRequest = + // await ParanetsRegistry.getLatestKnowledgeMinerAccessRequest( + // paranetId, + // miner.address, + // ); + // expect(latestRequest.miner).to.equal(miner.address); + // expect(latestRequest.status).to.equal(1); // PENDING status + // expect(latestRequest.createdAt).to.be.gt(0); + // expect(latestRequest.updatedAt).to.equal(latestRequest.createdAt); + + // // Approve request + // await expect( + // Paranet.connect(kcCreator).approveCuratedMiner( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAccessRequestAccepted') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ); + + // // Verify request state after approval + // const updatedRequest = + // await ParanetsRegistry.getLatestKnowledgeMinerAccessRequest( + // paranetId, + // miner.address, + // ); + // expect(updatedRequest.miner).to.equal(miner.address); + // expect(updatedRequest.status).to.equal(2); // ACCEPTED status + // expect(updatedRequest.createdAt).to.equal(latestRequest.createdAt); + // expect(updatedRequest.updatedAt).to.be.gt(latestRequest.updatedAt); + + // // Verify miner is now registered + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner.address, + // ), + // ).to.be.equal(true); + // }); + + // it('Should handle miner access request rejection', async () => { + // // Setup paranet with permissioned miners policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner = accounts[10]; + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, // nodes policy + // 1, // miners policy + // ACCESS_POLICIES.OPEN, // submission policy + // ); + + // // Request access + // await Paranet.connect(miner).requestParanetCuratedMinerAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ); + + // // Verify initial request state + // const latestRequest = + // await ParanetsRegistry.getLatestKnowledgeMinerAccessRequest( + // paranetId, + // miner.address, + // ); + // expect(latestRequest.miner).to.equal(miner.address); + // expect(latestRequest.status).to.equal(1); // PENDING status + // expect(latestRequest.createdAt).to.be.gt(0); + // expect(latestRequest.updatedAt).to.equal(latestRequest.createdAt); + + // // Reject request + // await expect( + // Paranet.connect(kcCreator).rejectCuratedMiner( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedMinerAccessRequestRejected') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // miner.address, + // ); + + // // Verify request state after rejection + // const updatedRequest = + // await ParanetsRegistry.getLatestKnowledgeMinerAccessRequest( + // paranetId, + // miner.address, + // ); + // expect(updatedRequest.miner).to.equal(miner.address); + // expect(updatedRequest.status).to.equal(3); // REJECTED status + // expect(updatedRequest.createdAt).to.equal(latestRequest.createdAt); + // expect(updatedRequest.updatedAt).to.be.gt(latestRequest.updatedAt); + + // // Verify miner is not registered + // expect( + // await ParanetsRegistry.isKnowledgeMinerRegistered( + // paranetId, + // miner.address, + // ), + // ).to.be.equal(false); + // }); + + // it('Should revert when non-owner tries to add/remove miners', async () => { + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner = accounts[10]; + // const nonOwner = accounts[11]; + + // const { paranetKCStorageContract, paranetKCTokenId, paranetKATokenId } = + // await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, + // 1, + // ACCESS_POLICIES.OPEN, + // ); + + // // Try to add miner as non-owner + // await expect( + // Paranet.connect(nonOwner).addParanetCuratedMiners( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [miner.address], + // ), + // ).to.be.revertedWith("Caller isn't the owner of the KA"); + + // // Try to remove miner as non-owner + // await expect( + // Paranet.connect(nonOwner).removeParanetCuratedMiners( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [miner.address], + // ), + // ).to.be.revertedWith("Caller isn't the owner of the KA"); + // }); + + // it('Should revert when requesting access multiple times', async () => { + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner = accounts[10]; + + // const { paranetKCStorageContract, paranetKCTokenId, paranetKATokenId } = + // await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, + // 1, + // ACCESS_POLICIES.OPEN, + // ); + + // // First request + // await Paranet.connect(miner).requestParanetCuratedMinerAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ); + + // // Try to request again while first request is pending + // await expect( + // Paranet.connect(miner).requestParanetCuratedMinerAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ), + // ).to.be.revertedWithCustomError( + // Paranet, + // 'ParanetCuratedMinerAccessRequestInvalidStatus', + // ); + // }); + + // it('Should allow registered miner to submit KC but reject unregistered miner', async () => { + // // Setup paranet with permissioned miners policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const miner1 = accounts[10]; + // const miner2 = accounts[11]; + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // publishingNodeIdentityId, + // receivingNodesIdentityIds, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // ACCESS_POLICIES.OPEN, // nodes policy + // 1, // miners policy - PERMISSIONED + // ACCESS_POLICIES.OPEN, // submission policy + // ); + + // // Add miner1 as a curated miner + // await Paranet.connect(kcCreator).addParanetCuratedMiners( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [miner1.address], + // ); + + // // Create KC for miner1 + // const signaturesData1 = await getKCSignaturesData( + // publishingNode, + // 1, + // receivingNodes, + // ); + // const { collectionId: miner1CollectionId } = + // await createKnowledgeCollection( + // miner1, // Using miner1 as KC creator + // publishingNodeIdentityId, + // receivingNodesIdentityIds, + // signaturesData1, + // { + // KnowledgeCollection: KnowledgeCollection, + // Token: Token, + // }, + // ); + + // // Create KC for miner2 + // const signaturesData2 = await getKCSignaturesData( + // publishingNode, + // 1, + // receivingNodes, + // ); + // const { collectionId: miner2CollectionId } = + // await createKnowledgeCollection( + // miner2, // Using miner2 as KC creator + // publishingNodeIdentityId, + // receivingNodesIdentityIds, + // signaturesData2, + // { + // KnowledgeCollection: KnowledgeCollection, + // Token: Token, + // }, + // ); + + // // Miner1 (registered) submits KC - should succeed + // await expect( + // Paranet.connect(miner1).submitKnowledgeCollection( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // await KnowledgeCollectionStorage.getAddress(), + // miner1CollectionId, + // ), + // ) + // .to.emit(Paranet, 'KnowledgeCollectionSubmittedToParanet') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // await KnowledgeCollectionStorage.getAddress(), + // miner1CollectionId, + // ); + + // // Verify miner1's KC was added + // const miner1CollectionBytes = ethers.keccak256( + // ethers.solidityPacked( + // ['address', 'uint256'], + // [await KnowledgeCollectionStorage.getAddress(), miner1CollectionId], + // ), + // ); + + // expect( + // await ParanetsRegistry.isKnowledgeCollectionRegistered( + // paranetId, + // miner1CollectionBytes, + // ), + // ).to.be.equal(true); + + // // Miner2 (unregistered) attempts to submit KC - should fail + // await expect( + // Paranet.connect(miner2).submitKnowledgeCollection( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // await KnowledgeCollectionStorage.getAddress(), + // miner2CollectionId, + // ), + // ).to.be.revertedWith('Miner is not registered'); + + // // Verify miner2's KC was not added + + // const miner2CollectionBytes = ethers.keccak256( + // ethers.solidityPacked( + // ['address', 'uint256'], + // [await KnowledgeCollectionStorage.getAddress(), miner2CollectionId], + // ), + // ); + // expect( + // await ParanetsRegistry.isKnowledgeCollectionRegistered( + // paranetId, + // miner2CollectionBytes, + // ), + // ).to.be.equal(false); + + // // Verify total KC count is 1 (only miner1's KC) + // expect( + // await ParanetsRegistry.getKnowledgeCollectionsCount(paranetId), + // ).to.equal(1); + // }); + // }); + + // describe('Paranet Permissioned Nodes', () => { + // it('Should allow owner to add and remove curated nodes', async () => { + // // Setup paranet with permissioned nodes policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const node1 = accounts[10]; + // const node2 = accounts[11]; + + // // Create profiles for test nodes + // await Profile.connect(node1).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Node1', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // await Profile.connect(node2).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Node2', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // const node1IdentityId = await IdentityStorage.getIdentityId( + // node1.address, + // ); + // const node2IdentityId = await IdentityStorage.getIdentityId( + // node2.address, + // ); + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // 1, // NODES_ACCESS_POLICY_PERMISSIONED + // ACCESS_POLICIES.OPEN, + // ACCESS_POLICIES.OPEN, + // ); + + // // Verify initial state + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 0, + // ); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node1IdentityId), + // ).to.be.equal(false); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node2IdentityId), + // ).to.be.equal(false); + + // // Add nodes + // await expect( + // Paranet.connect(kcCreator).addParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node1IdentityId, node2IdentityId], + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // node1IdentityId, + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // node2IdentityId, + // ); + + // // Verify nodes were added + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 2, + // ); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node1IdentityId), + // ).to.be.equal(true); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node2IdentityId), + // ).to.be.equal(true); + + // const curatedNodes = await ParanetsRegistry.getCuratedNodes(paranetId); + // expect(curatedNodes[0].identityId).to.equal(node1IdentityId); + // expect(curatedNodes[1].identityId).to.equal(node2IdentityId); + + // // Remove node1 + // await expect( + // Paranet.connect(kcCreator).removeParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node1IdentityId], + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeRemoved') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // node1IdentityId, + // ); + + // // Verify node1 was removed but node2 remains + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 1, + // ); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node1IdentityId), + // ).to.be.equal(false); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node2IdentityId), + // ).to.be.equal(true); + + // const remainingNodes = await ParanetsRegistry.getCuratedNodes(paranetId); + // expect(remainingNodes).to.have.lengthOf(1); + // expect(remainingNodes[0].identityId).to.equal(node2IdentityId); + // }); + + // it('Should handle node join requests correctly', async () => { + // // Setup paranet with permissioned nodes policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const applicantNode = accounts[10]; + + // // Create profile for applicant node + // await Profile.connect(applicantNode).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Applicant', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // const applicantIdentityId = await IdentityStorage.getIdentityId( + // applicantNode.address, + // ); + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // 1, // NODES_ACCESS_POLICY_PERMISSIONED + // ACCESS_POLICIES.OPEN, + // ACCESS_POLICIES.OPEN, + // ); + + // // Request to join + // await expect( + // Paranet.connect(applicantNode).requestParanetCuratedNodeAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeJoinRequestCreated') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ); + + // // Verify initial request state + // const latestRequest = await ParanetsRegistry.getLatestNodeJoinRequest( + // paranetId, + // applicantIdentityId, + // ); + // expect(latestRequest.identityId).to.equal(applicantIdentityId); + // expect(latestRequest.status).to.equal(1); // PENDING status + // expect(latestRequest.createdAt).to.be.gt(0); + // expect(latestRequest.updatedAt).to.equal(latestRequest.createdAt); + + // // Approve request + // await expect( + // Paranet.connect(kcCreator).approveCuratedNode( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeJoinRequestAccepted') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeAdded') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ); + + // // Verify request state after approval + // const updatedRequest = await ParanetsRegistry.getLatestNodeJoinRequest( + // paranetId, + // applicantIdentityId, + // ); + // expect(updatedRequest.identityId).to.equal(applicantIdentityId); + // expect(updatedRequest.status).to.equal(2); // ACCEPTED status + // expect(updatedRequest.createdAt).to.equal(latestRequest.createdAt); + // expect(updatedRequest.updatedAt).to.be.gt(latestRequest.updatedAt); + + // // Verify node is now registered + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, applicantIdentityId), + // ).to.be.equal(true); + // }); + + // it('Should handle node join request rejection correctly', async () => { + // // Setup paranet with permissioned nodes policy + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const applicantNode = accounts[10]; + + // // Create profile for applicant node + // await Profile.connect(applicantNode).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Applicant', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // const applicantIdentityId = await IdentityStorage.getIdentityId( + // applicantNode.address, + // ); + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // 1, // NODES_ACCESS_POLICY_PERMISSIONED + // ACCESS_POLICIES.OPEN, + // ACCESS_POLICIES.OPEN, + // ); + + // // Request to join + // await expect( + // Paranet.connect(applicantNode).requestParanetCuratedNodeAccess( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeJoinRequestCreated') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ); + + // // Verify initial request state + // const latestRequest = await ParanetsRegistry.getLatestNodeJoinRequest( + // paranetId, + // applicantIdentityId, + // ); + // expect(latestRequest.identityId).to.equal(applicantIdentityId); + // expect(latestRequest.status).to.equal(1); // PENDING status + + // // Reject request + // await expect( + // Paranet.connect(kcCreator).rejectCuratedNode( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ), + // ) + // .to.emit(Paranet, 'ParanetCuratedNodeJoinRequestRejected') + // .withArgs( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // applicantIdentityId, + // ); + + // // Verify request state after rejection + // const updatedRequest = await ParanetsRegistry.getLatestNodeJoinRequest( + // paranetId, + // applicantIdentityId, + // ); + // expect(updatedRequest.identityId).to.equal(applicantIdentityId); + // expect(updatedRequest.status).to.equal(3); // REJECTED status + // expect(updatedRequest.createdAt).to.equal(latestRequest.createdAt); + // expect(updatedRequest.updatedAt).to.be.gt(latestRequest.updatedAt); + + // // Verify node is not registered + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, applicantIdentityId), + // ).to.be.equal(false); + // }); + + // it('Should handle edge cases when adding and removing multiple nodes', async () => { + // const kcCreator = getDefaultKCCreator(accounts); + // const publishingNode = getDefaultPublishingNode(accounts); + // const receivingNodes = getDefaultReceivingNodes(accounts); + // const node1 = accounts[10]; + // const node2 = accounts[11]; + // const node3 = accounts[12]; + + // // Create profiles for test nodes + // await Profile.connect(node1).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Node1', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // await Profile.connect(node2).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Node2', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // await Profile.connect(node3).createProfile( + // accounts[0].address, + // [], // operational wallets + // 'Node3', + // '0x' + randomBytes(32).toString('hex'), + // 0, + // ); + // const node1IdentityId = await IdentityStorage.getIdentityId( + // node1.address, + // ); + // const node2IdentityId = await IdentityStorage.getIdentityId( + // node2.address, + // ); + // const node3IdentityId = await IdentityStorage.getIdentityId( + // node3.address, + // ); + + // const { + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // paranetId, + // } = await setupParanet( + // kcCreator, + // publishingNode, + // receivingNodes, + // { + // Paranet, + // Profile, + // Token, + // KnowledgeCollection, + // KnowledgeCollectionStorage, + // }, + // 'Test Paranet', + // 'Test Paranet Description', + // 1, // NODES_ACCESS_POLICY_PERMISSIONED + // ACCESS_POLICIES.OPEN, + // ACCESS_POLICIES.OPEN, + // ); + + // // Add nodes in sequence + // await Paranet.connect(kcCreator).addParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node1IdentityId], + // ); + + // await Paranet.connect(kcCreator).addParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node2IdentityId, node3IdentityId], + // ); + + // // Verify all nodes were added + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 3, + // ); + // const allNodes = await ParanetsRegistry.getCuratedNodes(paranetId); + // expect(allNodes.map((node) => node.identityId)).to.have.members([ + // node1IdentityId, + // node2IdentityId, + // node3IdentityId, + // ]); + + // // Remove nodes from edges (first and last) + // await Paranet.connect(kcCreator).removeParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node1IdentityId, node3IdentityId], + // ); + + // // Verify middle node remains + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 1, + // ); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node1IdentityId), + // ).to.be.equal(false); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node2IdentityId), + // ).to.be.equal(true); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node3IdentityId), + // ).to.be.equal(false); + + // // Try to remove non-existent node - should revert + // await expect( + // Paranet.connect(kcCreator).removeParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node1IdentityId], // Already removed + // ), + // ).to.be.revertedWithCustomError(Paranet, 'ParanetCuratedNodeDoesntExist'); + + // // Verify state remains unchanged + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 1, + // ); + // expect( + // await ParanetsRegistry.isCuratedNode(paranetId, node2IdentityId), + // ).to.be.equal(true); + + // // Try to add duplicate node + // await expect( + // Paranet.connect(kcCreator).addParanetCuratedNodes( + // paranetKCStorageContract, + // paranetKCTokenId, + // paranetKATokenId, + // [node2IdentityId], + // ), + // ).to.be.revertedWithCustomError( + // Paranet, + // 'ParanetCuratedNodeHasAlreadyBeenAdded', + // ); + + // expect(await ParanetsRegistry.getCuratedNodesCount(paranetId)).to.equal( + // 1, + // ); + // const finalNodes = await ParanetsRegistry.getCuratedNodes(paranetId); + // expect(finalNodes).to.have.lengthOf(1); + // expect(finalNodes[0].identityId).to.equal(node2IdentityId); + // }); + // }); +}); diff --git a/test/unit/Chronos.test.ts b/test/unit/Chronos.test.ts new file mode 100644 index 00000000..89d5fcd3 --- /dev/null +++ b/test/unit/Chronos.test.ts @@ -0,0 +1,164 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture, time } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import hre from 'hardhat'; + +import { Chronos } from '../../typechain'; + +describe('@unit Chronos', () => { + let accounts: SignerWithAddress[]; + let Chronos: Chronos; + + async function deployChronosFixture() { + const currentTime = await time.latest(); + const startTime = currentTime + 3600; // Start 1 hour from now + const epochLength = 3600; // 1 hour epochs + + const ChronosFactory = await hre.ethers.getContractFactory('Chronos'); + Chronos = await ChronosFactory.deploy(startTime, epochLength); + + accounts = await hre.ethers.getSigners(); + return { accounts, Chronos, startTime, epochLength }; + } + + beforeEach(async () => { + ({ accounts, Chronos } = await loadFixture(deployChronosFixture)); + }); + + describe('Constructor', () => { + it('Should revert with invalid start time', async () => { + const ChronosFactory = await hre.ethers.getContractFactory('Chronos'); + await expect( + ChronosFactory.deploy(0, 3600), + ).to.be.revertedWithCustomError(ChronosFactory, 'InvalidStartTime'); + }); + + it('Should revert with invalid epoch length', async () => { + const currentTime = await time.latest(); + const ChronosFactory = await hre.ethers.getContractFactory('Chronos'); + await expect( + ChronosFactory.deploy(currentTime + 3600, 0), + ).to.be.revertedWithCustomError(ChronosFactory, 'InvalidEpochLength'); + }); + }); + + describe('Basic getters', () => { + it('Should return correct start time', async () => { + const startTime = await Chronos.START_TIME(); + expect(await Chronos.startTime()).to.equal(startTime); + }); + + it('Should return correct epoch length', async () => { + const epochLength = await Chronos.EPOCH_LENGTH(); + expect(await Chronos.epochLength()).to.equal(epochLength); + }); + }); + + describe('getCurrentEpoch', () => { + it('Should return 1 before start time', async () => { + expect(await Chronos.getCurrentEpoch()).to.equal(1); + }); + + it('Should return correct epoch after start time', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + await time.increase(7200); // 2 hours after start + expect(await Chronos.getCurrentEpoch()).to.equal(3); // Should be in 3rd epoch + }); + }); + + describe('epochAtTimestamp', () => { + it('Should return 1 for timestamp before start time', async () => { + const startTime = await Chronos.START_TIME(); + expect(await Chronos.epochAtTimestamp(startTime - 1n)).to.equal(1); + }); + + it('Should return correct epoch for timestamp after start time', async () => { + const startTime = await Chronos.START_TIME(); + const timestamp = startTime + 7200n; // 2 hours after start + expect(await Chronos.epochAtTimestamp(timestamp)).to.equal(3); + }); + }); + + describe('timeUntilNextEpoch', () => { + it('Should return time until first epoch before start', async () => { + const startTime = await Chronos.START_TIME(); + const currentTime = await time.latest(); + const expected = startTime + 3600n - BigInt(currentTime); + expect(await Chronos.timeUntilNextEpoch()).to.be.closeTo(expected, 5); + }); + + it('Should return correct time within an epoch', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + await time.increase(1800); // Half epoch passed + expect(await Chronos.timeUntilNextEpoch()).to.be.closeTo(1800n, 5); + }); + }); + + /* eslint-disable @typescript-eslint/no-unused-expressions */ + describe('hasEpochElapsed', () => { + it('Should return false for future epochs', async () => { + expect(await Chronos.hasEpochElapsed(5)).to.be.false; + }); + + it('Should return true for past epochs', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + await time.increase(7200); // 2 hours after start + expect(await Chronos.hasEpochElapsed(2)).to.be.true; + }); + }); + + describe('timestampForEpoch', () => { + it('Should return 0 for epoch 0', async () => { + expect(await Chronos.timestampForEpoch(0)).to.equal(0); + }); + + it('Should return correct timestamp for future epoch', async () => { + const startTime = await Chronos.START_TIME(); + const epochLength = await Chronos.EPOCH_LENGTH(); + expect(await Chronos.timestampForEpoch(3)).to.equal( + startTime + epochLength * 2n, + ); + }); + }); + + describe('elapsedTimeInCurrentEpoch', () => { + it('Should return 0 before start time', async () => { + expect(await Chronos.elapsedTimeInCurrentEpoch()).to.equal(0); + }); + + it('Should return correct elapsed time within epoch', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + await time.increase(1800); // Half epoch passed + expect(await Chronos.elapsedTimeInCurrentEpoch()).to.be.closeTo(1800n, 5); + }); + }); + + describe('totalElapsedTime', () => { + it('Should return 0 before start time', async () => { + expect(await Chronos.totalElapsedTime()).to.equal(0); + }); + + it('Should return correct total elapsed time', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + await time.increase(7200); // 2 hours after start + expect(await Chronos.totalElapsedTime()).to.be.closeTo(7200n, 5); + }); + }); + + describe('isChronosActive', () => { + it('Should return false before start time', async () => { + expect(await Chronos.isChronosActive()).to.be.false; + }); + + it('Should return true after start time', async () => { + const startTime = await Chronos.START_TIME(); + await time.increaseTo('0x' + startTime.toString(16)); + expect(await Chronos.isChronosActive()).to.be.true; + }); + }); +}); diff --git a/test/unit/EpochStorage.test.ts b/test/unit/EpochStorage.test.ts index 6a42956a..c86d0ff4 100644 --- a/test/unit/EpochStorage.test.ts +++ b/test/unit/EpochStorage.test.ts @@ -26,6 +26,11 @@ describe('@unit EpochStorage', () => { ({ accounts, EpochStorage } = await loadFixture(deployEpochStorageFixture)); }); + it('Should have correct name and version', async () => { + expect(await EpochStorage.name()).to.equal('EpochStorage'); + expect(await EpochStorage.version()).to.equal('1.0.0'); + }); + it('Add knowledge value for single epoch, verify totals and max', async () => { const epoch = 10; await EpochStorage.addEpochProducedKnowledgeValue(123, epoch, 500); diff --git a/test/unit/KnowledgeCollection.test.ts b/test/unit/KnowledgeCollection.test.ts new file mode 100644 index 00000000..f80739cd --- /dev/null +++ b/test/unit/KnowledgeCollection.test.ts @@ -0,0 +1,212 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { ethers } from 'ethers'; +import hre from 'hardhat'; + +import { + KnowledgeCollection, + KnowledgeCollectionStorage, + EpochStorage, + AskStorage, + Chronos, + Token, + ParametersStorage, + IdentityStorage, + Hub, + Profile, + ParanetKnowledgeCollectionsRegistry, + ParanetKnowledgeMinersRegistry, + Identity, + Staking, +} from '../../typechain'; +import { + createKnowledgeCollection, + createProfilesAndKC, + getKCSignaturesData, +} from '../helpers/kc-helpers'; +import { createProfile } from '../helpers/profile-helpers'; +import { + getDefaultPublishingNode, + getDefaultReceivingNodes, + getDefaultKCCreator, +} from '../helpers/setup-helpers'; + +type KnowledgeCollectionFixture = { + accounts: SignerWithAddress[]; + KnowledgeCollection: KnowledgeCollection; + KnowledgeCollectionStorage: KnowledgeCollectionStorage; + EpochStorage: EpochStorage; + AskStorage: AskStorage; + Chronos: Chronos; + Token: Token; + ParametersStorage: ParametersStorage; + IdentityStorage: IdentityStorage; + Identity: Identity; + Profile: Profile; + ParanetsKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + Staking: Staking; +}; + +describe('@unit KnowledgeCollection', () => { + let accounts: SignerWithAddress[]; + let KnowledgeCollection: KnowledgeCollection; + let KnowledgeCollectionStorage: KnowledgeCollectionStorage; + let EpochStorage: EpochStorage; + let AskStorage: AskStorage; + let Chronos: Chronos; + let Token: Token; + let ParametersStorage: ParametersStorage; + let IdentityStorage: IdentityStorage; + let Identity: Identity; + let Profile: Profile; + let ParanetsKnowledgeCollectionsRegistry: ParanetKnowledgeCollectionsRegistry; + let ParanetKnowledgeMinersRegistry: ParanetKnowledgeMinersRegistry; + let Staking: Staking; + + async function deployKnowledgeCollectionFixture(): Promise { + await hre.deployments.fixture([ + 'Token', + 'AskStorage', + 'EpochStorage', + 'KnowledgeCollection', + 'ParanetKnowledgeCollectionsRegistry', + 'ParanetKnowledgeMinersRegistry', + 'Chronos', + 'Profile', + 'Identity', + 'Staking', + ]); + + accounts = await hre.ethers.getSigners(); + const Hub = await hre.ethers.getContract('Hub'); + + KnowledgeCollection = await hre.ethers.getContract( + 'KnowledgeCollection', + ); + KnowledgeCollectionStorage = + await hre.ethers.getContract( + 'KnowledgeCollectionStorage', + ); + EpochStorage = await hre.ethers.getContract('EpochStorageV8'); + AskStorage = await hre.ethers.getContract('AskStorage'); + Chronos = await hre.ethers.getContract('Chronos'); + Token = await hre.ethers.getContract('Token'); + ParametersStorage = + await hre.ethers.getContract('ParametersStorage'); + IdentityStorage = + await hre.ethers.getContract('IdentityStorage'); + Identity = await hre.ethers.getContract('Identity'); + Profile = await hre.ethers.getContract('Profile'); + ParanetsKnowledgeCollectionsRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeCollectionsRegistry', + ); + ParanetKnowledgeMinersRegistry = + await hre.ethers.getContract( + 'ParanetKnowledgeMinersRegistry', + ); + Staking = await hre.ethers.getContract('Staking'); + + await Hub.setContractAddress('HubOwner', accounts[0].address); + + return { + accounts, + KnowledgeCollection, + KnowledgeCollectionStorage, + EpochStorage, + AskStorage, + Chronos, + Token, + ParametersStorage, + IdentityStorage, + Profile, + ParanetsKnowledgeCollectionsRegistry, + ParanetKnowledgeMinersRegistry, + Identity, + Staking, + }; + } + + beforeEach(async () => { + hre.helpers.resetDeploymentsJson(); + ({ + accounts, + KnowledgeCollection, + KnowledgeCollectionStorage, + EpochStorage, + AskStorage, + Chronos, + Token, + ParametersStorage, + IdentityStorage, + Profile, + } = await loadFixture(deployKnowledgeCollectionFixture)); + }); + + it('Should create a knowledge collection successfully', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { receivingNodesIdentityIds, collectionId } = + await createProfilesAndKC(kcCreator, publishingNode, receivingNodes, { + Profile, + KnowledgeCollection, + Token, + }); + + expect(collectionId).to.equal(1); + + // Verify knowledge collection was created + const metadata = + await KnowledgeCollectionStorage.getKnowledgeCollectionMetadata( + collectionId, + ); + + expect(metadata[0][0].length).to.equal(receivingNodesIdentityIds.length); // merkle roots + expect(metadata[1].length).to.equal(0); // burned + expect(metadata[2]).to.equal(10); // minted + expect(metadata[3]).to.equal(1000); // byteSize + expect(metadata[4]).to.equal(2); // startEpoch + expect(metadata[5]).to.equal(4); // endEpoch + expect(metadata[6]).to.equal(ethers.parseEther('100')); // tokenAmount + expect(metadata[7]).to.equal(false); // isImmutable + }); + + it('Should revert if insufficient signatures provided', async () => { + const kcCreator = getDefaultKCCreator(accounts); + const publishingNode = getDefaultPublishingNode(accounts); + const receivingNodes = getDefaultReceivingNodes(accounts); + + const { identityId: publisherIdentityId } = await createProfile( + Profile, + publishingNode, + ); + + const signaturesData = await getKCSignaturesData( + publishingNode, + publisherIdentityId, + receivingNodes, + ); + + // Override receivers arrays to be empty + const receiversIdentityIds: number[] = []; + signaturesData.receiverRs = []; + signaturesData.receiverVSs = []; + + await expect( + createKnowledgeCollection( + kcCreator, + publisherIdentityId, + receiversIdentityIds, + signaturesData, + { KnowledgeCollection, Token }, + ), + ).to.be.revertedWithCustomError( + KnowledgeCollection, + 'MinSignaturesRequirementNotMet', + ); + }); +}); diff --git a/test/unit/Paymaster.test.ts b/test/unit/Paymaster.test.ts new file mode 100644 index 00000000..547fd0bf --- /dev/null +++ b/test/unit/Paymaster.test.ts @@ -0,0 +1,211 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { parseEther } from 'ethers'; +import hre from 'hardhat'; + +import { Hub, Paymaster, Token } from '../../typechain'; + +describe('@unit Paymaster', () => { + let accounts: SignerWithAddress[]; + let Hub: Hub; + let Token: Token; + let Paymaster: Paymaster; + let owner: SignerWithAddress; + let user: SignerWithAddress; + let knowledgeCollectionAddress: string; + + async function deployPaymasterFixture() { + await hre.deployments.fixture(['Hub', 'Token']); + + accounts = await hre.ethers.getSigners(); + [owner, user] = accounts; + + Hub = await hre.ethers.getContract('Hub'); + Token = await hre.ethers.getContract('Token'); + + // Deploy Paymaster + const PaymasterFactory = await hre.ethers.getContractFactory('Paymaster'); + Paymaster = await PaymasterFactory.deploy(Hub.getAddress()); + + // Set mock KnowledgeCollection address in Hub + knowledgeCollectionAddress = accounts[3].address; + await Hub.setContractAddress( + 'KnowledgeCollection', + knowledgeCollectionAddress, + ); + + // Reset user's balance to zero first + const initialBalance = await Token.balanceOf(user.address); + if (initialBalance > 0) { + const BURN_ADDRESS = accounts[66].address; + await Token.connect(user).transfer(BURN_ADDRESS, initialBalance); + } + + // Mint some tokens to user for testing + await Token.mint(user.address, parseEther('100')); + + return { accounts, Hub, Token, Paymaster, owner, user }; + } + + beforeEach(async () => { + ({ accounts, Hub, Token, Paymaster, owner, user } = await loadFixture( + deployPaymasterFixture, + )); + }); + + describe('Constructor', () => { + it('Should set correct hub address', async () => { + expect(await Paymaster.hub()).to.equal(await Hub.getAddress()); + }); + + it('Should set correct token contract', async () => { + expect(await Paymaster.tokenContract()).to.equal( + await Token.getAddress(), + ); + }); + + it('Should set correct owner', async () => { + expect(await Paymaster.owner()).to.equal(owner.address); + }); + }); + + /* eslint-disable @typescript-eslint/no-unused-expressions */ + describe('Access Control', () => { + it('Should allow owner to add allowed address', async () => { + await Paymaster.addAllowedAddress(user.address); + expect(await Paymaster.allowedAddresses(user.address)).to.be.true; + }); + + it('Should not allow non-owner to add allowed address', async () => { + await expect( + Paymaster.connect(user).addAllowedAddress(user.address), + ).to.be.revertedWithCustomError(Paymaster, 'OwnableUnauthorizedAccount'); + }); + + it('Should allow owner to remove allowed address', async () => { + await Paymaster.addAllowedAddress(user.address); + await Paymaster.removeAllowedAddress(user.address); + expect(await Paymaster.allowedAddresses(user.address)).to.be.false; + }); + }); + + describe('Fund Paymaster', () => { + const fundAmount = parseEther('100'); + + beforeEach(async () => { + // Approve tokens first + await Token.connect(user).approve(Paymaster.getAddress(), fundAmount); + }); + + it('Should allow funding with tokens', async () => { + await expect(Paymaster.connect(user).fundPaymaster(fundAmount)) + .to.emit(Token, 'Transfer') + .withArgs(user.address, await Paymaster.getAddress(), fundAmount); + }); + + it('Should revert with zero amount', async () => { + await expect( + Paymaster.connect(user).fundPaymaster(0), + ).to.be.revertedWithCustomError(Paymaster, 'ZeroTokenAmount'); + }); + + it('Should revert with insufficient allowance', async () => { + await Token.connect(user).approve(Paymaster.getAddress(), 0); + await expect( + Paymaster.connect(user).fundPaymaster(fundAmount), + ).to.be.revertedWithCustomError(Paymaster, 'TooLowAllowance'); + }); + + it('Should revert with insufficient balance', async () => { + const tooMuch = parseEther('200'); + await Token.connect(user).approve(Paymaster.getAddress(), tooMuch); + await expect( + Paymaster.connect(user).fundPaymaster(tooMuch), + ).to.be.revertedWithCustomError(Paymaster, 'TooLowBalance'); + }); + }); + + describe('Withdraw', () => { + const withdrawAmount = parseEther('50'); + + beforeEach(async () => { + // Fund the paymaster first + await Token.connect(user).approve( + Paymaster.getAddress(), + parseEther('100'), + ); + await Paymaster.connect(user).fundPaymaster(parseEther('100')); + }); + + it('Should allow owner to withdraw tokens', async () => { + await expect(Paymaster.withdraw(owner.address, withdrawAmount)) + .to.emit(Token, 'Transfer') + .withArgs(await Paymaster.getAddress(), owner.address, withdrawAmount); + }); + + it('Should revert when non-owner tries to withdraw', async () => { + await expect( + Paymaster.connect(user).withdraw(user.address, withdrawAmount), + ).to.be.revertedWithCustomError(Paymaster, 'OwnableUnauthorizedAccount'); + }); + + it('Should revert with zero amount', async () => { + await expect( + Paymaster.withdraw(owner.address, 0), + ).to.be.revertedWithCustomError(Paymaster, 'ZeroTokenAmount'); + }); + + it('Should revert with insufficient balance', async () => { + const tooMuch = parseEther('200'); + await expect( + Paymaster.withdraw(owner.address, tooMuch), + ).to.be.revertedWithCustomError(Paymaster, 'TooLowBalance'); + }); + }); + + describe('Cover Cost', () => { + const coverAmount = parseEther('30'); + + beforeEach(async () => { + // Fund the paymaster first + await Token.connect(user).approve( + Paymaster.getAddress(), + parseEther('100'), + ); + await Paymaster.connect(user).fundPaymaster(parseEther('100')); + // Add allowed address + await Paymaster.addAllowedAddress(user.address); + }); + + it('Should allow allowed address to cover cost', async () => { + await expect(Paymaster.connect(user).coverCost(coverAmount)) + .to.emit(Token, 'Transfer') + .withArgs( + await Paymaster.getAddress(), + knowledgeCollectionAddress, + coverAmount, + ); + }); + + it('Should revert when non-allowed address tries to cover cost', async () => { + const nonAllowed = accounts[4]; + await expect( + Paymaster.connect(nonAllowed).coverCost(coverAmount), + ).to.be.revertedWithCustomError(Paymaster, 'NotAllowed'); + }); + + it('Should revert with zero amount', async () => { + await expect( + Paymaster.connect(user).coverCost(0), + ).to.be.revertedWithCustomError(Paymaster, 'ZeroTokenAmount'); + }); + + it('Should revert with insufficient balance', async () => { + const tooMuch = parseEther('200'); + await expect( + Paymaster.connect(user).coverCost(tooMuch), + ).to.be.revertedWithCustomError(Paymaster, 'TooLowBalance'); + }); + }); +});