From 4d5657c6db134fb72dc1ff4e672f733fdbb9c97e Mon Sep 17 00:00:00 2001 From: Brent Ju Date: Fri, 8 Sep 2023 11:26:18 -0700 Subject: [PATCH 1/4] uploader task for assets and relationships --- script/data/data_example.json | 108 +++++++++++++++++++--------------- script/hardhat/sbUploader.js | 76 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 46 deletions(-) diff --git a/script/data/data_example.json b/script/data/data_example.json index 213a02f3..faab35b2 100644 --- a/script/data/data_example.json +++ b/script/data/data_example.json @@ -1,48 +1,64 @@ { - "blocks": { - "stories": [ - { - "id": null, - "blockType": "STORY", - "name": "The Full Grain World Bible", - "description": "Lorem Ipsum", - "author": "Sebastian Sinclair", - "content": "Lorem ipsum dolor sit amet consectetur adipiscing elit ullamcorper venenatis, habitasse egestas luctus penatibus eleifend at litora iaculis risus, cum quis habitant facilisi vel eu sapien taciti. Imperdiet id tincidunt erat primis sociosqu odio, nullam fusce bibendum arcu class lacus, purus cum condimentum sem varius. Magna risus scelerisque ullamcorper himenaeos et maecenas nibh erat donec, tellus vivamus elementum leo nascetur dui in litora. Malesuada nascetur eu elementum urna placerat ridiculus, nisi phasellus faucibus penatibus vivamus, fermentum ut interdum volutpat mauris.\n\n Mi himenaeos massa netus lacinia ac platea penatibus quisque felis sapien, ante ad libero faucibus sed sagittis egestas odio inceptos ridiculus, primis metus nunc sociis eros eleifend ultricies tincidunt taciti. Eu litora cursus penatibus massa cubilia fames pellentesque, curabitur nascetur consequat placerat nulla viverra egestas, ridiculus tellus class in lacinia maecenas.\n \n Eros dignissim sollicitudin torquent posuere litora neque et dui rutrum metus eleifend, sociis facilisi in eget varius netus praesent potenti urna curae. Elementum tempus feugiat suscipit blandit hendrerit placerat cum aliquet curabitur ultrices tempor, tincidunt neque lobortis condimentum cras ad nisi consequat sagittis primis. Curae nullam magna semper himenaeos penatibus potenti vestibulum nostra, magnis venenatis scelerisque mi rhoncus condimentum urna, diam a curabitur aliquam fames sociosqu tincidunt.\n\nViverra lobortis rutrum eget erat ullamcorper vestibulum praesent vivamus torquent, laoreet curabitur aptent dui vel ad ac eu egestas pharetra, turpis bibendum libero faucibus lacinia sociosqu ultricies consequat. Per nec eget hendrerit gravida urna in semper, sociis accumsan facilisi laoreet scelerisque torquent, ad est pharetra faucibus luctus nisl. Urna sociis ut lacus pharetra dis ullamcorper ad arcu, erat aenean congue integer fames blandit purus. Suspendisse condimentum facilisi aliquam at varius velit quis, dictum ullamcorper dapibus cum posuere nec pellentesque, porta laoreet tempus cursus porttitor sollicitudin.\n\nAuctor senectus praesent nostra porttitor platea dapibus phasellus vitae enim tristique venenatis laoreet, vehicula eleifend fusce metus lectus massa facilisis fames class neque eros. Libero magna eros posuere velit faucibus laoreet suscipit nascetur, etiam montes consequat phasellus aptent porttitor ante platea, leo lacinia molestie justo congue malesuada netus. Blandit primis facilisi vivamus scelerisque iaculis sociosqu per quisque odio lacus auctor tempor, sodales mauris donec non magnis nisi sollicitudin nulla semper senectus.\n \n Vel cursus fringilla venenatis ligula ridiculus dictum est libero gravida pharetra, nulla interdum netus laoreet mus dictumst magna class dapibus. Vulputate mauris congue leo nec nisi cum fermentum platea, purus non dis varius erat curabitur dapibus ligula, curae eros tellus venenatis vestibulum fusce himenaeos. Consequat dictumst convallis natoque donec curabitur lacus malesuada, leo a suspendisse mi nostra phasellus quam iaculis, mattis eleifend vulputate ut mollis vehicula." - } - ], - "groups": [ - { - "id": null, - "blockType": "GROUP", - "name": "The Fresh", - "description": "Human Like", - "groupingBlockType": "GROUP", - "ids": [] - } - ], - "characters": [ - { - "id": null, - "blockType": "CHARACTER", - "name": "Captain Crunch", - "description": "Lorem Ipsum" - } - ], - "locations": [ - { - "id": null, - "blockType": "LOCATION", - "name": "Weird Homeworld", - "description": "Lorem Lorem." - } - ], - "items": [ - { - "id": null, - "blockType": "ITEM", - "name": "The asdad", - "description": "asdd" - } - ] + "blocks": { + "stories": [ + { + "id": null, + "blockType": "STORY", + "name": "The Full Grain World Bible", + "description": "Lorem Ipsum", + "author": "Sebastian Sinclair", + "content": "Lorem ipsum dolor sit amet consectetur adipiscing elit ullamcorper venenatis, habitasse egestas luctus penatibus eleifend at litora iaculis risus, cum quis habitant facilisi vel eu sapien taciti. Imperdiet id tincidunt erat primis sociosqu odio, nullam fusce bibendum arcu class lacus, purus cum condimentum sem varius. Magna risus scelerisque ullamcorper himenaeos et maecenas nibh erat donec, tellus vivamus elementum leo nascetur dui in litora. Malesuada nascetur eu elementum urna placerat ridiculus, nisi phasellus faucibus penatibus vivamus, fermentum ut interdum volutpat mauris.\n\n Mi himenaeos massa netus lacinia ac platea penatibus quisque felis sapien, ante ad libero faucibus sed sagittis egestas odio inceptos ridiculus, primis metus nunc sociis eros eleifend ultricies tincidunt taciti. Eu litora cursus penatibus massa cubilia fames pellentesque, curabitur nascetur consequat placerat nulla viverra egestas, ridiculus tellus class in lacinia maecenas.\n \n Eros dignissim sollicitudin torquent posuere litora neque et dui rutrum metus eleifend, sociis facilisi in eget varius netus praesent potenti urna curae. Elementum tempus feugiat suscipit blandit hendrerit placerat cum aliquet curabitur ultrices tempor, tincidunt neque lobortis condimentum cras ad nisi consequat sagittis primis. Curae nullam magna semper himenaeos penatibus potenti vestibulum nostra, magnis venenatis scelerisque mi rhoncus condimentum urna, diam a curabitur aliquam fames sociosqu tincidunt.\n\nViverra lobortis rutrum eget erat ullamcorper vestibulum praesent vivamus torquent, laoreet curabitur aptent dui vel ad ac eu egestas pharetra, turpis bibendum libero faucibus lacinia sociosqu ultricies consequat. Per nec eget hendrerit gravida urna in semper, sociis accumsan facilisi laoreet scelerisque torquent, ad est pharetra faucibus luctus nisl. Urna sociis ut lacus pharetra dis ullamcorper ad arcu, erat aenean congue integer fames blandit purus. Suspendisse condimentum facilisi aliquam at varius velit quis, dictum ullamcorper dapibus cum posuere nec pellentesque, porta laoreet tempus cursus porttitor sollicitudin.\n\nAuctor senectus praesent nostra porttitor platea dapibus phasellus vitae enim tristique venenatis laoreet, vehicula eleifend fusce metus lectus massa facilisis fames class neque eros. Libero magna eros posuere velit faucibus laoreet suscipit nascetur, etiam montes consequat phasellus aptent porttitor ante platea, leo lacinia molestie justo congue malesuada netus. Blandit primis facilisi vivamus scelerisque iaculis sociosqu per quisque odio lacus auctor tempor, sodales mauris donec non magnis nisi sollicitudin nulla semper senectus.\n \n Vel cursus fringilla venenatis ligula ridiculus dictum est libero gravida pharetra, nulla interdum netus laoreet mus dictumst magna class dapibus. Vulputate mauris congue leo nec nisi cum fermentum platea, purus non dis varius erat curabitur dapibus ligula, curae eros tellus venenatis vestibulum fusce himenaeos. Consequat dictumst convallis natoque donec curabitur lacus malesuada, leo a suspendisse mi nostra phasellus quam iaculis, mattis eleifend vulputate ut mollis vehicula." + } + ], + "groups": [ + { + "id": null, + "blockType": "GROUP", + "name": "The Fresh", + "description": "Human Like", + "groupingBlockType": "GROUP", + "ids": [] + } + ], + "characters": [ + { + "id": null, + "blockType": "CHARACTER", + "name": "Captain Crunch", + "description": "Lorem Ipsum" + } + ], + "locations": [ + { + "id": null, + "blockType": "LOCATION", + "name": "Weird Homeworld", + "description": "Lorem Lorem." + } + ], + "items": [ + { + "id": null, + "blockType": "ITEM", + "name": "The asdad", + "description": "asdd" + } + ] + }, + "relationships": [ + { + "sourceContract": "0x05395B5Ea9b9e675DAb0000E4Ef3Cb6901e8d3f1", + "sourceAssetType": "stories", + "sourceAssetIndex": 0, + "destContract": "0x05395B5Ea9b9e675DAb0000E4Ef3Cb6901e8d3f1", + "destAssetType": "characters", + "destAssetIndex": 0, + "data": "0x", + "name": "TEST_RELATIONSHIP", + "ttl": 1, + "sourceId": 42, + "destId": 1000000000042, + "relationshipId": "0x683621362ba100a18914ac9e6e6c9388fdf9e0de20bed62f0b9c36aa49404e7a" } - } \ No newline at end of file + ] +} \ No newline at end of file diff --git a/script/hardhat/sbUploader.js b/script/hardhat/sbUploader.js index 36ef13b6..ddc6570f 100644 --- a/script/hardhat/sbUploader.js +++ b/script/hardhat/sbUploader.js @@ -119,6 +119,82 @@ async function main(args, hre) { }) ).then(() => console.log('Blocks created!')); + console.log('Setting up relationships...') + + const relationshipParams = await Promise.all(data.relationships.map( + async (relationship) => { + const { sourceContract, destContract, ttl, name, sourceAssetType, sourceAssetIndex, destAssetType, destAssetIndex } = relationship + const sourceId = (data.blocks)[sourceAssetType][sourceAssetIndex].id + const destId = (data.blocks)[destAssetType][destAssetIndex].id + const relationshipId = await relationshipModule.getRelationshipId(name); + const params = { + sourceContract, + sourceId, + destContract, + destId, + relationshipId, + ttl + } + return params; + } + )) + + console.log("Uploading " + relationshipParams.length + " relationships...") + const relCalls = relationshipParams.map( + (relationship) => { + return relationshipModule.interface.encodeFunctionData('relate', [relationship, "0x"]) + } + ) + + await Promise.all( + relCalls.chunk(batchSize).map(async (callChunk) => { + console.log('Uploading batch of ', callChunk.length, ' relationships'); + let tx; + try { + tx = await relationshipModule.multicall(callChunk); + } catch (e) { + console.log('ERROR sbUploader'); + console.log('chainId', chainId); + throw new Error(e); + } + console.log('tx: ', tx.hash); + console.log('Waiting for tx to be mined...'); + const receipt = await tx.wait(); + return updateRelationships(ethers, tx.hash, data, filePath, relationshipModule); + }) + ).then(() => console.log('Relationships created!')); +} + +async function updateRelationships(ethers, txHash, data, filePath, relationshipModule) { + const provider = ethers.provider; + const receipt = await provider.getTransactionReceipt(txHash); + + let createdRelationships; + + if (receipt.events) { + createdRelationships = events.filter((e) => e.event === "RelationSet").map((e) => e.args); + } else { + createdRelationships = receipt.logs.map( (log) => { + return relationshipModule.interface.parseLog(log) + }).map((e) => { + const ev = Object.keys(e.args).reduce((acc, key) => { + acc[key] = e.args[key]; + return acc; + }, {}); + return ev; + }) + } + + console.log("Writing relationship information to file...") + data.relationships.forEach((relationship, index) => { + const createdRelationship = createdRelationships[index] + data.relationships[index] = { ...relationship, + sourceId: createdRelationship.sourceId.toNumber(), + destId: createdRelationship.destId.toNumber(), + relationshipId: createdRelationship.relationshipId } + }) + + writeFileSync(filePath, JSON.stringify(data, null, 2)); } async function updateIdsTask(args, hre) { From 170ede03d33e1e176a557f578e36ef7576830623 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 16 Oct 2023 21:02:19 -0700 Subject: [PATCH 2/4] adapted batch uploader --- .../5/run-1697499962.json | 39 +++ .../5/run-1697499975.json | 68 +++++ .../5/run-latest.json | 40 +-- hardhat.config.js | 7 +- script/data/data_example.json | 37 +-- .../setups/SetupFranchiseLicensing.s.sol | 4 +- script/hardhat/batchUploader.js | 260 ++++++++++++++++++ script/hardhat/createIPAsset.js | 8 +- script/hardhat/sbUploader.js | 212 -------------- 9 files changed, 416 insertions(+), 259 deletions(-) create mode 100644 broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499962.json create mode 100644 broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499975.json create mode 100644 script/hardhat/batchUploader.js delete mode 100644 script/hardhat/sbUploader.js diff --git a/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499962.json b/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499962.json new file mode 100644 index 00000000..309b600e --- /dev/null +++ b/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499962.json @@ -0,0 +1,39 @@ +{ + "transactions": [ + { + "hash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", + "function": "configureFranchiseLicensing(uint256,((bool,uint256),(address,bytes),(bool,uint256),(address,bytes),bool,address,string))", + "arguments": [ + "250", + "((true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), (true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), false, 0xB6288e57bf7406B35ab4F70Fd1135E907107e386, https://commercial.license)" + ], + "rpc": "https://eth-goerli.g.alchemy.com/v2/7WyRjkZW0XJmt7tb4OqO9YTp5z_iVl-o", + "transaction": { + "type": "0x02", + "from": "0xb6288e57bf7406b35ab4f70fd1135e907107e386", + "to": "0x08341162ea337b086dca69e13ecf76f868f7025a", + "gas": "0x52377", + "value": "0x0", + "data": "0x4f0bf5b600000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6288e57bf7406b35ab4f70fd1135e907107e38600000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", + "nonce": "0x7b", + "accessList": [] + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [], + "libraries": [], + "pending": [ + "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa" + ], + "path": "/Users/drmanhattan/Workspace/StoryProtocol/protocol-contracts/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json", + "returns": {}, + "timestamp": 1697499962, + "chain": 5, + "multi": false, + "commit": "4d5657c" +} \ No newline at end of file diff --git a/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499975.json b/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499975.json new file mode 100644 index 00000000..3324fd95 --- /dev/null +++ b/broadcast/SetupFranchiseLicensing.s.sol/5/run-1697499975.json @@ -0,0 +1,68 @@ +{ + "transactions": [ + { + "hash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionType": "CALL", + "contractName": null, + "contractAddress": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", + "function": "configureFranchiseLicensing(uint256,((bool,uint256),(address,bytes),(bool,uint256),(address,bytes),bool,address,string))", + "arguments": [ + "250", + "((true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), (true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), false, 0xB6288e57bf7406B35ab4F70Fd1135E907107e386, https://commercial.license)" + ], + "rpc": "https://eth-goerli.g.alchemy.com/v2/7WyRjkZW0XJmt7tb4OqO9YTp5z_iVl-o", + "transaction": { + "type": "0x02", + "from": "0xb6288e57bf7406b35ab4f70fd1135e907107e386", + "to": "0x08341162ea337b086dca69e13ecf76f868f7025a", + "gas": "0x52377", + "value": "0x0", + "data": "0x4f0bf5b600000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6288e57bf7406b35ab4f70fd1135e907107e38600000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", + "nonce": "0x7b", + "accessList": [] + }, + "additionalContracts": [], + "isFixedGasLimit": false + } + ], + "receipts": [ + { + "transactionHash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionIndex": "0x1", + "blockHash": "0x172e87500b70c741f972f48644b58c753d80377ea24f3c8867890de073770fc7", + "blockNumber": "0x96becb", + "from": "0xB6288e57bf7406B35ab4F70Fd1135E907107e386", + "to": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", + "cumulativeGasUsed": "0x40a68", + "gasUsed": "0x3b860", + "contractAddress": null, + "logs": [ + { + "address": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", + "topics": [ + "0xe6fbfbfdd71227edcd5ffab5dbc0fac8662dffc1be715c591efb2214665b9de5" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6288e57bf7406b35ab4f70fd1135e907107e38600000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", + "blockHash": "0x172e87500b70c741f972f48644b58c753d80377ea24f3c8867890de073770fc7", + "blockNumber": "0x96becb", + "transactionHash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionIndex": "0x1", + "logIndex": "0x0", + "removed": false + } + ], + "status": "0x1", + "logsBloom": "0x02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "effectiveGasPrice": "0xb2d05e08" + } + ], + "libraries": [], + "pending": [], + "path": "/Users/drmanhattan/Workspace/StoryProtocol/protocol-contracts/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json", + "returns": {}, + "timestamp": 1697499975, + "chain": 5, + "multi": false, + "commit": "4d5657c" +} \ No newline at end of file diff --git a/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json b/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json index 0fcf512a..3324fd95 100644 --- a/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json +++ b/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json @@ -1,24 +1,24 @@ { "transactions": [ { - "hash": "0xdfc2808ada975f924666be3e2f660359651b6270bdc83ad20cf3afcb1f0487ae", + "hash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", "transactionType": "CALL", "contractName": null, "contractAddress": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", "function": "configureFranchiseLicensing(uint256,((bool,uint256),(address,bytes),(bool,uint256),(address,bytes),bool,address,string))", "arguments": [ - "67", - "((true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), (true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), false, 0x0000000000000000000000000000000000000001, https://commercial.license)" + "250", + "((true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), (true, 0), (0x0000000000000000000000000000000000000000, 0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000), false, 0xB6288e57bf7406B35ab4F70Fd1135E907107e386, https://commercial.license)" ], "rpc": "https://eth-goerli.g.alchemy.com/v2/7WyRjkZW0XJmt7tb4OqO9YTp5z_iVl-o", "transaction": { "type": "0x02", "from": "0xb6288e57bf7406b35ab4f70fd1135e907107e386", "to": "0x08341162ea337b086dca69e13ecf76f868f7025a", - "gas": "0x5223b", + "gas": "0x52377", "value": "0x0", - "data": "0x4f0bf5b6000000000000000000000000000000000000000000000000000000000000004300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", - "nonce": "0x69", + "data": "0x4f0bf5b600000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6288e57bf7406b35ab4f70fd1135e907107e38600000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", + "nonce": "0x7b", "accessList": [] }, "additionalContracts": [], @@ -27,14 +27,14 @@ ], "receipts": [ { - "transactionHash": "0xdfc2808ada975f924666be3e2f660359651b6270bdc83ad20cf3afcb1f0487ae", - "transactionIndex": "0x0", - "blockHash": "0x5a1e8597583953c1a0a0f5e52ed899153ab934101f67529ef5a8913ee395a853", - "blockNumber": "0x964939", + "transactionHash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionIndex": "0x1", + "blockHash": "0x172e87500b70c741f972f48644b58c753d80377ea24f3c8867890de073770fc7", + "blockNumber": "0x96becb", "from": "0xB6288e57bf7406B35ab4F70Fd1135E907107e386", "to": "0x08341162Ea337B086dCa69E13ECf76F868F7025A", - "cumulativeGasUsed": "0x3b77c", - "gasUsed": "0x3b77c", + "cumulativeGasUsed": "0x40a68", + "gasUsed": "0x3b860", "contractAddress": null, "logs": [ { @@ -42,11 +42,11 @@ "topics": [ "0xe6fbfbfdd71227edcd5ffab5dbc0fac8662dffc1be715c591efb2214665b9de5" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000004300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", - "blockHash": "0x5a1e8597583953c1a0a0f5e52ed899153ab934101f67529ef5a8913ee395a853", - "blockNumber": "0x964939", - "transactionHash": "0xdfc2808ada975f924666be3e2f660359651b6270bdc83ad20cf3afcb1f0487ae", - "transactionIndex": "0x0", + "data": "0x00000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b6288e57bf7406b35ab4f70fd1135e907107e38600000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a68747470733a2f2f636f6d6d65726369616c2e6c6963656e7365000000000000", + "blockHash": "0x172e87500b70c741f972f48644b58c753d80377ea24f3c8867890de073770fc7", + "blockNumber": "0x96becb", + "transactionHash": "0xd489222a56f1e29887a4d0507d9470e7c4b11f5ae2e71f6d16ed382d20cd13fa", + "transactionIndex": "0x1", "logIndex": "0x0", "removed": false } @@ -54,15 +54,15 @@ "status": "0x1", "logsBloom": "0x02000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "type": "0x2", - "effectiveGasPrice": "0xb2d05e0a" + "effectiveGasPrice": "0xb2d05e08" } ], "libraries": [], "pending": [], "path": "/Users/drmanhattan/Workspace/StoryProtocol/protocol-contracts/broadcast/SetupFranchiseLicensing.s.sol/5/run-latest.json", "returns": {}, - "timestamp": 1697045099, + "timestamp": 1697499975, "chain": 5, "multi": false, - "commit": "a2f2e1c" + "commit": "4d5657c" } \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index 6c6f3bd6..d8e1ed4b 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -14,7 +14,7 @@ const configureFranchiseLicensing = require("./script/hardhat/configureFranchise const createIPAsset = require("./script/hardhat/createIPAsset.js"); const getIPAssetRegistryAddress = require("./script/hardhat/getIPAssetRegistryAddress.js"); const getIPAsset = require("./script/hardhat/getIPAsset.js"); -const sbUploader = require("./script/hardhat/sbUploader.js"); +const batchUploader = require("./script/hardhat/batchUploader.js"); const namespacedStorageKey = require("./script/hardhat/namespacedStorageKey.js"); const { task } = require("hardhat/config"); @@ -73,17 +73,18 @@ task('sp:read-ip-asset') task('sp:uploader') .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Assets in, as given by FranchiseRegistry contract') + .addPositionalParam('receiver', 'Address that will receive the IP Assets') .addPositionalParam('filePath', 'path to the Json data') .addOptionalParam('batchSize', 'Number of blocks to upload in each batch', 100, types.int) .setDescription('Mass upload IP Assets from a Json file') - .setAction(sbUploader); + .setAction(batchUploader); task('sp:update-ip-assets') .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Assets in, as given by FranchiseRegistry contract') .addPositionalParam('tx', 'tx hash that created blocks') .addPositionalParam('filePath', 'path to the Json data') .setDescription('Update ids for blocks in the Json file') - .setAction(sbUploader.updateIds); + .setAction(batchUploader.updateIds); task('sp:eip7201-key') .addPositionalParam('namespace', 'Namespace, for example erc7201:example.main') diff --git a/script/data/data_example.json b/script/data/data_example.json index faab35b2..cd6f5811 100644 --- a/script/data/data_example.json +++ b/script/data/data_example.json @@ -1,64 +1,65 @@ { - "blocks": { + "ip-assets": { "stories": [ { "id": null, - "blockType": "STORY", + "ipAssetType": "STORY", "name": "The Full Grain World Bible", "description": "Lorem Ipsum", - "author": "Sebastian Sinclair", - "content": "Lorem ipsum dolor sit amet consectetur adipiscing elit ullamcorper venenatis, habitasse egestas luctus penatibus eleifend at litora iaculis risus, cum quis habitant facilisi vel eu sapien taciti. Imperdiet id tincidunt erat primis sociosqu odio, nullam fusce bibendum arcu class lacus, purus cum condimentum sem varius. Magna risus scelerisque ullamcorper himenaeos et maecenas nibh erat donec, tellus vivamus elementum leo nascetur dui in litora. Malesuada nascetur eu elementum urna placerat ridiculus, nisi phasellus faucibus penatibus vivamus, fermentum ut interdum volutpat mauris.\n\n Mi himenaeos massa netus lacinia ac platea penatibus quisque felis sapien, ante ad libero faucibus sed sagittis egestas odio inceptos ridiculus, primis metus nunc sociis eros eleifend ultricies tincidunt taciti. Eu litora cursus penatibus massa cubilia fames pellentesque, curabitur nascetur consequat placerat nulla viverra egestas, ridiculus tellus class in lacinia maecenas.\n \n Eros dignissim sollicitudin torquent posuere litora neque et dui rutrum metus eleifend, sociis facilisi in eget varius netus praesent potenti urna curae. Elementum tempus feugiat suscipit blandit hendrerit placerat cum aliquet curabitur ultrices tempor, tincidunt neque lobortis condimentum cras ad nisi consequat sagittis primis. Curae nullam magna semper himenaeos penatibus potenti vestibulum nostra, magnis venenatis scelerisque mi rhoncus condimentum urna, diam a curabitur aliquam fames sociosqu tincidunt.\n\nViverra lobortis rutrum eget erat ullamcorper vestibulum praesent vivamus torquent, laoreet curabitur aptent dui vel ad ac eu egestas pharetra, turpis bibendum libero faucibus lacinia sociosqu ultricies consequat. Per nec eget hendrerit gravida urna in semper, sociis accumsan facilisi laoreet scelerisque torquent, ad est pharetra faucibus luctus nisl. Urna sociis ut lacus pharetra dis ullamcorper ad arcu, erat aenean congue integer fames blandit purus. Suspendisse condimentum facilisi aliquam at varius velit quis, dictum ullamcorper dapibus cum posuere nec pellentesque, porta laoreet tempus cursus porttitor sollicitudin.\n\nAuctor senectus praesent nostra porttitor platea dapibus phasellus vitae enim tristique venenatis laoreet, vehicula eleifend fusce metus lectus massa facilisis fames class neque eros. Libero magna eros posuere velit faucibus laoreet suscipit nascetur, etiam montes consequat phasellus aptent porttitor ante platea, leo lacinia molestie justo congue malesuada netus. Blandit primis facilisi vivamus scelerisque iaculis sociosqu per quisque odio lacus auctor tempor, sodales mauris donec non magnis nisi sollicitudin nulla semper senectus.\n \n Vel cursus fringilla venenatis ligula ridiculus dictum est libero gravida pharetra, nulla interdum netus laoreet mus dictumst magna class dapibus. Vulputate mauris congue leo nec nisi cum fermentum platea, purus non dis varius erat curabitur dapibus ligula, curae eros tellus venenatis vestibulum fusce himenaeos. Consequat dictumst convallis natoque donec curabitur lacus malesuada, leo a suspendisse mi nostra phasellus quam iaculis, mattis eleifend vulputate ut mollis vehicula." + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ], "groups": [ { "id": null, - "blockType": "GROUP", + "ipAssetType": "GROUP", "name": "The Fresh", "description": "Human Like", - "groupingBlockType": "GROUP", - "ids": [] + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ], "characters": [ { "id": null, - "blockType": "CHARACTER", + "ipAssetType": "CHARACTER", "name": "Captain Crunch", - "description": "Lorem Ipsum" + "description": "Lorem Ipsum", + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ], "locations": [ { "id": null, - "blockType": "LOCATION", + "ipAssetType": "LOCATION", "name": "Weird Homeworld", - "description": "Lorem Lorem." + "description": "Lorem Lorem.", + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ], "items": [ { "id": null, - "blockType": "ITEM", + "ipAssetType": "ITEM", "name": "The asdad", - "description": "asdd" + "description": "asdd", + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ] }, "relationships": [ { - "sourceContract": "0x05395B5Ea9b9e675DAb0000E4Ef3Cb6901e8d3f1", + "sourceContract": "same", "sourceAssetType": "stories", "sourceAssetIndex": 0, - "destContract": "0x05395B5Ea9b9e675DAb0000E4Ef3Cb6901e8d3f1", + "destContract": "same", "destAssetType": "characters", "destAssetIndex": 0, "data": "0x", "name": "TEST_RELATIONSHIP", "ttl": 1, - "sourceId": 42, - "destId": 1000000000042, - "relationshipId": "0x683621362ba100a18914ac9e6e6c9388fdf9e0de20bed62f0b9c36aa49404e7a" + "sourceId": null, + "destId": null, + "relationshipId": null } ] } \ No newline at end of file diff --git a/script/foundry/setups/SetupFranchiseLicensing.s.sol b/script/foundry/setups/SetupFranchiseLicensing.s.sol index c59d2622..09878b4f 100644 --- a/script/foundry/setups/SetupFranchiseLicensing.s.sol +++ b/script/foundry/setups/SetupFranchiseLicensing.s.sol @@ -15,9 +15,9 @@ contract SetupFranchiseLicensing is Script, BroadcastManager, JsonDeploymentHand LicensingModule licensingModule; // ********* EDIT THESE FOR CONFIG ********* - uint constant FRANCHISE_ID = 67; + uint constant FRANCHISE_ID = 250; - address constant REVOKER_ADDRESS = address(0x1); + address constant REVOKER_ADDRESS = address(0xB6288e57bf7406B35ab4F70Fd1135E907107e386); bool constant ROOT_IP_ASSET_HAS_COMMERCIAL_RIGHTS = false; bool constant NON_COMMERCIAL_SUBLICENSING = true; diff --git a/script/hardhat/batchUploader.js b/script/hardhat/batchUploader.js new file mode 100644 index 00000000..d5c31360 --- /dev/null +++ b/script/hardhat/batchUploader.js @@ -0,0 +1,260 @@ +const loadDeployment = require('./loadDeployment.js'); +const { getIPAssetRegistryAddress } = require('./getIPAssetRegistryAddress.js'); +const { createIPAsset, validateIPAssetType } = require('./createIPAsset.js'); +const { readFileSync, writeFileSync } = require('fs'); + +////// ARRAYS ////// + +Array.range = function (n) { + return Array(n) + .fill() + .map((_, i) => i); +}; + +Array.prototype.chunk = function (size) { + return Array.range(Math.ceil(this.length / size)).map((i) => this.slice(i * size, i * size + size)); +}; + +////// IP ASSETS ////// + +function getCreatedIpAssets(receipt, ipAssetRegistry) { + if (receipt.events) { + return events.filter((e) => e.event === "IPAssetWritten").map((e) => e.args); + } else { + const events = receipt.logs + .filter(log => log.topics[0] === ipAssetRegistry.interface.getEventTopic("IPAssetWritten")) + .map((log) => { + return ipAssetRegistry.interface.parseLog(log); + }).filter((e) => e.name === "IPAssetWritten") + .map((e) => { + const ev = Object.keys(e.args).reduce((acc, key) => { + acc[key] = e.args[key]; + return acc; + }, {}); + return ev; + }) + .map((e) => { + e.id = e.IPAssetId.toNumber(); + return e; + }); + return events; + } +} + +const mapIpAssets = (jsonIpAssets, createdIpAssets) => { + return jsonIpAssets.map((jIpa) => { + const newIpa = createdIpAssets.find((cIpa) => cIpa.name === jIpa.name); + if (newIpa) { + console.log('Found!') + return { ...jIpa, id: newIpa.id }; + } else { + console.log('Not found...') + return jIpa; + } + }); +} + +const ipAssetNumberToJSONKey = (ipAssetType) => { + switch (ipAssetType) { + case 1: + return "stories"; + case 2: + return "characters"; + case 3: + return "art"; + case 4: + return "groups"; + case 5: + return "locations"; + case 6: + return "items"; + default: + throw new Error("Invalid story block type: " + ipAssetType); + } +} + + +const updateFileData = (data, ipAssetType, createdIpAssets) => { + console.log(createdIpAssets) + const assetsOfThisType = createdIpAssets.filter((ipAsset) => ipAsset.blockType === ipAssetType); + console.log("assetsOfThisType: ", assetsOfThisType); + const key = ipAssetNumberToJSONKey(ipAssetType); + console.log("key: ", key); + if (assetsOfThisType.length > 0) { + data['ip-assets'][key] = mapIpAssets(data['ip-assets'][key], assetsOfThisType); + + } +} + +async function updateIds(ethers, txHash, data, filePath, ipAssetRegistry) { + const provider = ethers.provider; + const receipt = await provider.getTransactionReceipt(txHash); + + const createdIpAssets = getCreatedIpAssets(receipt, ipAssetRegistry); + console.log(createdIpAssets) + + updateFileData(data, 1, createdIpAssets); + updateFileData(data, 2, createdIpAssets); + updateFileData(data, 3, createdIpAssets); + updateFileData(data, 4, createdIpAssets); + updateFileData(data, 5, createdIpAssets); + updateFileData(data, 6, createdIpAssets); + + writeFileSync(filePath, JSON.stringify(data, null, 2)); +} + +async function batchUploadIpAssets(data, contracts, ipRegistryAddress, receiver, batchSize, chainId, ethers, filePath) { + const ipAssets = Object.keys(data['ip-assets']) + .map((key) => data['ip-assets'][key]) + .reduce((acc, val) => acc.concat(val), []) + .map((ipAsset) => { return { ...ipAsset, ipAssetType: validateIPAssetType(ipAsset.ipAssetType) }; }) + .filter((ipAsset) => ipAsset.id === null); + + console.log("Will upload: ", ipAssets.length, "story blocks"); + + const ipAssetRegistry = await contracts.IPAssetsRegistry.attach(ipRegistryAddress); + + const calls = ipAssets.map((ipAsset) => { + return ipAssetRegistry.interface.encodeFunctionData( + 'createIPAsset', + [ + ipAsset.ipAssetType, + ipAsset.name, + ipAsset.description, + ipAsset.mediaURL ?? '', + receiver, + 0 + ] + ); + }); + + console.log('Batches: ', Math.ceil(calls.length / batchSize)); + + await Promise.all( + calls.chunk(batchSize).map(async (callChunk) => { + console.log('Uploading batch of ', callChunk.length, ' story blocks'); + let tx; + try { + tx = await ipAssetRegistry.multicall(callChunk); + } catch (e) { + console.log('ERROR batchUploader'); + console.log('chainId', chainId); + throw new Error(e); + } + console.log('tx: ', tx.hash); + console.log('Waiting for tx to be mined...'); + const receipt = await tx.wait(); + return updateIds(ethers, tx.hash, data, filePath, ipAssetRegistry); + }) + ).then(() => console.log('IpAssets created!')); +} + +////// RELATIONSHIPS ////// + +async function batchUploadRelationships(data, contracts, ipaRegistryAddress, batchSize, chainId, ethers, filePath) { + const relationshipModule = await contracts.protocolRelationshipModule; + const relationshipParams = await Promise.all(data.relationships.map( + async (relationship) => { + const { sourceContract, destContract, ttl, name, sourceAssetType, sourceAssetIndex, destAssetType, destAssetIndex } = relationship; + const sourceId = (data['ip-assets'])[sourceAssetType][sourceAssetIndex].id; + const destId = (data['ip-assets'])[destAssetType][destAssetIndex].id; + const relationshipId = await relationshipModule.getRelationshipId(name); + const params = { + sourceContract: sourceContract === "same" ? ipaRegistryAddress : sourceContract, + sourceId, + destContract: destContract === "same" ? ipaRegistryAddress : destContract, + destId, + relationshipId, + ttl + }; + return params; + } + )); + + console.log(relationshipParams) + + console.log("Uploading " + relationshipParams.length + " relationships..."); + const relCalls = relationshipParams.map( + (relationship) => { + return relationshipModule.interface.encodeFunctionData('relate', [relationship, "0x"]); + } + ); + + await Promise.all( + relCalls.chunk(batchSize).map(async (callChunk) => { + console.log('Uploading batch of ', callChunk.length, ' relationships'); + let tx; + try { + tx = await relationshipModule.multicall(callChunk); + } catch (e) { + console.log('ERROR batchUploader'); + console.log('chainId', chainId); + throw new Error(e); + } + console.log('tx: ', tx.hash); + console.log('Waiting for tx to be mined...'); + const receipt = await tx.wait(); + return updateRelationships(receipt, data, filePath, relationshipModule); + }) + ).then(() => console.log('Relationships created!')); +} + +async function updateRelationships(receipt, data, filePath, relationshipModule) { + let createdRelationships; + + if (receipt.events) { + createdRelationships = receipt.events.filter((e) => e.event === "RelationSet").map((e) => e.args); + } else { + createdRelationships = receipt.logs.map((log) => { + return relationshipModule.interface.parseLog(log) + }).map((e) => { + const ev = Object.keys(e.args).reduce((acc, key) => { + acc[key] = e.args[key]; + return acc; + }, {}); + return ev; + }) + } + + console.log("Writing relationship information to file...") + data.relationships.forEach((relationship, index) => { + const createdRelationship = createdRelationships[index] + data.relationships[index] = { + ...relationship, + sourceId: createdRelationship.sourceId.toNumber(), + destId: createdRelationship.destId.toNumber(), + relationshipId: createdRelationship.relationshipId + } + }) + + writeFileSync(filePath, JSON.stringify(data, null, 2)); +} + +////// EXPORTED METHODS ////// + +async function main(args, hre) { + const { ethers } = hre; + const { chainId, contracts } = await loadDeployment(hre); + const { franchiseId, receiver, filePath, batchSize } = args; + const data = JSON.parse(readFileSync(filePath, 'utf8')); + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); + + await batchUploadIpAssets(data, contracts, address, receiver, batchSize, chainId, ethers, filePath); + + console.log('Setting up relationships...') + + await batchUploadRelationships(data, contracts, address, batchSize, chainId, ethers, filePath); +} + +async function updateIdsTask(args, hre) { + const { ethers } = hre; + const { franchiseId, tx, filePath } = args; + const data = JSON.parse(readFileSync(filePath, 'utf8')); + const { chainId, contracts } = await loadDeployment(hre); + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); + const ipAssetRegistry = await contracts.IPAssetsRegistry.attach(address); + await updateIds(ethers, tx, data, filePath, ipAssetRegistry); +} + +module.exports = main; +module.exports.updateIds = updateIdsTask; diff --git a/script/hardhat/createIPAsset.js b/script/hardhat/createIPAsset.js index 582e322f..28911714 100644 --- a/script/hardhat/createIPAsset.js +++ b/script/hardhat/createIPAsset.js @@ -6,9 +6,9 @@ function findId(events) { return event.args.tokenId.toString() } -function validateIPAssetType(IPAssetType) { - console.log("IPAssetType: ", IPAssetType); - switch (IPAssetType) { +function validateIPAssetType(ipAssetType) { + console.log("IPAssetType: ", ipAssetType); + switch (ipAssetType) { case "STORY": return 1; case "CHARACTER": @@ -22,7 +22,7 @@ function validateIPAssetType(IPAssetType) { case "ITEM": return 6; default: - throw new Error("Invalid story block type: " + IPAssetType); + throw new Error("Invalid story block type: " + ipAssetType); } } diff --git a/script/hardhat/sbUploader.js b/script/hardhat/sbUploader.js deleted file mode 100644 index ddc6570f..00000000 --- a/script/hardhat/sbUploader.js +++ /dev/null @@ -1,212 +0,0 @@ -const loadDeployment = require('./loadDeployment.js'); -const { getIPAssetRegistryAddress } = require('./getIPAssetRegistryAddress.js'); -const { createIPAsset, validateIPAssetType } = require('./createIPAsset.js'); -const { readFileSync, writeFileSync } = require('fs'); - -Array.range = function (n) { - return Array(n) - .fill() - .map((_, i) => i); -}; - -Array.prototype.chunk = function (size) { - return Array.range(Math.ceil(this.length / size)).map((i) => this.slice(i * size, i * size + size)); -}; - -function getCreatedBlocks(receipt, IPAssetRegistry) { - if (receipt.events) { - return events.filter((e) => e.event === "IPAssetWritten").map((e) => e.args); - } else { - const events = receipt.logs.map((log) => { - return IPAssetRegistry.interface.parseLog(log); - }).filter((e) => e.name === "IPAssetWritten") - .map((e) => { - const ev = Object.keys(e.args).reduce((acc, key) => { - acc[key] = e.args[key]; - return acc; - }, {}); - return ev; - }) - .map((e) => { - e.id = e.IPAssetId.toNumber(); - return e; - }); - return events; - } -} - -async function updateIds(ethers, txHash, data, filePath, IPAssetRegistry) { - const provider = ethers.provider; - const receipt = await provider.getTransactionReceipt(txHash); - - const createdBlocks = getCreatedBlocks(receipt, IPAssetRegistry); - - const mapBlocks = (blocks, createdBlocks) => { - return blocks.map((b) => { - const createdBlock = createdBlocks.find((s) => s.name === b.name); - if (createdBlock) { - return { ...b, id: createdBlock.id }; - } else { - return b; - } - }); - } - const createdStories = createdBlocks.filter((block) => block.blockType === 1); - if (createdStories.length > 0) { - data.blocks.stories = mapBlocks(data.blocks.stories, createdStories); - } - const createdCharacters = createdBlocks.filter((block) => block.blockType === 2); - if (createdCharacters.length > 0) { - data.blocks.characters = mapBlocks(data.blocks.characters, createdCharacters); - } - const createdArts = createdBlocks.filter((block) => block.blockType === 3); - if (createdArts.length > 0) { - data.blocks.arts = mapBlocks(data.blocks.arts, createdArts); - } - const createdGroups = createdBlocks.filter((block) => block.blockType === 4); - if (createdGroups.length > 0) { - data.blocks.groups = mapBlocks(data.blocks.groups, createdGroups); - } - const createdLocations = createdBlocks.filter((block) => block.blockType === 5); - if (createdLocations.length > 0) { - data.blocks.locations = mapBlocks(data.blocks.locations, createdLocations); - } - const createdItems = createdBlocks.filter((block) => block.blockType === 6); - if (createdItems.length > 0) { - data.blocks.items = mapBlocks(data.blocks.items, createdItems); - } - writeFileSync(filePath, JSON.stringify(data, null, 2)); -} - - - -async function main(args, hre) { - const { ethers } = hre; - const { chainId, contracts } = await loadDeployment(hre); - const { franchiseId, filePath, batchSize } = args; - const data = JSON.parse(readFileSync(filePath, 'utf8')); - const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); - - const blocks = Object.keys(data.blocks) - .map((key) => data.blocks[key]) - .reduce((acc, val) => acc.concat(val), []) - .map((block) => { return { ...block, numBlockType: validateIPAssetType(block.blockType) }}) - .filter((block) => block.id === null); - console.log("Will upload: ", blocks.length, "story blocks"); - - const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); - const calls = blocks.map((block) => { - return IPAssetRegistry.interface.encodeFunctionData('createIPAsset', [block.numBlockType, block.name, block.description, block.mediaURL ?? '']) - }); - - console.log('Batches: ', Math.ceil(calls.length / batchSize)); - - await Promise.all( - calls.chunk(batchSize).map(async (callChunk) => { - console.log('Uploading batch of ', callChunk.length, ' story blocks'); - let tx; - try { - tx = await IPAssetRegistry.multicall(callChunk); - } catch (e) { - console.log('ERROR sbUploader'); - console.log('chainId', chainId); - throw new Error(e); - } - console.log('tx: ', tx.hash); - console.log('Waiting for tx to be mined...'); - const receipt = await tx.wait(); - return updateIds(ethers, tx.hash, data, filePath, IPAssetRegistry); - }) - ).then(() => console.log('Blocks created!')); - - console.log('Setting up relationships...') - - const relationshipParams = await Promise.all(data.relationships.map( - async (relationship) => { - const { sourceContract, destContract, ttl, name, sourceAssetType, sourceAssetIndex, destAssetType, destAssetIndex } = relationship - const sourceId = (data.blocks)[sourceAssetType][sourceAssetIndex].id - const destId = (data.blocks)[destAssetType][destAssetIndex].id - const relationshipId = await relationshipModule.getRelationshipId(name); - const params = { - sourceContract, - sourceId, - destContract, - destId, - relationshipId, - ttl - } - return params; - } - )) - - console.log("Uploading " + relationshipParams.length + " relationships...") - const relCalls = relationshipParams.map( - (relationship) => { - return relationshipModule.interface.encodeFunctionData('relate', [relationship, "0x"]) - } - ) - - await Promise.all( - relCalls.chunk(batchSize).map(async (callChunk) => { - console.log('Uploading batch of ', callChunk.length, ' relationships'); - let tx; - try { - tx = await relationshipModule.multicall(callChunk); - } catch (e) { - console.log('ERROR sbUploader'); - console.log('chainId', chainId); - throw new Error(e); - } - console.log('tx: ', tx.hash); - console.log('Waiting for tx to be mined...'); - const receipt = await tx.wait(); - return updateRelationships(ethers, tx.hash, data, filePath, relationshipModule); - }) - ).then(() => console.log('Relationships created!')); -} - -async function updateRelationships(ethers, txHash, data, filePath, relationshipModule) { - const provider = ethers.provider; - const receipt = await provider.getTransactionReceipt(txHash); - - let createdRelationships; - - if (receipt.events) { - createdRelationships = events.filter((e) => e.event === "RelationSet").map((e) => e.args); - } else { - createdRelationships = receipt.logs.map( (log) => { - return relationshipModule.interface.parseLog(log) - }).map((e) => { - const ev = Object.keys(e.args).reduce((acc, key) => { - acc[key] = e.args[key]; - return acc; - }, {}); - return ev; - }) - } - - console.log("Writing relationship information to file...") - data.relationships.forEach((relationship, index) => { - const createdRelationship = createdRelationships[index] - data.relationships[index] = { ...relationship, - sourceId: createdRelationship.sourceId.toNumber(), - destId: createdRelationship.destId.toNumber(), - relationshipId: createdRelationship.relationshipId } - }) - - writeFileSync(filePath, JSON.stringify(data, null, 2)); -} - -async function updateIdsTask(args, hre) { - const { ethers } = hre; - const { franchiseId, tx, filePath } = args; - const data = JSON.parse(readFileSync(filePath, 'utf8')); - const { chainId, contracts } = await loadDeployment(hre); - const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); - const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); - await updateIds(ethers, tx, data, filePath, IPAssetRegistry); -} - - -module.exports = main; -module.exports.updateIds = updateIdsTask; From 2af70dbcad7b450d46d15526115078c466a14103 Mon Sep 17 00:00:00 2001 From: brentju <68607591+brentju@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:24:04 -0700 Subject: [PATCH 3/4] Update README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 03688986..698d1771 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,50 @@ Read Story Block npx hardhat sp:read-ip-asset--network ``` +Upload Story Blocks and Relationships + +This section requires more detail to establish how to format an input JSON data file. In the file, the data should be separated under "blocks" and "relationships". The blocks are stored in arrays and represented as JSON objects. For each story block, the following fields are required: +``` +// example: +stories: [ +// this is an individual story to be uploaded. +{ + id: null, + blockType: enum, + name: string, + description: string, + ... +} +] +``` + +The ID is initially set to null to identify a block that is yet to be uploaded; the file will be rewritten once blocks are successfully uploaded. + +To upload a relationship, the following fields are needed: + +``` +relationships: [ +{ + sourceContract: 0x... + destContract: 0x... + data, + name, + ttl, + sourceAssetType: {type of src IP asset: stories, characters, etc.}, + destAssetType: {type of destination IP asset: stories, characters, etc.} + sourceAssetIndex: int, index of desired asset in the array specified from above sourceAssetType + destAssetIndex: int, index of desired asset in the array specified from above destAssetType +} +] +``` + +To call the task, use the following: +``` +npx hardhat --network -sp:uploader +``` + + + ### Working with a local network Foundry comes with local network [anvil](https://book.getfoundry.sh/anvil/index.html) baked in, and allows us to deploy to our local network for quick testing locally. From 61d9543e5574e0ccbcb1aec3c557d956a6c581ec Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 17 Oct 2023 16:38:21 -0700 Subject: [PATCH 4/4] updated readme instructions --- README.md | 53 +++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 698d1771..efc2724d 100644 --- a/README.md +++ b/README.md @@ -264,48 +264,53 @@ npx hardhat sp:read-ip-asset--network Upload Story Blocks and Relationships -This section requires more detail to establish how to format an input JSON data file. In the file, the data should be separated under "blocks" and "relationships". The blocks are stored in arrays and represented as JSON objects. For each story block, the following fields are required: +This section requires more detail to establish how to format an input JSON data file. In the file, the data should be separated under "ip-assets" and "relationships". + +The IP Assets are stored in arrays and represented as JSON objects. For each story block, the following fields are required: ``` // example: stories: [ // this is an individual story to be uploaded. { - id: null, - blockType: enum, - name: string, - description: string, - ... + "id": null, + "ipAssetType": "STORY", + "name": "The Full Grain World Bible", + "description": "Lorem Ipsum", + "mediaURL": "https://www.youtube.com/watch?v=9bZkp7q19f0" } ] ``` -The ID is initially set to null to identify a block that is yet to be uploaded; the file will be rewritten once blocks are successfully uploaded. +The ID is initially set to null to identify an IPAsset that is yet to be uploaded; the file will be rewritten once blocks are successfully uploaded. This means, once the script finished if some batch txs failed, running the script again will only try to upload the failed IP Assets (since it skips ids that are not null) -To upload a relationship, the following fields are needed: +To upload relationships, the following fields are needed: ``` -relationships: [ -{ - sourceContract: 0x... - destContract: 0x... - data, - name, - ttl, - sourceAssetType: {type of src IP asset: stories, characters, etc.}, - destAssetType: {type of destination IP asset: stories, characters, etc.} - sourceAssetIndex: int, index of desired asset in the array specified from above sourceAssetType - destAssetIndex: int, index of desired asset in the array specified from above destAssetType -} -] +"relationships": [ + { + "sourceContract": "same", // Address of the source contract, or "same" if it's the franchise passed as parameter + "sourceAssetType": "stories", // key for the source IPA data array in the JSON + "sourceAssetIndex": 0, // Index of the source IPA in the array correspondent to the key above + "destContract": "same", // Address of the destination contract, or "same" if it's the franchise passed as parameter + "destAssetType": "characters", // key for the destination IPA data array in the JSON + "destAssetIndex": 0, // Index of the destination IPA in the array correspondent to the key above + "data": "0x", // Hook params, if any + "name": "TEST_RELATIONSHIP", // Name of the relationship, as per SPIP + "ttl": 1, // Int, duration in seconds of the relationship, in case it can be time limited. Ignored otherwise + "sourceId": null, //null in the beginning since the IPAs are not uploaded, will be set by the script + "destId": null, //null in the beginning since the IPAs are not uploaded, will be set by the script + "relationshipId": null //null in the beginning since relationship is unset, it will be the hash of name. + } + ] ``` +[The full example JSON is in script/data/data_example.json](/script/data/data_example.json) + To call the task, use the following: ``` -npx hardhat --network -sp:uploader +npx hardhat --network -sp:uploader
--batchSize ``` - - ### Working with a local network Foundry comes with local network [anvil](https://book.getfoundry.sh/anvil/index.html) baked in, and allows us to deploy to our local network for quick testing locally.