diff --git a/db/migrations/postgres/000093_add_ffi_details.down.sql b/db/migrations/postgres/000093_add_ffi_details.down.sql new file mode 100644 index 000000000..cc95ed927 --- /dev/null +++ b/db/migrations/postgres/000093_add_ffi_details.down.sql @@ -0,0 +1,4 @@ +BEGIN; +ALTER TABLE ffimethods DROP COLUMN details; +ALTER TABLE ffievents DROP COLUMN details; +COMMIT; \ No newline at end of file diff --git a/db/migrations/postgres/000093_add_ffi_details.up.sql b/db/migrations/postgres/000093_add_ffi_details.up.sql new file mode 100644 index 000000000..7cf46ed61 --- /dev/null +++ b/db/migrations/postgres/000093_add_ffi_details.up.sql @@ -0,0 +1,4 @@ +BEGIN; +ALTER TABLE ffimethods ADD COLUMN details TEXT; +ALTER TABLE ffievents ADD COLUMN details TEXT; +COMMIT; \ No newline at end of file diff --git a/db/migrations/sqlite/000093_add_ffi_details.down.sql b/db/migrations/sqlite/000093_add_ffi_details.down.sql new file mode 100644 index 000000000..90f917aaf --- /dev/null +++ b/db/migrations/sqlite/000093_add_ffi_details.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE ffimethods DROP COLUMN details; +ALTER TABLE ffievents DROP COLUMN details; diff --git a/db/migrations/sqlite/000093_add_ffi_details.up.sql b/db/migrations/sqlite/000093_add_ffi_details.up.sql new file mode 100644 index 000000000..28435d439 --- /dev/null +++ b/db/migrations/sqlite/000093_add_ffi_details.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE ffimethods ADD COLUMN details TEXT; +ALTER TABLE ffievents ADD COLUMN details TEXT; diff --git a/docs/reference/firefly_interface_format.md b/docs/reference/firefly_interface_format.md index 2daf90b87..7f77b506b 100644 --- a/docs/reference/firefly_interface_format.md +++ b/docs/reference/firefly_interface_format.md @@ -35,26 +35,28 @@ There are four required fields when broadcasting a contract interface in FireFly ## Method -Let's look at a what goes inside the `methods` array now. It is also a JSON object that has a `name`, a list of `params` which are the arguments the function will take and a list of `returns` which are the return values of the function. Optionally, it also has a `description` which can be helpful in OpenAPI Spec generation. +Let's look at a what goes inside the `methods` array now. It is also a JSON object that has a `name`, a list of `params` which are the arguments the function will take and a list of `returns` which are the return values of the function. It also has an optional `description` which can be helpful in OpenAPI Spec generation. Finally, it has an optional `details` object which wraps blockchain specific information about this method. This can be used by the blockchain plugin when invoking this function, and it is also used in documentation generation. ```json { "name": "add", "description": "Add two numbers together", "params": [], - "returns": [] + "returns": [], + "details": {} } ``` ## Event -What goes into the `events` array is very similar. It is also a JSON object that has a `name` and a list of `params`. The difference is that `events` don't have `returns`. Arguments that are passed to the event when it is emitted are in `params`. Optionally, it also has a `description` which can be helpful in OpenAPI Spec generation. +What goes into the `events` array is very similar. It is also a JSON object that has a `name` and a list of `params`. The difference is that `events` don't have `returns`. Arguments that are passed to the event when it is emitted are in `params`. It also has an optional `description` which can be helpful in OpenAPI Spec generation. Finally, it has an optional `details` object which wraps blockchain specific information about this event. This can be used by the blockchain plugin when invoking this function, and it is also used in documentation generation. ```json { "name": "added", "description": "An event that occurs when numbers have been added", - "params": [] + "params": [], + "details": {} } ``` @@ -137,7 +139,10 @@ Putting it all together, here is a full example of the FireFly Interface format } } } - ] + ], + "details": { + "stateMutability": "viewable" + } }, { "name": "set", @@ -154,7 +159,10 @@ Putting it all together, here is a full example of the FireFly Interface format } } ], - "returns": [] + "returns": [], + "details": { + "stateMutability": "payable" + } } ], "events": [ @@ -183,7 +191,8 @@ Putting it all together, here is a full example of the FireFly Interface format } } } - ] + ], + "details": {} } ] } diff --git a/docs/reference/types/contractlistener.md b/docs/reference/types/contractlistener.md index 1af969f57..8fa0c318a 100644 --- a/docs/reference/types/contractlistener.md +++ b/docs/reference/types/contractlistener.md @@ -91,6 +91,7 @@ nav_order: 9 | `name` | The name of the event | `string` | | `description` | A description of the smart contract event | `string` | | `params` | An array of event parameter/argument definitions | [`FFIParam[]`](#ffiparam) | +| `details` | Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) | ## FFIParam diff --git a/docs/reference/types/ffi.md b/docs/reference/types/ffi.md index 699a3b0f7..c188540aa 100644 --- a/docs/reference/types/ffi.md +++ b/docs/reference/types/ffi.md @@ -49,7 +49,10 @@ nav_order: 8 } } } - ] + ], + "details": { + "stateMutability": "viewable" + } }, { "id": "fc6f54ee-2e3c-4e56-b17c-4a1a0ae7394b", @@ -69,7 +72,10 @@ nav_order: 8 } } ], - "returns": [] + "returns": [], + "details": { + "stateMutability": "payable" + } } ], "events": [ @@ -132,6 +138,7 @@ nav_order: 8 | `description` | A description of the smart contract method | `string` | | `params` | An array of method parameter/argument definitions | [`FFIParam[]`](#ffiparam) | | `returns` | An array of method return definitions | [`FFIParam[]`](#ffiparam) | +| `details` | Additional blockchain specific fields about this method from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) | ## FFIParam @@ -154,6 +161,7 @@ nav_order: 8 | `name` | The name of the event | `string` | | `description` | A description of the smart contract event | `string` | | `params` | An array of event parameter/argument definitions | [`FFIParam[]`](#ffiparam) | +| `details` | Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation. | [`JSONObject`](simpletypes#jsonobject) | ## FFIParam diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index b80be7cd2..42cedbc04 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -404,6 +404,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -466,6 +475,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -884,6 +902,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -1040,6 +1067,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -1510,10 +1546,6 @@ paths: description: The UUID of the node that generated the batch format: uuid type: string - payloadRef: - description: For broadcast batches, this is the reference to - the binary batch in shared storage - type: string tx: description: The FireFly transaction associated with this batch properties: @@ -1598,10 +1630,6 @@ paths: description: The UUID of the node that generated the batch format: uuid type: string - payloadRef: - description: For broadcast batches, this is the reference to the - binary batch in shared storage - type: string tx: description: The FireFly transaction associated with this batch properties: @@ -2050,6 +2078,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -2114,6 +2151,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -2227,6 +2273,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -2258,6 +2313,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -2325,6 +2389,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -2387,6 +2460,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -2504,6 +2586,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -2566,6 +2657,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -2689,6 +2789,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -2751,6 +2860,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -2881,6 +2999,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -2943,6 +3070,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -3075,6 +3211,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -3392,6 +3537,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -3496,6 +3650,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -3584,6 +3747,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -3729,6 +3901,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -3859,6 +4040,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -9135,6 +9325,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -9197,6 +9396,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -9350,6 +9558,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -9687,6 +9904,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -9811,6 +10037,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -9894,6 +10129,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -10045,6 +10289,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -10443,10 +10696,6 @@ paths: description: The UUID of the node that generated the batch format: uuid type: string - payloadRef: - description: For broadcast batches, this is the reference to - the binary batch in shared storage - type: string tx: description: The FireFly transaction associated with this batch properties: @@ -10538,10 +10787,6 @@ paths: description: The UUID of the node that generated the batch format: uuid type: string - payloadRef: - description: For broadcast batches, this is the reference to the - binary batch in shared storage - type: string tx: description: The FireFly transaction associated with this batch properties: @@ -11018,6 +11263,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -11082,6 +11336,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used + by the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -11202,6 +11465,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -11233,6 +11505,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -11300,6 +11581,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -11362,6 +11652,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -11486,6 +11785,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -11548,6 +11856,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -11678,6 +11995,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -11740,6 +12066,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -11877,6 +12212,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI event definition format: uuid @@ -11939,6 +12283,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this method from the original smart contract. Used by + the blockchain plugin and for documentation generation. + type: object id: description: The UUID of the FFI method definition format: uuid @@ -12078,6 +12431,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -12402,6 +12764,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by + the blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -12513,6 +12884,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -12601,6 +12981,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -12760,6 +13149,15 @@ paths: description: description: A description of the smart contract event type: string + details: + additionalProperties: + description: Additional blockchain specific fields about + this event from the original smart contract. Used by the + blockchain plugin and for documentation generation. + description: Additional blockchain specific fields about this + event from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the event type: string @@ -12897,6 +13295,15 @@ paths: description: description: A description of the smart contract method type: string + details: + additionalProperties: + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + description: Additional blockchain specific fields about this + method from the original smart contract. Used by the blockchain + plugin and for documentation generation. + type: object name: description: The name of the method type: string @@ -18141,8 +18548,7 @@ paths: /namespaces/{ns}/network/identities/{did}: get: deprecated: true - description: Gets an identity by its DID (deprecated - use /identities/{did} - instead of /network/identities/{did}) + description: Gets an identity by its DID operationId: getNetworkIdentityByDIDNamespace parameters: - description: The identity DID @@ -25724,8 +26130,7 @@ paths: /network/identities/{did}: get: deprecated: true - description: Gets an identity by its DID (deprecated - use /identities/{did} - instead of /network/identities/{did}) + description: Gets an identity by its DID operationId: getNetworkIdentityByDID parameters: - description: The identity DID diff --git a/docs/tutorials/custom_contracts/ethereum.md b/docs/tutorials/custom_contracts/ethereum.md index cea0aa2e7..8e4d00061 100644 --- a/docs/tutorials/custom_contracts/ethereum.md +++ b/docs/tutorials/custom_contracts/ethereum.md @@ -517,6 +517,35 @@ To make a read-only request to the blockchain to check the current value of the > **NOTE:** Some contracts may have queries that require input parameters. That's why the query endpoint is a `POST`, rather than a `GET` so that parameters can be passed as JSON in the request body. This particular function does not have any parameters, so we just pass an empty JSON object. +## Passing additional options with a request + +Some smart contract functions may accept or require additional options to be passed with the request. For example, a Solidity function might be `payable`, meaning that a `value` field must be specified, indicating an amount of ETH to be transferred with the request. Each of your smart contract API's `/invoke` or `/query` endpoints support an `options` object in addition to the `input` arguments for the function itself. + +Here is an example of sending 100 wei with a transaction: + +### Request + +`POST` `http://localhost:5000/api/v1/namespaces/default/apis/simple-storage/invoke/set` + +```json +{ + "input": { + "newValue": 3 + }, + "options": { + "value": 100 + } +} +``` + +### Response + +```json +{ + "id": "41c67c63-52cf-47ce-8a59-895fe2ffdc86" +} +``` + ## Create a blockchain event listener Now that we've seen how to submit transactions and preform read-only queries to the blockchain, let's look at how to receive blockchain events so we know when things are happening in realtime. diff --git a/go.mod b/go.mod index 60cbe046b..46d127585 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,8 @@ require ( github.com/golang-migrate/migrate/v4 v4.15.2 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 - github.com/hyperledger/firefly-common v0.1.9 + github.com/hyperledger/firefly-common v0.1.10 + github.com/hyperledger/firefly-signer v0.9.10 github.com/jarcoal/httpmock v1.1.0 github.com/karlseguin/ccache v2.0.3+incompatible github.com/lib/pq v1.10.6 @@ -46,7 +47,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/karlseguin/expect v1.0.8 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -72,6 +72,7 @@ require ( github.com/stretchr/objx v0.2.0 // indirect github.com/subosito/gotenv v1.4.0 // indirect github.com/wayneashleyberry/terminal-dimensions v1.0.0 // indirect + github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 // indirect github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect diff --git a/go.sum b/go.sum index f4441f95a..758f8d0bc 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,7 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aidarkhanov/nanoid v1.0.8 h1:yxyJkgsEDFXP7+97vc6JevMcjyb03Zw+/9fqhlVXBXA= github.com/aidarkhanov/nanoid v1.0.8/go.mod h1:vadfZHT+m4uDhttg0yY4wW3GKtl2T6i4d2Age+45pYk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -190,6 +191,19 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= @@ -366,9 +380,11 @@ github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1S github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -432,6 +448,7 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= @@ -710,8 +727,11 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hyperledger/firefly-common v0.1.9 h1:7QWaJ6WU5NrLp+lKqkWpLi/T+HAJn4tk84tq/cxpghk= -github.com/hyperledger/firefly-common v0.1.9/go.mod h1:MYL6Dbj3KqM/79IkS+mCzJ7wRguNbd/PKdVu8aXo5TI= +github.com/hyperledger/firefly-common v0.1.5/go.mod h1:qGy7i8eWlE8Ed7jFn2Hpn8bYaflL204j4NJB1mAfTms= +github.com/hyperledger/firefly-common v0.1.10 h1:BtP20uuPMP9+Q1+bRPJyBDCp5hObqd+JVMqoAoWBvVw= +github.com/hyperledger/firefly-common v0.1.10/go.mod h1:MYL6Dbj3KqM/79IkS+mCzJ7wRguNbd/PKdVu8aXo5TI= +github.com/hyperledger/firefly-signer v0.9.10 h1:pJgeuAH+q2qLuSSJ4sW3eZ9rd7oEyssCKDBNMj905As= +github.com/hyperledger/firefly-signer v0.9.10/go.mod h1:BjzIWMj4e1Em52liuyCV6dQhPeVc3WOuLOnEDGS4QK4= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -772,6 +792,8 @@ github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jarcoal/httpmock v1.1.0 h1:F47ChZj1Y2zFsCXxNkBPwNNKnAyOATcdQibk0qEdVCE= github.com/jarcoal/httpmock v1.1.0/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -788,6 +810,7 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -815,6 +838,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= @@ -962,6 +986,7 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -976,6 +1001,8 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -1022,8 +1049,10 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -1111,6 +1140,7 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -1148,6 +1178,7 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -1167,6 +1198,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= @@ -1248,10 +1280,13 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= @@ -1303,6 +1338,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1319,8 +1355,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1331,6 +1369,7 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1381,6 +1420,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1634,6 +1674,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/apiserver/ffi2swagger.go b/internal/apiserver/ffi2swagger.go index 844cf2ef8..e69209bc0 100644 --- a/internal/apiserver/ffi2swagger.go +++ b/internal/apiserver/ffi2swagger.go @@ -21,10 +21,16 @@ import ( "encoding/json" "fmt" "net/http" + "sort" + "strings" "github.com/getkin/kin-openapi/openapi3" + "github.com/hyperledger/firefly-common/pkg/config" "github.com/hyperledger/firefly-common/pkg/ffapi" "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/i18n" + "github.com/hyperledger/firefly/internal/coreconfig" + "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/pkg/core" "github.com/hyperledger/firefly/pkg/database" ) @@ -73,34 +79,49 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core } return ffapi.NewSwaggerGen(&ffapi.Options{ - Title: ffi.Name, - Version: ffi.Version, - Description: ffi.Description, - BaseURL: baseURL, + Title: ffi.Name, + Version: ffi.Version, + Description: ffi.Description, + BaseURL: baseURL, + DefaultRequestTimeout: config.GetDuration(coreconfig.APIRequestTimeout), }).Generate(ctx, routes) } func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *core.FFIMethod, hasLocation bool) []*ffapi.Route { + ctx := context.Background() + description := method.Description + if method.Details != nil && len(method.Details) > 0 { + additionalDetailsHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetails) + description = fmt.Sprintf("%s\n\n%s:\n\n%s", description, additionalDetailsHeader, buildDetailsTable(ctx, method.Details)) + } routes = append(routes, &ffapi.Route{ - Name: fmt.Sprintf("invoke_%s", method.Pathname), - Path: fmt.Sprintf("invoke/%s", method.Pathname), // must match a route defined in apiserver routes! - Method: http.MethodPost, - JSONInputSchema: func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() }, - JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() }, - JSONOutputCodes: []int{http.StatusOK}, + Name: fmt.Sprintf("invoke_%s", method.Pathname), + Path: fmt.Sprintf("invoke/%s", method.Pathname), // must match a route defined in apiserver routes! + Method: http.MethodPost, + JSONInputSchema: func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() }, + JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() }, + JSONOutputCodes: []int{http.StatusOK}, + PreTranslatedDescription: description, }) routes = append(routes, &ffapi.Route{ - Name: fmt.Sprintf("query_%s", method.Pathname), - Path: fmt.Sprintf("query/%s", method.Pathname), // must match a route defined in apiserver routes! - Method: http.MethodPost, - JSONInputSchema: func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() }, - JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() }, - JSONOutputCodes: []int{http.StatusOK}, + Name: fmt.Sprintf("query_%s", method.Pathname), + Path: fmt.Sprintf("query/%s", method.Pathname), // must match a route defined in apiserver routes! + Method: http.MethodPost, + JSONInputSchema: func(ctx context.Context) string { return contractCallJSONSchema(&method.Params, hasLocation).String() }, + JSONOutputSchema: func(ctx context.Context) string { return ffiParamsJSONSchema(&method.Returns).String() }, + JSONOutputCodes: []int{http.StatusOK}, + PreTranslatedDescription: description, }) return routes } func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, hasLocation bool) []*ffapi.Route { + ctx := context.Background() + description := event.Description + if event.Details != nil && len(event.Details) > 0 { + additionalDetailsHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetails) + description = fmt.Sprintf("%s\n\n%s:\n\n%s", description, additionalDetailsHeader, buildDetailsTable(ctx, event.Details)) + } routes = append(routes, &ffapi.Route{ Name: fmt.Sprintf("createlistener_%s", event.Pathname), Path: fmt.Sprintf("listeners/%s", event.Pathname), // must match a route defined in apiserver routes! @@ -111,8 +132,9 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, h } return &ContractListenerInputWithLocation{} }, - JSONOutputValue: func() interface{} { return &core.ContractListener{} }, - JSONOutputCodes: []int{http.StatusOK}, + JSONOutputValue: func() interface{} { return &core.ContractListener{} }, + JSONOutputCodes: []int{http.StatusOK}, + PreTranslatedDescription: description, }) routes = append(routes, &ffapi.Route{ Name: fmt.Sprintf("getlistener_%s", event.Pathname), @@ -133,15 +155,18 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *core.FFIEvent, h * Returns the JSON Schema as an `fftypes.JSONObject`. */ func contractCallJSONSchema(params *core.FFIParams, hasLocation bool) *fftypes.JSONObject { - req := &core.ContractCallRequest{ - Input: *ffiParamsJSONSchema(params), + properties := fftypes.JSONObject{ + "input": ffiParamsJSONSchema(params), + "options": fftypes.JSONObject{ + "type": "object", + }, } if !hasLocation { - req.Location = fftypes.JSONAnyPtr(`{}`) + properties["location"] = fftypes.JSONAnyPtr(`{}`) } return &fftypes.JSONObject{ "type": "object", - "properties": req, + "properties": properties, } } @@ -163,3 +188,20 @@ func ffiParamJSONSchema(param *core.FFIParam) *fftypes.JSONObject { } return nil } + +func buildDetailsTable(ctx context.Context, details map[string]interface{}) string { + keyHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetailsKey) + valueHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetailsKey) + var s strings.Builder + s.WriteString(fmt.Sprintf("| %s | %s |\n|-----|-------|\n", keyHeader, valueHeader)) + keys := make([]string, len(details)) + i := 0 + for key := range details { + keys[i] = key + } + sort.Strings(keys) + for _, key := range keys { + s.WriteString(fmt.Sprintf("|%s|%s|\n", key, details[key])) + } + return s.String() +} diff --git a/internal/apiserver/ffi2swagger_test.go b/internal/apiserver/ffi2swagger_test.go index b85854c58..e6a862b12 100644 --- a/internal/apiserver/ffi2swagger_test.go +++ b/internal/apiserver/ffi2swagger_test.go @@ -65,6 +65,10 @@ func testFFI() *core.FFI { Schema: fftypes.JSONAnyPtr(`{"type": "boolean"}`), }, }, + Details: fftypes.JSONObject{ + "payable": true, + "stateMutability": "payable", + }, }, { Name: "method2", @@ -84,6 +88,9 @@ func testFFI() *core.FFI { Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`), }, }, + Details: fftypes.JSONObject{ + "anonymous": true, + }, }, }, }, diff --git a/internal/apiserver/route_get_net_did.go b/internal/apiserver/route_get_net_did.go index 55d981c8d..5f04259f9 100644 --- a/internal/apiserver/route_get_net_did.go +++ b/internal/apiserver/route_get_net_did.go @@ -35,7 +35,7 @@ var getNetworkIdentityByDID = &ffapi.Route{ PathParams: []*ffapi.PathParam{ {Name: "did", Description: coremsgs.APIParamsDID}, }, - Description: coremsgs.APIEndpointsGetNetworkIdentityByDID, + Description: coremsgs.APIEndpointsGetIdentityByDID, Deprecated: true, // use getIdentityByDID instead JSONInputValue: nil, JSONOutputValue: func() interface{} { return &core.IdentityWithVerifiers{} }, diff --git a/internal/apiserver/route_post_contract_query.go b/internal/apiserver/route_post_contract_query.go index d5e6e8068..c74dc052f 100644 --- a/internal/apiserver/route_post_contract_query.go +++ b/internal/apiserver/route_post_contract_query.go @@ -29,8 +29,8 @@ var postContractQuery = &ffapi.Route{ Path: "contracts/query", Method: http.MethodPost, PathParams: nil, - QueryParams: []*ffapi.QueryParam{}, Description: coremsgs.APIEndpointsPostContractQuery, + QueryParams: []*ffapi.QueryParam{}, JSONInputValue: func() interface{} { return &core.ContractCallRequest{} }, JSONOutputValue: func() interface{} { return make(map[string]interface{}) }, JSONOutputCodes: []int{http.StatusOK}, diff --git a/internal/apiserver/route_spi_get_namespace_by_name.go b/internal/apiserver/route_spi_get_namespace_by_name.go index fd5bb6659..f879ee4f2 100644 --- a/internal/apiserver/route_spi_get_namespace_by_name.go +++ b/internal/apiserver/route_spi_get_namespace_by_name.go @@ -32,7 +32,7 @@ var spiGetNamespaceByName = &ffapi.Route{ {Name: "ns", Description: coremsgs.APIParamsNamespace}, }, QueryParams: nil, - Description: coremsgs.APIEndpointsAdminGetNamespaceByName, + Description: coremsgs.APIEndpointsPostNewNamespace, JSONInputValue: nil, JSONOutputValue: func() interface{} { return &core.Namespace{} }, JSONOutputCodes: []int{http.StatusOK}, diff --git a/internal/blockchain/ethereum/abi_definitions.go b/internal/blockchain/ethereum/abi_definitions.go index a0849f00a..e86b540b3 100644 --- a/internal/blockchain/ethereum/abi_definitions.go +++ b/internal/blockchain/ethereum/abi_definitions.go @@ -16,10 +16,12 @@ package ethereum -var batchPinMethodABI = ABIElementMarshaling{ +import "github.com/hyperledger/firefly-signer/pkg/abi" + +var batchPinMethodABI = &abi.Entry{ Name: "pinBatch", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { InternalType: "string", Name: "namespace", @@ -48,10 +50,10 @@ var batchPinMethodABI = ABIElementMarshaling{ }, } -var batchPinEventABI = ABIElementMarshaling{ +var batchPinEventABI = &abi.Entry{ Name: "BatchPin", Type: "event", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Indexed: false, InternalType: "address", @@ -97,12 +99,12 @@ var batchPinEventABI = ABIElementMarshaling{ }, } -var networkVersionMethodABI = ABIElementMarshaling{ +var networkVersionMethodABI = &abi.Entry{ Name: "networkVersion", Type: "function", StateMutability: "pure", - Inputs: []ABIArgumentMarshaling{}, - Outputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{}, + Outputs: abi.ParameterArray{ { InternalType: "uint8", Type: "uint8", diff --git a/internal/blockchain/ethereum/ethereum.go b/internal/blockchain/ethereum/ethereum.go index 2646a66f0..74389618a 100644 --- a/internal/blockchain/ethereum/ethereum.go +++ b/internal/blockchain/ethereum/ethereum.go @@ -33,6 +33,7 @@ import ( "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-common/pkg/wsclient" + "github.com/hyperledger/firefly-signer/pkg/abi" "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/internal/metrics" "github.com/hyperledger/firefly/pkg/blockchain" @@ -42,8 +43,11 @@ import ( const ( broadcastBatchEventSignature = "BatchPin(address,uint256,string,bytes32,bytes32,string,bytes32[])" + addressType = "address" + boolType = "bool" booleanType = "boolean" integerType = "integer" + tupleType = "tuple" stringType = "string" arrayType = "array" objectType = "object" @@ -142,10 +146,16 @@ type paramDetails struct { } type Schema struct { - Type string `json:"type"` - Details *paramDetails `json:"details,omitempty"` - Properties map[string]*Schema `json:"properties,omitempty"` - Items *Schema `json:"items,omitempty"` + OneOf []SchemaType `json:"oneOf,omitempty"` + Type string `json:"type,omitempty"` + Details *paramDetails `json:"details,omitempty"` + Properties map[string]*Schema `json:"properties,omitempty"` + Items *Schema `json:"items,omitempty"` + Description string `json:"description,omitempty"` +} + +type SchemaType struct { + Type string `json:"type"` } func (s *Schema) ToJSON() string { @@ -153,25 +163,12 @@ func (s *Schema) ToJSON() string { return string(b) } -// ABIArgumentMarshaling is abi.ArgumentMarshaling -type ABIArgumentMarshaling struct { - Name string `json:"name"` - Type string `json:"type"` - InternalType string `json:"internalType,omitempty"` - Components []ABIArgumentMarshaling `json:"components,omitempty"` - Indexed bool `json:"indexed,omitempty"` -} - -// ABIElementMarshaling is the serialized representation of a method or event in an ABI -type ABIElementMarshaling struct { - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Payable bool `json:"payable,omitempty"` - Constant bool `json:"constant,omitempty"` - Anonymous bool `json:"anonymous,omitempty"` - StateMutability string `json:"stateMutability,omitempty"` - Inputs []ABIArgumentMarshaling `json:"inputs"` - Outputs []ABIArgumentMarshaling `json:"outputs"` +type EthconnectMessageRequest struct { + Headers EthconnectMessageHeaders `json:"headers,omitempty"` + To string `json:"to"` + From string `json:"from,omitempty"` + Method *abi.Entry `json:"method"` + Params []interface{} `json:"params"` } type EthconnectMessageHeaders struct { @@ -180,7 +177,7 @@ type EthconnectMessageHeaders struct { } type FFIGenerationInput struct { - ABI []ABIElementMarshaling `json:"abi,omitempty"` + ABI *abi.ABI `json:"abi,omitempty"` } var addressVerify = regexp.MustCompile("^[0-9a-f]{40}$") @@ -643,7 +640,7 @@ func wrapError(ctx context.Context, errRes *ethError, res *resty.Response, err e return ffresty.WrapRestErr(ctx, res, err, coremsgs.MsgEthconnectRESTErr) } -func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, address, signingKey string, abi ABIElementMarshaling, requestID string, input []interface{}, options map[string]interface{}) (map[string]interface{}, error) { +func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, options map[string]interface{}) (map[string]interface{}, error) { headers := EthconnectMessageHeaders{ Type: messageType, } @@ -670,7 +667,7 @@ func (e *Ethereum) buildEthconnectRequestBody(ctx context.Context, messageType, return body, nil } -func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi ABIElementMarshaling, requestID string, input []interface{}, options map[string]interface{}) error { +func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey string, abi *abi.Entry, requestID string, input []interface{}, options map[string]interface{}) error { if e.metrics.IsMetricsEnabled() { e.metrics.BlockchainTransaction(address, abi.Name) } @@ -695,7 +692,7 @@ func (e *Ethereum) invokeContractMethod(ctx context.Context, address, signingKey return nil } -func (e *Ethereum) queryContractMethod(ctx context.Context, address string, abi ABIElementMarshaling, input []interface{}, options map[string]interface{}) (*resty.Response, error) { +func (e *Ethereum) queryContractMethod(ctx context.Context, address string, abi *abi.Entry, input []interface{}, options map[string]interface{}) (*resty.Response, error) { if e.metrics.IsMetricsEnabled() { e.metrics.BlockchainQuery(address, abi.Name) } @@ -837,38 +834,49 @@ func (e *Ethereum) GetFFIParamValidator(ctx context.Context) (core.FFIParamValid return &FFIParamValidator{}, nil } -func (e *Ethereum) FFIEventDefinitionToABI(ctx context.Context, event *core.FFIEventDefinition) (ABIElementMarshaling, error) { - abiElement := ABIElementMarshaling{ +func (e *Ethereum) FFIEventDefinitionToABI(ctx context.Context, event *core.FFIEventDefinition) (*abi.Entry, error) { + abiInputs, err := e.convertFFIParamsToABIParameters(ctx, event.Params) + if err != nil { + return nil, err + } + abiEntry := &abi.Entry{ Name: event.Name, Type: "event", - Inputs: make([]ABIArgumentMarshaling, len(event.Params)), + Inputs: abiInputs, } - - if err := e.addParamsToList(ctx, abiElement.Inputs, event.Params); err != nil { - return abiElement, err + if event.Details != nil { + abiEntry.Anonymous = event.Details.GetBool("anonymous") } - return abiElement, nil + return abiEntry, nil } -func (e *Ethereum) FFIMethodToABI(ctx context.Context, method *core.FFIMethod) (ABIElementMarshaling, error) { - abiElement := ABIElementMarshaling{ - Name: method.Name, - Type: "function", - Inputs: make([]ABIArgumentMarshaling, len(method.Params)), - Outputs: make([]ABIArgumentMarshaling, len(method.Returns)), +func (e *Ethereum) FFIMethodToABI(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (*abi.Entry, error) { + abiInputs, err := e.convertFFIParamsToABIParameters(ctx, method.Params) + if err != nil { + return nil, err } - if err := e.addParamsToList(ctx, abiElement.Inputs, method.Params); err != nil { - return abiElement, err + abiOutputs, err := e.convertFFIParamsToABIParameters(ctx, method.Returns) + if err != nil { + return nil, err } - if err := e.addParamsToList(ctx, abiElement.Outputs, method.Returns); err != nil { - return abiElement, err + abiEntry := &abi.Entry{ + Name: method.Name, + Type: "function", + Inputs: abiInputs, + Outputs: abiOutputs, } - - return abiElement, nil + if method.Details != nil { + if stateMutability, ok := method.Details.GetStringOk("stateMutability"); ok { + abiEntry.StateMutability = abi.StateMutability(stateMutability) + } + abiEntry.Payable = method.Details.GetBool("payable") + abiEntry.Constant = method.Details.GetBool("constant") + } + return abiEntry, nil } -func ABIArgumentToTypeString(typeName string, components []ABIArgumentMarshaling) string { +func ABIArgumentToTypeString(typeName string, components abi.ParameterArray) string { if strings.HasPrefix(typeName, "tuple") { suffix := typeName[5:] children := make([]string, len(components)) @@ -880,7 +888,7 @@ func ABIArgumentToTypeString(typeName string, components []ABIArgumentMarshaling return typeName } -func ABIMethodToSignature(abi *ABIElementMarshaling) string { +func ABIMethodToSignature(abi *abi.Entry) string { result := abi.Name + "(" if len(abi.Inputs) > 0 { types := make([]string, len(abi.Inputs)) @@ -898,49 +906,55 @@ func (e *Ethereum) GenerateEventSignature(ctx context.Context, event *core.FFIEv if err != nil { return "" } - return ABIMethodToSignature(&abi) + return ABIMethodToSignature(abi) } -func (e *Ethereum) addParamsToList(ctx context.Context, abiParamList []ABIArgumentMarshaling, params core.FFIParams) error { +func (e *Ethereum) convertFFIParamsToABIParameters(ctx context.Context, params core.FFIParams) (abi.ParameterArray, error) { + abiParamList := make(abi.ParameterArray, len(params)) for i, param := range params { c := core.NewFFISchemaCompiler() v, _ := e.GetFFIParamValidator(ctx) c.RegisterExtension(v.GetExtensionName(), v.GetMetaSchema(), v) err := c.AddResource(param.Name, strings.NewReader(param.Schema.String())) if err != nil { - return err + return nil, err } s, err := c.Compile(param.Name) if err != nil { - return err + return nil, err } abiParamList[i] = processField(param.Name, s) } - return nil + return abiParamList, nil } -func processField(name string, schema *jsonschema.Schema) ABIArgumentMarshaling { +func processField(name string, schema *jsonschema.Schema) *abi.Parameter { details := getParamDetails(schema) - arg := ABIArgumentMarshaling{ + parameter := &abi.Parameter{ Name: name, Type: details.Type, InternalType: details.InternalType, Indexed: details.Indexed, } - if schema.Types[0] == objectType { - arg.Components = buildABIArgumentArray(schema.Properties) + if len(schema.Types) > 0 { + switch schema.Types[0] { + case objectType: + parameter.Components = buildABIParameterArrayForObject(schema.Properties) + case arrayType: + parameter.Components = buildABIParameterArrayForObject(schema.Items2020.Properties) + } } - return arg + return parameter } -func buildABIArgumentArray(properties map[string]*jsonschema.Schema) []ABIArgumentMarshaling { - args := make([]ABIArgumentMarshaling, len(properties)) +func buildABIParameterArrayForObject(properties map[string]*jsonschema.Schema) abi.ParameterArray { + parameters := make(abi.ParameterArray, len(properties)) for propertyName, propertySchema := range properties { details := getParamDetails(propertySchema) - arg := processField(propertyName, propertySchema) - args[*details.Index] = arg + parameter := processField(propertyName, propertySchema) + parameters[*details.Index] = parameter } - return args + return parameters } func getParamDetails(schema *jsonschema.Schema) *paramDetails { @@ -964,13 +978,14 @@ func getParamDetails(schema *jsonschema.Schema) *paramDetails { return paramDetails } -func (e *Ethereum) prepareRequest(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (ABIElementMarshaling, []interface{}, error) { +func (e *Ethereum) prepareRequest(ctx context.Context, method *core.FFIMethod, input map[string]interface{}) (*abi.Entry, []interface{}, error) { orderedInput := make([]interface{}, len(method.Params)) - abi, err := e.FFIMethodToABI(ctx, method) + abi, err := e.FFIMethodToABI(ctx, method, input) if err != nil { return abi, orderedInput, err } for i, ffiParam := range method.Params { + orderedInput[i] = input[ffiParam.Name] } return abi, orderedInput, nil @@ -996,121 +1011,167 @@ func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *core.FFIG if err != nil { return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "unable to deserialize JSON as ABI") } - if len(input.ABI) == 0 { + if len(*input.ABI) == 0 { return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "ABI is empty") } - ffi := e.convertABIToFFI(generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI) - return ffi, nil + return e.convertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI) } -func (e *Ethereum) convertABIToFFI(ns, name, version, description string, abi []ABIElementMarshaling) *core.FFI { +func (e *Ethereum) convertABIToFFI(ctx context.Context, ns, name, version, description string, abi *abi.ABI) (*core.FFI, error) { ffi := &core.FFI{ Namespace: ns, Name: name, Version: version, Description: description, - Methods: []*core.FFIMethod{}, - Events: []*core.FFIEvent{}, - } - - for _, element := range abi { - switch element.Type { - case "event": - event := &core.FFIEvent{ - FFIEventDefinition: core.FFIEventDefinition{ - Name: element.Name, - Params: e.convertABIArgumentsToFFI(element.Inputs), - }, - } - ffi.Events = append(ffi.Events, event) - case "function": - method := &core.FFIMethod{ - Name: element.Name, - Params: e.convertABIArgumentsToFFI(element.Inputs), - Returns: e.convertABIArgumentsToFFI(element.Outputs), - } - ffi.Methods = append(ffi.Methods, method) + Methods: make([]*core.FFIMethod, len(abi.Functions())), + Events: make([]*core.FFIEvent, len(abi.Events())), + } + i := 0 + for _, f := range abi.Functions() { + method, err := e.convertABIFunctionToFFIMethod(ctx, f) + if err != nil { + return nil, err + } + ffi.Methods[i] = method + i++ + } + i = 0 + for _, f := range abi.Events() { + event, err := e.convertABIEventToFFIEvent(ctx, f) + if err != nil { + return nil, err } + ffi.Events[i] = event + i++ } - return ffi + return ffi, nil } -func (e *Ethereum) convertABIArgumentsToFFI(args []ABIArgumentMarshaling) core.FFIParams { - ffiParams := core.FFIParams{} - for _, arg := range args { +func (e *Ethereum) convertABIFunctionToFFIMethod(ctx context.Context, abiFunction *abi.Entry) (*core.FFIMethod, error) { + params := make([]*core.FFIParam, len(abiFunction.Inputs)) + returns := make([]*core.FFIParam, len(abiFunction.Outputs)) + details := map[string]interface{}{} + for i, input := range abiFunction.Inputs { + typeComponent, err := input.TypeComponentTreeCtx(ctx) + if err != nil { + return nil, err + } + schema := e.getSchemaForABIInput(ctx, typeComponent) param := &core.FFIParam{ - Name: arg.Name, + Name: input.Name, + Schema: fftypes.JSONAnyPtr(schema.ToJSON()), } - s := e.getSchema(arg) - param.Schema = fftypes.JSONAnyPtr(s.ToJSON()) - ffiParams = append(ffiParams, param) + params[i] = param } - return ffiParams -} - -func (e *Ethereum) getSchema(arg ABIArgumentMarshaling) *Schema { - s := &Schema{ - Type: e.getFFIType(arg.Type), - Details: ¶mDetails{ - Type: arg.Type, - InternalType: arg.InternalType, - Indexed: arg.Indexed, - }, + for i, output := range abiFunction.Outputs { + typeComponent, err := output.TypeComponentTreeCtx(ctx) + if err != nil { + return nil, err + } + schema := e.getSchemaForABIInput(ctx, typeComponent) + param := &core.FFIParam{ + Name: output.Name, + Schema: fftypes.JSONAnyPtr(schema.ToJSON()), + } + returns[i] = param } - var properties map[string]*Schema - if len(arg.Components) > 0 { - properties = e.getSchemaForObjectComponents(arg) + if abiFunction.StateMutability != "" { + details["stateMutability"] = string(abiFunction.StateMutability) } - if s.Type == arrayType { - levels := strings.Count(arg.Type, "[]") - innerType := e.getFFIType(strings.ReplaceAll(arg.Type, "[]", "")) - innerSchema := &Schema{ - Type: innerType, - } - if len(arg.Components) > 0 { - innerSchema.Properties = e.getSchemaForObjectComponents(arg) + if abiFunction.Payable { + details["payable"] = true + } + if abiFunction.Constant { + details["constant"] = true + } + return &core.FFIMethod{ + Name: abiFunction.Name, + Params: params, + Returns: returns, + Details: details, + }, nil +} + +func (e *Ethereum) convertABIEventToFFIEvent(ctx context.Context, abiEvent *abi.Entry) (*core.FFIEvent, error) { + params := make([]*core.FFIParam, len(abiEvent.Inputs)) + details := map[string]interface{}{} + for i, output := range abiEvent.Inputs { + typeComponent, err := output.TypeComponentTreeCtx(ctx) + if err != nil { + return nil, err } - for i := 1; i < levels; i++ { - innerSchema = &Schema{ - Type: arrayType, - Items: innerSchema, - } + schema := e.getSchemaForABIInput(ctx, typeComponent) + param := &core.FFIParam{ + Name: output.Name, + Schema: fftypes.JSONAnyPtr(schema.ToJSON()), } - s.Items = innerSchema - } else { - s.Properties = properties + params[i] = param + } + if abiEvent.Anonymous { + details["anonymous"] = true } - return s + return &core.FFIEvent{ + FFIEventDefinition: core.FFIEventDefinition{ + Name: abiEvent.Name, + Params: params, + Details: details, + }, + }, nil } -func (e *Ethereum) getSchemaForObjectComponents(arg ABIArgumentMarshaling) map[string]*Schema { - m := make(map[string]*Schema, len(arg.Components)) - for i, component := range arg.Components { - componentSchema := e.getSchema(component) - componentSchema.Details.Index = new(int) - *componentSchema.Details.Index = i - m[component.Name] = componentSchema +func (e *Ethereum) getSchemaForABIInput(ctx context.Context, typeComponent abi.TypeComponent) *Schema { + schema := &Schema{ + Details: ¶mDetails{ + Type: typeComponent.Parameter().Type, + InternalType: typeComponent.Parameter().InternalType, + Indexed: typeComponent.Parameter().Indexed, + }, } - return m + switch typeComponent.ComponentType() { + case abi.ElementaryComponent: + t := e.getFFIType(typeComponent.ElementaryType().String()) + if t == core.FFIInputTypeInteger { + schema.OneOf = []SchemaType{ + {Type: "string"}, + {Type: "integer"}, + } + schema.Description = i18n.Expand(ctx, coremsgs.APIIntegerDescription) + } else { + schema.Type = t.String() + } + case abi.FixedArrayComponent, abi.DynamicArrayComponent: + schema.Type = arrayType + childSchema := e.getSchemaForABIInput(ctx, typeComponent.ArrayChild()) + schema.Items = childSchema + schema.Details = childSchema.Details + childSchema.Details = nil + case abi.TupleComponent: + schema.Type = objectType + schema.Properties = make(map[string]*Schema, len(typeComponent.TupleChildren())) + for i, tupleChild := range typeComponent.TupleChildren() { + childSchema := e.getSchemaForABIInput(ctx, tupleChild) + childSchema.Details.Index = new(int) + *childSchema.Details.Index = i + schema.Properties[tupleChild.KeyName()] = childSchema + } + } + return schema } -func (e *Ethereum) getFFIType(solitidyType string) string { - - switch solitidyType { +func (e *Ethereum) getFFIType(solidityType string) core.FFIInputType { + switch solidityType { case stringType, "address": - return stringType - case "bool": - return booleanType - case "tuple": - return objectType + return core.FFIInputTypeString + case boolType: + return core.FFIInputTypeBoolean + case tupleType: + return core.FFIInputTypeObject default: switch { - case strings.HasSuffix(solitidyType, "[]"): - return arrayType - case strings.Contains(solitidyType, "byte"): - return stringType - case strings.Contains(solitidyType, "int"): - return integerType + case strings.Contains(solidityType, "byte"): + return core.FFIInputTypeString + case strings.Contains(solidityType, "int"): + return core.FFIInputTypeInteger } } return "" diff --git a/internal/blockchain/ethereum/ethereum_test.go b/internal/blockchain/ethereum/ethereum_test.go index d766df181..df6e2d754 100644 --- a/internal/blockchain/ethereum/ethereum_test.go +++ b/internal/blockchain/ethereum/ethereum_test.go @@ -28,9 +28,12 @@ import ( "github.com/hyperledger/firefly-common/pkg/config" "github.com/hyperledger/firefly-common/pkg/ffresty" "github.com/hyperledger/firefly-common/pkg/fftypes" + "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" "github.com/hyperledger/firefly-common/pkg/wsclient" + "github.com/hyperledger/firefly-signer/pkg/abi" "github.com/hyperledger/firefly/internal/coreconfig" + "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/mocks/blockchainmocks" "github.com/hyperledger/firefly/mocks/metricsmocks" "github.com/hyperledger/firefly/mocks/wsmocks" @@ -52,17 +55,17 @@ func testFFIMethod() *core.FFIMethod { Params: []*core.FFIParam{ { Name: "x", - Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`), }, { Name: "y", - Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`), }, }, Returns: []*core.FFIParam{ { Name: "z", - Schema: fftypes.JSONAnyPtr(`{"type": "integer", "details": {"type": "uint256"}}`), + Schema: fftypes.JSONAnyPtr(`{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256"}}`), }, }, } @@ -1907,7 +1910,7 @@ func TestInvokeContractOK(t *testing.T) { method := testFFIMethod() params := map[string]interface{}{ "x": float64(1), - "y": float64(2), + "y": "1000000000000000000000000", } options := map[string]interface{}{ "customOption": "customValue", @@ -1922,7 +1925,7 @@ func TestInvokeContractOK(t *testing.T) { headers := body["headers"].(map[string]interface{}) assert.Equal(t, "SendTransaction", headers["type"]) assert.Equal(t, float64(1), params[0]) - assert.Equal(t, float64(2), params[1]) + assert.Equal(t, "1000000000000000000000000", params[1]) assert.Equal(t, body["customOption"].(string), "customValue") return httpmock.NewJsonResponderOrPanic(200, "")(req) }) @@ -1957,7 +1960,7 @@ func TestInvokeContractInvalidOption(t *testing.T) { headers := body["headers"].(map[string]interface{}) assert.Equal(t, "SendTransaction", headers["type"]) assert.Equal(t, float64(1), params[0]) - assert.Equal(t, float64(2), params[1]) + assert.Equal(t, "1000000000000000000000000", params[1]) return httpmock.NewJsonResponderOrPanic(200, "")(req) }) err = e.InvokeContract(context.Background(), "", signingKey, fftypes.JSONAnyPtrBytes(locationBytes), method, params, options) @@ -2298,20 +2301,20 @@ func TestFFIMethodToABI(t *testing.T) { Returns: []*core.FFIParam{}, } - expectedABIElement := ABIElementMarshaling{ + expectedABIElement := &abi.Entry{ Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "newValue", Type: "uint256", Indexed: false, }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, } - abi, err := e.FFIMethodToABI(context.Background(), method) + abi, err := e.FFIMethodToABI(context.Background(), method, nil) assert.NoError(t, err) assert.Equal(t, expectedABIElement, abi) } @@ -2358,15 +2361,15 @@ func TestFFIMethodToABIObject(t *testing.T) { Returns: []*core.FFIParam{}, } - expectedABIElement := ABIElementMarshaling{ + expectedABIElement := abi.Entry{ Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "widget", Type: "tuple", Indexed: false, - Components: []ABIArgumentMarshaling{ + Components: abi.ParameterArray{ { Name: "radius", Type: "uint256", @@ -2382,12 +2385,89 @@ func TestFFIMethodToABIObject(t *testing.T) { }, }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, } - abi, err := e.FFIMethodToABI(context.Background(), method) + abi, err := e.FFIMethodToABI(context.Background(), method, nil) assert.NoError(t, err) - assert.Equal(t, expectedABIElement, abi) + assert.ObjectsAreEqual(expectedABIElement, abi) +} + +func TestABIFFIConversionArrayOfObjects(t *testing.T) { + e, _ := newTestEthereum() + + abiJSON := `[ + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "enum ComplexStorage.Alignment", + "name": "alignment", + "type": "uint8" + } + ], + "internalType": "struct ComplexStorage.BoxContent[]", + "name": "contents", + "type": "tuple[]" + } + ], + "internalType": "struct ComplexStorage.Box[]", + "name": "newBox", + "type": "tuple[]" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "payable", + "type": "function", + "payable": true, + "constant": true + } + ]` + + var abi *abi.ABI + json.Unmarshal([]byte(abiJSON), &abi) + abiFunction := abi.Functions()["set"] + + ffiMethod, err := e.convertABIFunctionToFFIMethod(context.Background(), abiFunction) + assert.NoError(t, err) + abiFunctionOut, err := e.FFIMethodToABI(context.Background(), ffiMethod, nil) + assert.NoError(t, err) + + expectedABIFunctionJSON, err := json.Marshal(abiFunction) + assert.NoError(t, err) + abiFunctionJSON, err := json.Marshal(abiFunctionOut) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedABIFunctionJSON), string(abiFunctionJSON)) + } func TestFFIMethodToABINestedArray(t *testing.T) { @@ -2416,10 +2496,10 @@ func TestFFIMethodToABINestedArray(t *testing.T) { Returns: []*core.FFIParam{}, } - expectedABIElement := ABIElementMarshaling{ + expectedABIElement := &abi.Entry{ Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "widget", Type: "string[][]", @@ -2427,12 +2507,16 @@ func TestFFIMethodToABINestedArray(t *testing.T) { Indexed: false, }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, } - abi, err := e.FFIMethodToABI(context.Background(), method) + abi, err := e.FFIMethodToABI(context.Background(), method, nil) assert.NoError(t, err) - assert.Equal(t, expectedABIElement, abi) + expectedABIJSON, err := json.Marshal(expectedABIElement) + assert.NoError(t, err) + abiJSON, err := json.Marshal(abi) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedABIJSON), string(abiJSON)) } func TestFFIMethodToABIInvalidJSON(t *testing.T) { @@ -2449,7 +2533,7 @@ func TestFFIMethodToABIInvalidJSON(t *testing.T) { Returns: []*core.FFIParam{}, } - _, err := e.FFIMethodToABI(context.Background(), method) + _, err := e.FFIMethodToABI(context.Background(), method, nil) assert.Regexp(t, "invalid json", err) } @@ -2472,7 +2556,7 @@ func TestFFIMethodToABIBadSchema(t *testing.T) { Returns: []*core.FFIParam{}, } - _, err := e.FFIMethodToABI(context.Background(), method) + _, err := e.FFIMethodToABI(context.Background(), method, nil) assert.Regexp(t, "compilation failed", err) } @@ -2495,31 +2579,31 @@ func TestFFIMethodToABIBadReturn(t *testing.T) { }, } - _, err := e.FFIMethodToABI(context.Background(), method) + _, err := e.FFIMethodToABI(context.Background(), method, nil) assert.Regexp(t, "compilation failed", err) } func TestConvertABIToFFI(t *testing.T) { e, _ := newTestEthereum() - abi := []ABIElementMarshaling{ + abi := &abi.ABI{ { Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "newValue", Type: "uint256", InternalType: "uint256", }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, { Name: "get", Type: "function", - Inputs: []ABIArgumentMarshaling{}, - Outputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{}, + Outputs: abi.ParameterArray{ { Name: "value", Type: "uint256", @@ -2530,12 +2614,12 @@ func TestConvertABIToFFI(t *testing.T) { { Name: "Updated", Type: "event", - Inputs: []ABIArgumentMarshaling{{ + Inputs: abi.ParameterArray{{ Name: "value", Type: "uint256", InternalType: "uint256", }}, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, } @@ -2583,24 +2667,25 @@ func TestConvertABIToFFI(t *testing.T) { }, } - actualFFI := e.convertABIToFFI("default", "SimpleStorage", "v0.0.1", "desc", abi) + actualFFI, err := e.convertABIToFFI(context.Background(), "default", "SimpleStorage", "v0.0.1", "desc", abi) + assert.NoError(t, err) assert.NotNil(t, actualFFI) - assert.Equal(t, expectedFFI, actualFFI) + assert.ObjectsAreEqual(expectedFFI, actualFFI) } func TestConvertABIToFFIWithObject(t *testing.T) { e, _ := newTestEthereum() - abi := []ABIElementMarshaling{ - { + abi := &abi.ABI{ + &abi.Entry{ Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "newValue", Type: "tuple", InternalType: "struct WidgetFactory.Widget", - Components: []ABIArgumentMarshaling{ + Components: abi.ParameterArray{ { Name: "size", Type: "uint256", @@ -2614,11 +2699,12 @@ func TestConvertABIToFFIWithObject(t *testing.T) { }, }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, } - schema := fftypes.JSONAnyPtr(`{"type":"object","details":{"type":"tuple","internalType":"struct WidgetFactory.Widget"},"properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":1}},"size":{"type":"integer","details":{"type":"uint256","internalType":"uint256","index":0}}}}`) + bigIntDesc := i18n.Expand(context.Background(), coremsgs.APIIntegerDescription) + schema := fftypes.JSONAnyPtr(fmt.Sprintf(`{"type":"object","details":{"type":"tuple","internalType":"struct WidgetFactory.Widget"},"properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":1}},"size":{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256","internalType":"uint256","index":0},"description":"%s"}}}`, bigIntDesc)) expectedFFI := &core.FFI{ Name: "WidgetTest", @@ -2635,31 +2721,39 @@ func TestConvertABIToFFIWithObject(t *testing.T) { }, }, Returns: core.FFIParams{}, + Details: map[string]interface{}{}, }, }, Events: []*core.FFIEvent{}, } - actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi) + actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi) + assert.NoError(t, err) assert.NotNil(t, actualFFI) - assert.Equal(t, expectedFFI, actualFFI) + + expectedFFIJSON, err := json.Marshal(expectedFFI) + assert.NoError(t, err) + actualFFIJSON, err := json.Marshal(actualFFI) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON)) + } func TestConvertABIToFFIWithArray(t *testing.T) { e, _ := newTestEthereum() - abi := []ABIElementMarshaling{ + abi := &abi.ABI{ { Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "newValue", Type: "string[]", InternalType: "string[]", }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, } @@ -2680,35 +2774,42 @@ func TestConvertABIToFFIWithArray(t *testing.T) { }, }, Returns: core.FFIParams{}, + Details: map[string]interface{}{}, }, }, Events: []*core.FFIEvent{}, } - actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi) + actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi) + assert.NoError(t, err) assert.NotNil(t, actualFFI) - assert.Equal(t, expectedFFI, actualFFI) + + expectedFFIJSON, err := json.Marshal(expectedFFI) + assert.NoError(t, err) + actualFFIJSON, err := json.Marshal(actualFFI) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON)) } func TestConvertABIToFFIWithNestedArray(t *testing.T) { e, _ := newTestEthereum() - abi := []ABIElementMarshaling{ + abi := &abi.ABI{ { Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { Name: "newValue", - Type: "string[][]", - InternalType: "string[][]", + Type: "uint256[][]", + InternalType: "uint256[][]", }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, } - schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"string[][]","internalType":"string[][]"},"items":{"type":"array","items":{"type":"string"}}}`) + schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"uint256[][]","internalType":"uint256[][]"},"items":{"type":"array","items":{"oneOf":[{"type":"string"},{"type":"integer"}],"description":"An integer. You are recommended to use a JSON string. A JSON number can be used for values up to the safe maximum."}}}`) expectedFFI := &core.FFI{ Name: "WidgetTest", Version: "v0.0.1", @@ -2724,29 +2825,35 @@ func TestConvertABIToFFIWithNestedArray(t *testing.T) { }, }, Returns: core.FFIParams{}, + Details: map[string]interface{}{}, }, }, Events: []*core.FFIEvent{}, } - actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi) + actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi) + assert.NoError(t, err) assert.NotNil(t, actualFFI) - assert.Equal(t, expectedFFI, actualFFI) + expectedFFIJSON, err := json.Marshal(expectedFFI) + assert.NoError(t, err) + actualFFIJSON, err := json.Marshal(actualFFI) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON)) } func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) { e, _ := newTestEthereum() - abi := []ABIElementMarshaling{ + abi := &abi.ABI{ { Name: "set", Type: "function", - Inputs: []ABIArgumentMarshaling{ + Inputs: abi.ParameterArray{ { InternalType: "struct WidgetFactory.Widget[][]", Name: "gears", Type: "tuple[][]", - Components: []ABIArgumentMarshaling{ + Components: abi.ParameterArray{ { InternalType: "string", Name: "description", @@ -2765,11 +2872,12 @@ func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) { }, }, }, - Outputs: []ABIArgumentMarshaling{}, + Outputs: abi.ParameterArray{}, }, } - schema := fftypes.JSONAnyPtr(`{"type":"array","details":{"type":"tuple[][]","internalType":"struct WidgetFactory.Widget[][]"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":0}},"inUse":{"type":"boolean","details":{"type":"bool","internalType":"bool","index":2}},"size":{"type":"integer","details":{"type":"uint256","internalType":"uint256","index":1}}}}}}`) + bigIntDesc := i18n.Expand(context.Background(), coremsgs.APIIntegerDescription) + schema := fftypes.JSONAnyPtr(fmt.Sprintf(`{"type":"array","details":{"type":"tuple[][]","internalType":"struct WidgetFactory.Widget[][]"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","details":{"type":"string","internalType":"string","index":0}},"inUse":{"type":"boolean","details":{"type":"bool","internalType":"bool","index":2}},"size":{"oneOf":[{"type":"string"},{"type":"integer"}],"details":{"type":"uint256","internalType":"uint256","index":1},"description":"%s"}}}}}`, bigIntDesc)) expectedFFI := &core.FFI{ Name: "WidgetTest", Version: "v0.0.1", @@ -2785,14 +2893,20 @@ func TestConvertABIToFFIWithNestedArrayOfObjects(t *testing.T) { }, }, Returns: core.FFIParams{}, + Details: map[string]interface{}{}, }, }, Events: []*core.FFIEvent{}, } - actualFFI := e.convertABIToFFI("default", "WidgetTest", "v0.0.1", "desc", abi) + actualFFI, err := e.convertABIToFFI(context.Background(), "default", "WidgetTest", "v0.0.1", "desc", abi) + assert.NoError(t, err) assert.NotNil(t, actualFFI) - assert.Equal(t, expectedFFI, actualFFI) + expectedFFIJSON, err := json.Marshal(expectedFFI) + assert.NoError(t, err) + actualFFIJSON, err := json.Marshal(actualFFI) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedFFIJSON), string(actualFFIJSON)) } func TestGenerateFFI(t *testing.T) { @@ -2843,14 +2957,13 @@ func TestGenerateFFIBadABI(t *testing.T) { func TestGetFFIType(t *testing.T) { e, _ := newTestEthereum() - assert.Equal(t, e.getFFIType("string"), "string") - assert.Equal(t, e.getFFIType("address"), "string") - assert.Equal(t, e.getFFIType("byte"), "string") - assert.Equal(t, e.getFFIType("bool"), "boolean") - assert.Equal(t, e.getFFIType("uint256"), "integer") - assert.Equal(t, e.getFFIType("string[]"), "array") - assert.Equal(t, e.getFFIType("tuple"), "object") - assert.Equal(t, e.getFFIType("foobar"), "") + assert.Equal(t, core.FFIInputTypeString, e.getFFIType("string")) + assert.Equal(t, core.FFIInputTypeString, e.getFFIType("address")) + assert.Equal(t, core.FFIInputTypeString, e.getFFIType("byte")) + assert.Equal(t, core.FFIInputTypeBoolean, e.getFFIType("bool")) + assert.Equal(t, core.FFIInputTypeInteger, e.getFFIType("uint256")) + assert.Equal(t, core.FFIInputTypeObject, e.getFFIType("tuple")) + assert.Equal(t, fftypes.FFEnumValue("", ""), e.getFFIType("foobar")) } func TestGenerateEventSignature(t *testing.T) { @@ -3026,7 +3139,114 @@ func TestHandleNetworkActionFail(t *testing.T) { assert.EqualError(t, err, "pop") em.AssertExpectations(t) +} + +func TestConvertABIToFFIBadInputType(t *testing.T) { + e, _ := newTestEthereum() + + abiJSON := `[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "foobar" + } + ], + "name": "set", + "outputs": [], + "stateMutability": "payable", + "type": "function", + "payable": true, + "constant": true + } + ]` + + var abi *abi.ABI + json.Unmarshal([]byte(abiJSON), &abi) + _, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi) + assert.Regexp(t, "FF22025", err) +} + +func TestConvertABIToFFIBadOutputType(t *testing.T) { + e, _ := newTestEthereum() + + abiJSON := `[ + { + "outputs": [ + { + "internalType": "string", + "name": "name", + "type": "foobar" + } + ], + "name": "set", + "stateMutability": "viewable", + "type": "function" + } + ]` + + var abi *abi.ABI + json.Unmarshal([]byte(abiJSON), &abi) + _, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi) + assert.Regexp(t, "FF22025", err) +} + +func TestConvertABIToFFIBadEventType(t *testing.T) { + e, _ := newTestEthereum() + + abiJSON := `[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "foobar" + } + ], + "name": "set", + "type": "event" + } + ]` + + var abi *abi.ABI + json.Unmarshal([]byte(abiJSON), &abi) + _, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi) + assert.Regexp(t, "FF22025", err) +} + +func TestConvertABIEventFFIEvent(t *testing.T) { + e, _ := newTestEthereum() + + abiJSON := `[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "set", + "type": "event", + "anonymous": true + } + ]` + + var abi *abi.ABI + json.Unmarshal([]byte(abiJSON), &abi) + ffi, err := e.convertABIToFFI(context.Background(), "ns1", "name", "version", "description", abi) + assert.NoError(t, err) + + actualABIEvent, err := e.FFIEventDefinitionToABI(context.Background(), &ffi.Events[0].FFIEventDefinition) + assert.NoError(t, err) + + expectedABIEventJSON, err := json.Marshal(abi.Events()["set"]) + assert.NoError(t, err) + actualABIEventJSON, err := json.Marshal(actualABIEvent) + assert.NoError(t, err) + assert.JSONEq(t, string(expectedABIEventJSON), string(actualABIEventJSON)) } func TestNetworkVersion(t *testing.T) { diff --git a/internal/blockchain/ethereum/eventstream.go b/internal/blockchain/ethereum/eventstream.go index 4059a60a4..459184639 100644 --- a/internal/blockchain/ethereum/eventstream.go +++ b/internal/blockchain/ethereum/eventstream.go @@ -25,6 +25,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/hyperledger/firefly-common/pkg/ffresty" "github.com/hyperledger/firefly-common/pkg/log" + "github.com/hyperledger/firefly-signer/pkg/abi" "github.com/hyperledger/firefly/internal/coremsgs" "github.com/hyperledger/firefly/pkg/core" ) @@ -45,12 +46,12 @@ type eventStream struct { } type subscription struct { - ID string `json:"id"` - Name string `json:"name,omitempty"` - Stream string `json:"stream"` - FromBlock string `json:"fromBlock"` - Address string `json:"address"` - Event ABIElementMarshaling `json:"event"` + ID string `json:"id"` + Name string `json:"name,omitempty"` + Stream string `json:"stream"` + FromBlock string `json:"fromBlock"` + Address string `json:"address"` + Event *abi.Entry `json:"event"` } func (s *streamManager) getEventStreams(ctx context.Context) (streams []*eventStream, err error) { @@ -134,7 +135,7 @@ func (s *streamManager) getSubscriptions(ctx context.Context) (subs []*subscript return subs, nil } -func (s *streamManager) createSubscription(ctx context.Context, location *Location, stream, subName, fromBlock string, abi ABIElementMarshaling) (*subscription, error) { +func (s *streamManager) createSubscription(ctx context.Context, location *Location, stream, subName, fromBlock string, abi *abi.Entry) (*subscription, error) { // Map FireFly "firstEvent" values to Ethereum "fromBlock" values switch fromBlock { case string(core.SubOptsFirstEventOldest): @@ -170,7 +171,7 @@ func (s *streamManager) deleteSubscription(ctx context.Context, subID string) er return nil } -func (s *streamManager) ensureFireFlySubscription(ctx context.Context, instancePath, fromBlock, stream string, abi ABIElementMarshaling) (sub *subscription, err error) { +func (s *streamManager) ensureFireFlySubscription(ctx context.Context, instancePath, fromBlock, stream string, abi *abi.Entry) (sub *subscription, err error) { // Include a hash of the instance path in the subscription, so if we ever point at a different // contract configuration, we re-subscribe from block 0. // We don't need full strength hashing, so just use the first 16 chars for readability. diff --git a/internal/blockchain/ethereum/ffi_param_validator.go b/internal/blockchain/ethereum/ffi_param_validator.go index f64b1001f..898b94e2a 100644 --- a/internal/blockchain/ethereum/ffi_param_validator.go +++ b/internal/blockchain/ethereum/ffi_param_validator.go @@ -33,31 +33,40 @@ var bytesRegex, _ = regexp.Compile("^bytes([0-9]{1,2})?") func (v *FFIParamValidator) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) { valid := true if details, ok := m["details"]; ok { + var jsonTypeString string n, _ := details.(map[string]interface{}) blockchainType := n["type"].(string) - jsonType := m["type"].(string) - switch jsonType { - case "string": - if blockchainType != "string" && - blockchainType != "address" && + jsonType, ok := m["type"] + if ok { + jsonTypeString = jsonType.(string) + } else { + _, ok := m["oneOf"] + if ok { + jsonTypeString = integerType + } + } + switch jsonTypeString { + case stringType: + if blockchainType != stringType && + blockchainType != addressType && !isEthereumNumberType(blockchainType) && !isEthereumBytesType(blockchainType) { valid = false } - case "integer": + case integerType: if !isEthereumNumberType(blockchainType) { valid = false } - case "boolean": - if blockchainType != "bool" { + case booleanType: + if blockchainType != boolType { valid = false } - case "array": + case arrayType: if !strings.HasSuffix(blockchainType, "[]") { valid = false } - case "object": - if blockchainType != "tuple" { + case objectType: + if blockchainType != tupleType { valid = false } } @@ -75,118 +84,129 @@ func (v *FFIParamValidator) GetMetaSchema() *jsonschema.Schema { "$ref": "#/$defs/ethereumParam", "$defs": { "ethereumParam": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "not": { - "const": "object" + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "not": { + "const": "object" + } + }, + "details": { + "$ref": "#/$defs/details" + } + }, + "required": [ + "details" + ] + }, + { + "type": "object", + "properties": { + "type": { + "const": "object" + }, + "details": { + "$ref": "#/$defs/details" + }, + "properties": { + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/$defs/ethereumObjectChildParam" + } + } + } + }, + "required": [ + "details", + "type" + ] } + ] + }, + "ethereumObjectChildParam": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "not": { + "const": "object" + } + }, + "details": { + "$ref": "#/$defs/objectFieldDetails" + } + }, + "required": [ + "details" + ] }, - "details": { - "$ref": "#/$defs/details" + { + "type": "object", + "properties": { + "type": { + "const": "object" + }, + "details": { + "$ref": "#/$defs/objectFieldDetails" + }, + "properties": { + "type": "object", + "patternProperties": { + ".*": { + "$ref": "#/$defs/ethereumObjectChildParam" + } + } + } + }, + "required": [ + "details" + ] } - }, - "required": ["details"] - }, - { + ] + }, + "details": { "type": "object", "properties": { "type": { - "const": "object" + "type": "string" }, - "details": { - "$ref": "#/$defs/details" + "internalType": { + "type": "string" }, - "properties": { - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/$defs/ethereumObjectChildParam" - } - } + "indexed": { + "type": "boolean" } }, - "required": ["details"] - } - ] + "required": [ + "type" + ] }, - - "ethereumObjectChildParam": { - "oneOf": [ - { + "objectFieldDetails": { "type": "object", "properties": { "type": { - "type": "string", - "not": { - "const": "object" - } + "type": "string" }, - "details": { - "$ref": "#/$defs/objectFieldDetails" - } - }, - "required": ["details"] - }, - { - "type": "object", - "properties": { - "type": { - "const": "object" + "internalType": { + "type": "string" }, - "details": { - "$ref": "#/$defs/objectFieldDetails" + "indexed": { + "type": "boolean" }, - "properties": { - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/$defs/ethereumObjectChildParam" - } - } + "index": { + "type": "integer" } }, - "required": ["details"] - } - ] - }, - - "details": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "internalType": { - "type": "string" - }, - "indexed": { - "type": "boolean" - } - }, - "required": ["type"] - }, - - "objectFieldDetails": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "internalType": { - "type": "string" - }, - "indexed": { - "type": "boolean" - }, - "index": { - "type": "integer" - } - }, - "required": ["type", "index"] + "required": [ + "type", + "index" + ] } } }`) diff --git a/internal/blockchain/ethereum/ffi_param_validator_test.go b/internal/blockchain/ethereum/ffi_param_validator_test.go index 97a72854a..4f740b334 100644 --- a/internal/blockchain/ethereum/ffi_param_validator_test.go +++ b/internal/blockchain/ethereum/ffi_param_validator_test.go @@ -103,7 +103,7 @@ func TestSchemaTypeInvalidFFIType(t *testing.T) { "type": "uint256" } }`) - assert.Regexp(t, "'/type' does not validate", err) + assert.Regexp(t, "oneOf failed", err) } func TestSchemaTypeMissing(t *testing.T) { @@ -300,6 +300,34 @@ func TestInputInvalidNestedBlockchainType(t *testing.T) { assert.Regexp(t, "cannot cast integer to string", err) } +func TestInputInvalidOneOf(t *testing.T) { + _, err := NewTestSchema(` + { + "oneOf": "banana", + "details": { + "type": "uint256", + "internalType": "uint256" + } + }`) + assert.Regexp(t, "'/oneOf' does not validate", err) +} + +func TestInputInvalidOneOfType(t *testing.T) { + _, err := NewTestSchema(` + { + "oneOf": [ + { + "type": "banana" + } + ], + "details": { + "type": "uint256", + "internalType": "uint256" + } + }`) + assert.Regexp(t, "'/oneOf/0/type' does not validate", err) +} + func TestInputNoAdditionalProperties(t *testing.T) { s, err := NewTestSchema(` { diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go index d5f4f9eaf..df32b0580 100644 --- a/internal/coremsgs/en_api_translations.go +++ b/internal/coremsgs/en_api_translations.go @@ -189,4 +189,9 @@ var ( APIHistogramStartTimeParam = ffm("api.histogramStartTime", "Start time of the data to be fetched") APIHistogramEndTimeParam = ffm("api.histogramEndTime", "End time of the data to be fetched") APIHistogramBucketsParam = ffm("api.histogramBuckets", "Number of buckets between start time and end time") + APIIntegerDescription = ffm("api.integer", "An integer. You are recommended to use a JSON string. A JSON number can be used for values up to the safe maximum.") + + APISmartContractDetails = ffm("api.smartContractDetails", "Additional smart contract details") + APISmartContractDetailsKey = ffm("api.smartContractDetailsKey", "Key") + APISmartContractDetailsValue = ffm("api.smartContractDetailsValue", "Value") ) diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go index 5e6fce00d..f884bc79d 100644 --- a/internal/coremsgs/en_struct_descriptions.go +++ b/internal/coremsgs/en_struct_descriptions.go @@ -100,7 +100,7 @@ var ( // Member field descriptions MemberIdentity = ffm("Member.identity", "The DID of the group member") - MembertNode = ffm("Member.node", "The UUID of the node that receives a copy of the off-chain message for the identity") + MemberNode = ffm("Member.node", "The UUID of the node that receives a copy of the off-chain message for the identity") // DataRef field descriptions DataRefID = ffm("DataRef.id", "The UUID of the referenced data resource") @@ -247,6 +247,7 @@ var ( FFIMethodDescription = ffm("FFIMethod.description", "A description of the smart contract method") FFIMethodParams = ffm("FFIMethod.params", "An array of method parameter/argument definitions") FFIMethodReturns = ffm("FFIMethod.returns", "An array of method return definitions") + FFIMethodDetails = ffm("FFIMethod.details", "Additional blockchain specific fields about this method from the original smart contract. Used by the blockchain plugin and for documentation generation.") // FFIEvent field descriptions FFIEventID = ffm("FFIEvent.id", "The UUID of the FFI event definition") @@ -257,6 +258,7 @@ var ( FFIEventDescription = ffm("FFIEvent.description", "A description of the smart contract event") FFIEventParams = ffm("FFIEvent.params", "An array of event parameter/argument definitions") FFIEventSignature = ffm("FFIEvent.signature", "The stringified signature of the event, as computed by the blockchain plugin") + FFIEventDetails = ffm("FFIEvent.details", "Additional blockchain specific fields about this event from the original smart contract. Used by the blockchain plugin and for documentation generation.") // FFIParam field descriptions FFIParamName = ffm("FFIParam.name", "The name of the parameter. Note that parameters must be ordered correctly on the FFI, according to the order in the blockchain smart contract") diff --git a/internal/database/sqlcommon/ffi_events_sql.go b/internal/database/sqlcommon/ffi_events_sql.go index 83b7fdca4..f3baa90f6 100644 --- a/internal/database/sqlcommon/ffi_events_sql.go +++ b/internal/database/sqlcommon/ffi_events_sql.go @@ -38,6 +38,7 @@ var ( "pathname", "description", "params", + "details", } ffiEventFilterFieldMap = map[string]string{ "interface": "interface_id", @@ -87,6 +88,7 @@ func (s *SQLCommon) UpsertFFIEvent(ctx context.Context, event *core.FFIEvent) (e event.Pathname, event.Description, event.Params, + event.Details, ), func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionFFIEvents, core.ChangeEventTypeCreated, event.Namespace, event.ID) @@ -109,6 +111,7 @@ func (s *SQLCommon) ffiEventResult(ctx context.Context, row *sql.Rows) (*core.FF &event.Pathname, &event.Description, &event.Params, + &event.Details, ) if err != nil { return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, ffieventsTable) diff --git a/internal/database/sqlcommon/ffi_events_sql_test.go b/internal/database/sqlcommon/ffi_events_sql_test.go index a0aef1293..4eefa6c0a 100644 --- a/internal/database/sqlcommon/ffi_events_sql_test.go +++ b/internal/database/sqlcommon/ffi_events_sql_test.go @@ -178,7 +178,7 @@ func TestGetFFIEvents(t *testing.T) { ) s, mock := newMockProvider().init() rows := sqlmock.NewRows(ffiEventsColumns). - AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`)) + AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`{}`)) mock.ExpectQuery("SELECT .*").WillReturnRows(rows) _, _, err := s.GetFFIEvents(context.Background(), filter) assert.NoError(t, err) @@ -222,7 +222,7 @@ func TestGetFFIEventsQueryResultFail(t *testing.T) { func TestGetFFIEvent(t *testing.T) { s, mock := newMockProvider().init() rows := sqlmock.NewRows(ffiEventsColumns). - AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`)) + AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`{}`)) mock.ExpectQuery("SELECT .*").WillReturnRows(rows) FFIEvent, err := s.GetFFIEvent(context.Background(), "ns1", fftypes.NewUUID(), "math") assert.NoError(t, err) diff --git a/internal/database/sqlcommon/ffi_methods_sql.go b/internal/database/sqlcommon/ffi_methods_sql.go index 219251695..7363a61a5 100644 --- a/internal/database/sqlcommon/ffi_methods_sql.go +++ b/internal/database/sqlcommon/ffi_methods_sql.go @@ -39,6 +39,7 @@ var ( "description", "params", "returns", + "details", } ffiMethodFilterFieldMap = map[string]string{ "interface": "interface_id", @@ -90,6 +91,7 @@ func (s *SQLCommon) UpsertFFIMethod(ctx context.Context, method *core.FFIMethod) method.Description, method.Params, method.Returns, + method.Details, ), func() { s.callbacks.UUIDCollectionNSEvent(database.CollectionFFIMethods, core.ChangeEventTypeCreated, method.Namespace, method.ID) @@ -113,6 +115,7 @@ func (s *SQLCommon) ffiMethodResult(ctx context.Context, row *sql.Rows) (*core.F &method.Description, &method.Params, &method.Returns, + &method.Details, ) if err != nil { return nil, i18n.WrapError(ctx, err, coremsgs.MsgDBReadErr, ffimethodsTable) diff --git a/internal/database/sqlcommon/ffi_methods_sql_test.go b/internal/database/sqlcommon/ffi_methods_sql_test.go index d96aa5328..236b3876f 100644 --- a/internal/database/sqlcommon/ffi_methods_sql_test.go +++ b/internal/database/sqlcommon/ffi_methods_sql_test.go @@ -182,7 +182,7 @@ func TestGetFFIMethods(t *testing.T) { ) s, mock := newMockProvider().init() rows := sqlmock.NewRows(ffiMethodsColumns). - AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`)) + AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`), []byte(`{}`)) mock.ExpectQuery("SELECT .*").WillReturnRows(rows) _, _, err := s.GetFFIMethods(context.Background(), filter) assert.NoError(t, err) @@ -226,7 +226,7 @@ func TestGetFFIMethodsQueryResultFail(t *testing.T) { func TestGetFFIMethod(t *testing.T) { s, mock := newMockProvider().init() rows := sqlmock.NewRows(ffiMethodsColumns). - AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`)) + AddRow(fftypes.NewUUID().String(), fftypes.NewUUID().String(), "ns1", "sum", "sum", "", []byte(`[]`), []byte(`[]`), []byte(`{}`)) mock.ExpectQuery("SELECT .*").WillReturnRows(rows) FFIMethod, err := s.GetFFIMethod(context.Background(), "ns1", fftypes.NewUUID(), "math") assert.NoError(t, err) diff --git a/internal/reference/reference.go b/internal/reference/reference.go index cb5fa4f06..d53cc4a6a 100644 --- a/internal/reference/reference.go +++ b/internal/reference/reference.go @@ -184,6 +184,9 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e }`), }, }, + Details: fftypes.JSONObject{ + "stateMutability": "viewable", + }, }, { ID: fftypes.MustParseUUID("fc6f54ee-2e3c-4e56-b17c-4a1a0ae7394b"), @@ -204,6 +207,9 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e }, }, Returns: core.FFIParams{}, + Details: fftypes.JSONObject{ + "stateMutability": "payable", + }, }, }, Events: []*core.FFIEvent{ @@ -237,6 +243,7 @@ func GenerateObjectsReferenceMarkdown(ctx context.Context) (map[string][]byte, e }`), }, }, + Details: fftypes.JSONObject{}, }, }, }, diff --git a/pkg/core/batch.go b/pkg/core/batch.go index 799166d8b..a8e187fb6 100644 --- a/pkg/core/batch.go +++ b/pkg/core/batch.go @@ -78,11 +78,10 @@ type Batch struct { // BatchPersisted is the structure written to the database type BatchPersisted struct { BatchHeader - Hash *fftypes.Bytes32 `ffstruct:"Batch" json:"hash"` - Manifest *fftypes.JSONAny `ffstruct:"Batch" json:"manifest"` - TX TransactionRef `ffstruct:"Batch" json:"tx"` - PayloadRef string `ffstruct:"Batch" json:"payloadRef,omitempty"` - Confirmed *fftypes.FFTime `ffstruct:"Batch" json:"confirmed"` + Hash *fftypes.Bytes32 `ffstruct:"Batch" json:"hash"` + Manifest *fftypes.JSONAny `ffstruct:"Batch" json:"manifest"` + TX TransactionRef `ffstruct:"Batch" json:"tx"` + Confirmed *fftypes.FFTime `ffstruct:"Batch" json:"confirmed"` } // BatchPayload contains the full JSON of the messages and data, but diff --git a/pkg/core/ffi.go b/pkg/core/ffi.go index e2818f66a..504cacf80 100644 --- a/pkg/core/ffi.go +++ b/pkg/core/ffi.go @@ -26,6 +26,22 @@ import ( "github.com/santhosh-tekuri/jsonschema/v5" ) +// FFIInputType is the type of a JSON field in a request to FireFly's API +type FFIInputType = fftypes.FFEnum + +var ( + // FFIInputTypeInteger is a json integer or string to be treated as an integer + FFIInputTypeInteger = fftypes.FFEnumValue("ffiinputtype", "integer") + // FFIInputTypeString is a JSON string + FFIInputTypeString = fftypes.FFEnumValue("ffiinputtype", "string") + // FFIInputTypeArray is a JSON boolean + FFIInputTypeBoolean = fftypes.FFEnumValue("ffiinputtype", "boolean") + // FFIInputTypeArray is a JSON array + FFIInputTypeArray = fftypes.FFEnumValue("ffiinputtype", "array") + // FFIInputTypeObject is a JSON object + FFIInputTypeObject = fftypes.FFEnumValue("ffiinputtype", "object") +) + type FFIParamValidator interface { Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (jsonschema.ExtSchema, error) GetMetaSchema() *jsonschema.Schema @@ -50,20 +66,22 @@ type FFI struct { } type FFIMethod struct { - ID *fftypes.UUID `ffstruct:"FFIMethod" json:"id,omitempty" ffexcludeinput:"true"` - Interface *fftypes.UUID `ffstruct:"FFIMethod" json:"interface,omitempty" ffexcludeinput:"true"` - Name string `ffstruct:"FFIMethod" json:"name"` - Namespace string `ffstruct:"FFIMethod" json:"namespace,omitempty" ffexcludeinput:"true"` - Pathname string `ffstruct:"FFIMethod" json:"pathname" ffexcludeinput:"true"` - Description string `ffstruct:"FFIMethod" json:"description"` - Params FFIParams `ffstruct:"FFIMethod" json:"params"` - Returns FFIParams `ffstruct:"FFIMethod" json:"returns"` + ID *fftypes.UUID `ffstruct:"FFIMethod" json:"id,omitempty" ffexcludeinput:"true"` + Interface *fftypes.UUID `ffstruct:"FFIMethod" json:"interface,omitempty" ffexcludeinput:"true"` + Name string `ffstruct:"FFIMethod" json:"name"` + Namespace string `ffstruct:"FFIMethod" json:"namespace,omitempty" ffexcludeinput:"true"` + Pathname string `ffstruct:"FFIMethod" json:"pathname" ffexcludeinput:"true"` + Description string `ffstruct:"FFIMethod" json:"description"` + Params FFIParams `ffstruct:"FFIMethod" json:"params"` + Returns FFIParams `ffstruct:"FFIMethod" json:"returns"` + Details fftypes.JSONObject `ffstruct:"FFIMethod" json:"details,omitempty"` } type FFIEventDefinition struct { - Name string `ffstruct:"FFIEvent" json:"name"` - Description string `ffstruct:"FFIEvent" json:"description"` - Params FFIParams `ffstruct:"FFIEvent" json:"params"` + Name string `ffstruct:"FFIEvent" json:"name"` + Description string `ffstruct:"FFIEvent" json:"description"` + Params FFIParams `ffstruct:"FFIEvent" json:"params"` + Details fftypes.JSONObject `ffstruct:"FFIEvent" json:"details,omitempty"` } type FFIEvent struct { @@ -112,21 +130,21 @@ func (f *FFI) SetBroadcastMessage(msgID *fftypes.UUID) { } // Scan implements sql.Scanner -func (m *FFIParams) Scan(src interface{}) error { +func (p *FFIParams) Scan(src interface{}) error { switch src := src.(type) { case nil: - m = nil + p = nil return nil case string: - return json.Unmarshal([]byte(src), &m) + return json.Unmarshal([]byte(src), &p) case []byte: - return json.Unmarshal(src, &m) + return json.Unmarshal(src, &p) default: - return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, m) + return i18n.NewError(context.Background(), i18n.MsgTypeRestoreFailed, src, p) } } -func (m FFIParams) Value() (driver.Value, error) { - bytes, _ := json.Marshal(m) +func (p FFIParams) Value() (driver.Value, error) { + bytes, _ := json.Marshal(p) return bytes, nil } diff --git a/pkg/core/ffi_param_validator.go b/pkg/core/ffi_param_validator.go index 81e1ff6c8..2ac925686 100644 --- a/pkg/core/ffi_param_validator.go +++ b/pkg/core/ffi_param_validator.go @@ -28,20 +28,59 @@ func (v BaseFFIParamValidator) Compile(ctx jsonschema.CompilerContext, m map[str func (v *BaseFFIParamValidator) GetMetaSchema() *jsonschema.Schema { return jsonschema.MustCompileString("ffi.json", `{ - "properties" : { - "type": { - "type": "string", - "enum": [ - "boolean", - "integer", - "string", - "array", - "object" - ] + "$ref": "#/$defs/ffiParam", + "$defs": { + "integerTypeOptions": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "integer", + "string" + ] + } + } + }, + "ffiParam": { + "oneOf": [ + { + "properties": { + "type": { + "type": [ + "string" + ], + "enum": [ + "boolean", + "integer", + "string", + "array", + "object" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "oneOf": { + "type": "array", + "items": { + "$ref": "#/$defs/integerTypeOptions" + } + } + }, + "required": [ + "oneOf" + ] + } + ] + } } - }, - "required": ["type"] -}`) + }`) } func (v *BaseFFIParamValidator) GetExtensionName() string {