From 636a3f97dbbe5124eb62fceff4991d0126c48057 Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Wed, 15 May 2024 16:11:00 -0300 Subject: [PATCH 1/5] document dynamic primitive (erc-721) --- .../10-evm/5-dynamic-erc721.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md diff --git a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md new file mode 100644 index 00000000..0f83f221 --- /dev/null +++ b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md @@ -0,0 +1,53 @@ +# Dynamic ERC721 + +This primitive allows registering new erc721 extensions in runtime, triggered by +a generic smart contract event. + +## Configuration + +```yaml +# extensions.yaml +extensions: + - name: "Dynamic erc721" + type: "dynamic-primitive" + startBlockHeight: 100 + contractAddress: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + scheduledPrefix: "derc721" + burnScheduledPrefix: "bderc721" + eventSignature: "CustomEvent(address)" + abiPath: "./CustomEventEmitter.json" + targetType: "erc721" + fields: + contractAddress: address +``` + +### Meaning + +- `contractAdress` is the address of the smart contract that will emit the event that will trigger the erc721 primitive creation. +- `scheduledPrefix` and `burnScheduledPrefix` are the prefixes used for the +resulting erc721 events. This primitive won't emit events by itself. See [the +erc721 section for details](3-ERC721.md#meaning). +- `abiPath`, specifying a path to a .json file describing the compiled contract +– the only field required in this file is the `abi` field in the top-level +object; +- `eventSignature`, specifying the signature of the event consisting only of the event name followed by parameter types (without names) in order, enclosed in parentheses and separated by commas. +- `fields.contractAddress` should have the name of the field in the emitted event. + +The example configuration assumes that the event has the following signature: + +```solidity +event CustomEvent(address indexed address); +``` + +The `contractAddress` fields needs to be changed to the name of the field in the +event otherwise. + +## Performance implications + +Normally the funnels fetch all of the configured primitives concurrently in a +range of blocks configured by either the `funnelBlockGroupSize` or +`presyncStepSize` variables. But a dynamic primitive can change the set of +extensions to fetch in the range, so if there are dynamic primitives configured, +the funnels will instead *first* request all of the events for these, update +the set of extensions, and afterwards request the data for the resulting set +concurrently. This implies a small reduction of concurrency for the funnels. \ No newline at end of file From 8d5cc1c069c3bd91f895ac32175e89130db0ebcc Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Wed, 15 May 2024 16:16:07 -0300 Subject: [PATCH 2/5] fix erc721 primitive link --- .../2-primitive-catalogue/10-evm/5-dynamic-erc721.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md index 0f83f221..4dd727f9 100644 --- a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md +++ b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md @@ -26,7 +26,7 @@ extensions: - `contractAdress` is the address of the smart contract that will emit the event that will trigger the erc721 primitive creation. - `scheduledPrefix` and `burnScheduledPrefix` are the prefixes used for the resulting erc721 events. This primitive won't emit events by itself. See [the -erc721 section for details](3-ERC721.md#meaning). +erc721 section for details](ERC721#meaning). - `abiPath`, specifying a path to a .json file describing the compiled contract – the only field required in this file is the `abi` field in the top-level object; From 27798b25b713b67b50fbb315cbb892a1cbc046ce Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Wed, 22 May 2024 21:11:24 -0300 Subject: [PATCH 3/5] update the config format and add utility functions --- .../10-evm/5-dynamic-erc721.md | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md index 4dd727f9..322897ea 100644 --- a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md +++ b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md @@ -1,7 +1,13 @@ # Dynamic ERC721 -This primitive allows registering new erc721 extensions in runtime, triggered by -a generic smart contract event. +This primitive allows registering new [ERC721](ERC721) extensions in runtime, +triggered by a generic smart contract event. This works by having a smart +contract that has an event that acts as a *trigger* to the engine, and the +engine in response adds a new extension, without needing to change the +configuration file. An use-case for this would be a factory contract, which +takes care of deploying new instances of the contract tracked by the newly +generated primitive, and then emits and event to signal the engine to start +tracking this through an event. ## Configuration @@ -12,35 +18,74 @@ extensions: type: "dynamic-primitive" startBlockHeight: 100 contractAddress: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - scheduledPrefix: "derc721" - burnScheduledPrefix: "bderc721" eventSignature: "CustomEvent(address)" abiPath: "./CustomEventEmitter.json" - targetType: "erc721" - fields: - contractAddress: address + targetConfig: + type: "erc721" + scheduledPrefix: "nft" + burnScheduledPrefix: "nftburn" + dynamicFields: + contractAddress: nftAddress ``` ### Meaning +There are two type of fields in this configuration. The top level fields are +used for the extension that have the role of monitoring the network for the +trigger event. These are the following: + - `contractAdress` is the address of the smart contract that will emit the event that will trigger the erc721 primitive creation. -- `scheduledPrefix` and `burnScheduledPrefix` are the prefixes used for the -resulting erc721 events. This primitive won't emit events by itself. See [the -erc721 section for details](ERC721#meaning). - `abiPath`, specifying a path to a .json file describing the compiled contract – the only field required in this file is the `abi` field in the top-level object; - `eventSignature`, specifying the signature of the event consisting only of the event name followed by parameter types (without names) in order, enclosed in parentheses and separated by commas. -- `fields.contractAddress` should have the name of the field in the emitted event. + +The nested fields instead are used to construct the configurations for the +dynamically generated extensions. It's divided into a static part, which is in +`targetConfig`. And a dynamic part which is in `dynamicFields`. Dynamic in this +context means that it depends on the data included on the emitted event. + +#### Static configuration + +- `scheduledPrefix` and `burnScheduledPrefix` are the prefixes used for the +resulting ERC721 events. This primitive won't emit events by itself. See [the +ERC721 section for details](ERC721#meaning). + +#### Dynamic configuration + +- `contractAddress` has the name of the field in the emitted event that contains +the address of the ERC721. The example configuration assumes that the event has the following signature: ```solidity -event CustomEvent(address indexed address); +event CustomEvent(address indexed nftAddress); ``` -The `contractAddress` fields needs to be changed to the name of the field in the -event otherwise. +## Utility functions + +Generated extensions have a _name_, that is derived from the name of the trigger +extension, and the order of the dynamic primitive. The following function can be +used to generate this name. + +```ts +export function generateDynamicPrimitiveName(parentName: string, id: number): string; +``` + +For example, the first dynamic primitive that gets created will have a name of +`generateDynamicPrimitiveName(parentCdeName, 0)`, and so on. This name can be +then used as an argument to the other utility functions. + +There is also a public utility function to get the list of all of the +dynamically generated extensions. In this case the `config` field is formatted +as json. It can be used to extract, the contract address, for example. + +```ts +export async function getDynamicExtensions( + readonlyDBConn: Pool, + parent: string +): Promise<{ name: string; config: string }[]>; +``` ## Performance implications From 1130d2eedce7af4a1c03d4f459bec3ec8820177b Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Thu, 23 May 2024 03:15:38 -0300 Subject: [PATCH 4/5] fix generateDynamicPrimitiveName example name Co-authored-by: Sebastien Guillemot --- .../2-primitive-catalogue/10-evm/5-dynamic-erc721.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md index 322897ea..d6271866 100644 --- a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md +++ b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-erc721.md @@ -73,7 +73,7 @@ export function generateDynamicPrimitiveName(parentName: string, id: number): st ``` For example, the first dynamic primitive that gets created will have a name of -`generateDynamicPrimitiveName(parentCdeName, 0)`, and so on. This name can be +`generateDynamicPrimitiveName(parentPrimitiveName, 0)`, and so on. This name can be then used as an argument to the other utility functions. There is also a public utility function to get the list of all of the From c65ec55f21f0f1c7bff1a3344a2fb37030807d41 Mon Sep 17 00:00:00 2001 From: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com> Date: Fri, 24 May 2024 01:18:02 -0300 Subject: [PATCH 5/5] address review comments --- .../10-evm/5-dynamic-primitives.md | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-primitives.md diff --git a/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-primitives.md b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-primitives.md new file mode 100644 index 00000000..36580d2e --- /dev/null +++ b/docs/home/300-react-to-events/2-primitive-catalogue/10-evm/5-dynamic-primitives.md @@ -0,0 +1,157 @@ +# Dynamic primitives + +This primitive allows registering new extensions in runtime, without changing +the configuration files. These are triggered by a generic smart contract event. +A use-case for this would be a factory contract. + +Currently the dynamic extensions can be one of: + +- [ERC721](ERC721) +- [Generic](Generic) + +## Configuration + +### ERC721 example + +```yaml +# extensions.yaml +extensions: + - name: "Dynamic erc721" + type: "dynamic-primitive" + startBlockHeight: 100 + contractAddress: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + eventSignature: "ERC721Created(address)" + abiPath: "./FactoryERC721.json" + targetConfig: + type: "erc721" + scheduledPrefix: "nft" + burnScheduledPrefix: "nftburn" + dynamicFields: + contractAddress: nftAddress +``` + +### Meaning + +There are two type of fields in this configuration. The top level fields are +used for the extension that have the role of monitoring the network for the +trigger event. These are the following: + +- `contractAdress` is the address of the smart contract that will emit the event that will trigger the erc721 primitive creation. +- `abiPath`, specifying a path to a .json file describing the compiled contract +– the only field required in this file is the `abi` field in the top-level +object; +- `eventSignature`, specifying the signature of the event consisting only of the event name followed by parameter types (without names) in order, enclosed in parentheses and separated by commas. + +The nested fields instead are used to construct the configuration for the +dynamically generated extensions. It's divided into a static part, which is in +`targetConfig`. And a dynamic part which is in `dynamicFields`. Dynamic in this +context means that it depends on the data included on the emitted event. + +#### Static configuration + +- `scheduledPrefix` is the prefix used for the events emitted by the dynamic +extensions. This primitive won't emit events by itself. + +- For the [erc721](erc721) case, there is also `burnScheduledPrefix`, for which +the comment above also applies. + +- For the [generic](Generic) primitive the `targetConfig` field would look like +this instead: + +```yaml +targetConfig: + type: "generic" + abiPath: "./abis/MyCustomContract.json" + eventSignature: "MyEvent(address,uint256)" + scheduledPrefix: "cst" +``` + +#### Dynamic configuration + +- `contractAddress` has the name of the parameter in the emitted event that +contains the address for the new primitive. + +### Factory contracts + +Solidity has no standardized way to create a factory contract. Instead, it's up +to you to create your own factory contract whatever way works best, and to emit +events that trigger the dynamic primitive inside it. The example configuration +assumes that the event (such as in your factory contract) has the following +signature: + +```solidity +// note: the name of the argument (nftAddress) has to match the name specified in dynamicFields +event ERC721Created(address indexed nftAddress); +``` + +Which could be part of the following implementation. + + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract CustomERC721 is ERC721 { + constructor(string memory name, string memory symbol) ERC721(name, symbol) {} +} + +contract FactoryERC721 { + event ERC721Created(address indexed nftAddress); // emitted when ERC721 token is deployed + + /// @dev deploys an ERC721 token with given parameters + /// @return address deployed + function deployERC721(string memory _name, string memory symbol) public returns (address) { + CustomERC721 t = new CustomERC721(_name, symbol); + emit ERC721Created(address(t)); + return address(t); + } +} +``` + +**NOTE:** Extra parameters in the event will be ignored, and the position does not matter. + +## Utility functions + +Generated extensions have a _name_, that is derived from the name of the trigger +extension, and the order of the dynamic primitive. The following function can be +used to generate this name. + +```ts +export function generateDynamicPrimitiveName(parentName: string, id: number): string; +``` + +For example, the first dynamic primitive that gets created will have a name of +`generateDynamicPrimitiveName(parentCdeName, 0)`, and so on. This name can be +then used as an argument to the other utility functions. + +There is also a public utility function to get the list of all of the +dynamically generated extensions. The `config` field has a JSON with the +concrete parameters for that particular extension. These are the ones that would +be [in the configuration file](../../funnel-types/configuration#extensions) if +the primitive was not dynamic. The only exception is the `name` field, which is +instead provided as a separated field. + +```ts +export async function getDynamicExtensions( + readonlyDBConn: Pool, + parent: string +): Promise<{ name: string; config: string }[]>; +``` + +## Performance implications + +During a typical execution, the steps are as follows: +1. Fetch all primitives for a block range concurrently (depending on `funnelBlockGroupSize` or +`presyncStepSize`). +2. Construct the blocks from the fetched data to feed to the state machine. + +However, dynamic primitive can change the set of primitives the rollup monitors (and therefore change what has to be fetched for a given block range). Therefore, if there are dynamic primitives configured, funnels will instead + +1. Request all of the events for dynamic primitives and update the set of primitives tracked by the rollup +2. Fetch primitives (both the static ones and the new ones after the dynamic primitive update) +3. Send blocks to the state machine as before + +This extra step introduces a performance reduction (whose impact depends how long it takes to fetch data from the node your game is connecting to) \ No newline at end of file