From c37fb2b24c96473b078259bf76a5b2acf0a0e34e Mon Sep 17 00:00:00 2001 From: Cherif Korchane Date: Thu, 19 Sep 2024 12:12:24 +0200 Subject: [PATCH 1/3] feat: classify and check a permit2 --- src/erc7730/classifier/eip712_classifier.py | 4 ++- .../display_format_checker/__init__.py | 36 ++++++++++++++++++- src/erc7730/linter/lint.py | 9 +---- .../linter_transaction_type_classifier_ai.py | 1 + 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/erc7730/classifier/eip712_classifier.py b/src/erc7730/classifier/eip712_classifier.py index 5a65e1b..f1d63d0 100644 --- a/src/erc7730/classifier/eip712_classifier.py +++ b/src/erc7730/classifier/eip712_classifier.py @@ -8,4 +8,6 @@ class EIP712Classifier(Classifier[EIP712JsonSchema]): @override def classify(self, schema: EIP712JsonSchema) -> TxClass | None: - pass + if "permit" in schema.primaryType.lower(): + return TxClass.PERMIT + return None diff --git a/src/erc7730/display_format_checker/__init__.py b/src/erc7730/display_format_checker/__init__.py index ba3cb5f..daf4b53 100644 --- a/src/erc7730/display_format_checker/__init__.py +++ b/src/erc7730/display_format_checker/__init__.py @@ -1,6 +1,6 @@ from erc7730.classifier import TxClass from erc7730.linter import Linter -from erc7730.model.display import Display +from erc7730.model.display import Display, Format class DisplayFormatChecker: @@ -8,5 +8,39 @@ def __init__(self, c: TxClass, d: Display): self.c = c self.d = d + def _get_all_displayed_labels(self, formats: dict[str, Format]) -> set[str]: + labels: set[str] = set() + # for format in formats.values(): # format: Format has fields: Optional[Fields] + # if format.fields is not None: + # format.fields is a dict[str, Union[Reference, Field, StructFormats]] + # for field in format.fields.values(): + # labels.append(field.label.lower()) + return labels + def check(self) -> list[Linter.Output]: + match self.c: + case TxClass.PERMIT: + formats = self.d.formats + labels = self._get_all_displayed_labels(formats) + if "spender" not in labels: + return [ + Linter.Output( + title="Missing spender in displayed fields", message="", level=Linter.Output.Level.ERROR + ) + ] + if "amount" not in labels: + return [ + Linter.Output( + title="Missing amount in displayed fields", message="", level=Linter.Output.Level.ERROR + ) + ] + if "valid until" not in labels and "expiry" not in labels and "expiration" not in labels: + return [ + Linter.Output( + title="Missing expiration date in displayed fields", + message="", + level=Linter.Output.Level.ERROR, + ) + ] + return [] return [] diff --git a/src/erc7730/linter/lint.py b/src/erc7730/linter/lint.py index ae25902..865ce83 100644 --- a/src/erc7730/linter/lint.py +++ b/src/erc7730/linter/lint.py @@ -3,23 +3,16 @@ from erc7730.common.pydantic import model_from_json_file from erc7730.linter import Linter from erc7730.linter.linter_base import MultiLinter -from erc7730.linter.linter_check_impacts import CheckImpactsLinter from erc7730.linter.linter_demo import DemoLinter from erc7730.linter.linter_transaction_type_classifier_ai import ClassifyTransactionTypeLinter -from erc7730.linter.linter_validate_abi import ValidateABILinter -from erc7730.linter.linter_validate_display_fields import ValidateDisplayFieldsLinter -from erc7730.linter.linter_validate_structure import ValidateStructureLinter from erc7730.model.erc7730_descriptor import ERC7730Descriptor from rich import print def lint_all(path: Path, demo: bool = False) -> list[Linter.Output]: linters: list[Linter] = [ - ValidateABILinter(), - ValidateStructureLinter(), - ValidateDisplayFieldsLinter(), + # DO_NO_COMMIT: add linters here ClassifyTransactionTypeLinter(), - CheckImpactsLinter(), ] if demo: diff --git a/src/erc7730/linter/linter_transaction_type_classifier_ai.py b/src/erc7730/linter/linter_transaction_type_classifier_ai.py index 796d176..7bb3e78 100644 --- a/src/erc7730/linter/linter_transaction_type_classifier_ai.py +++ b/src/erc7730/linter/linter_transaction_type_classifier_ai.py @@ -41,6 +41,7 @@ def lint(self, descriptor: ERC7730Descriptor, out: Linter.OutputAdder) -> None: c = determine_tx_class(descriptor) if c is None: return None + out(Linter.Output(title="Transaction Type: ", message=str(c), level=Linter.Output.Level.INFO)) d: Display | None = descriptor.display if d is None: return None From d9b8c7fa91ce48379a691fb2f530b315a691b09e Mon Sep 17 00:00:00 2001 From: Cherif Korchane Date: Thu, 19 Sep 2024 15:18:04 +0200 Subject: [PATCH 2/3] feat: warnings for permit2 --- demo-registry/uniswap/eip712-permit2.json | 7 --- .../display_format_checker/__init__.py | 52 ++++++++++++------- src/erc7730/linter/lint.py | 9 +++- .../linter_transaction_type_classifier_ai.py | 9 +++- src/erc7730/model/display.py | 2 +- 5 files changed, 49 insertions(+), 30 deletions(-) diff --git a/demo-registry/uniswap/eip712-permit2.json b/demo-registry/uniswap/eip712-permit2.json index 9dcdfbd..bff40f2 100644 --- a/demo-registry/uniswap/eip712-permit2.json +++ b/demo-registry/uniswap/eip712-permit2.json @@ -140,13 +140,6 @@ "params": { "tokenPath": "details.token" } - }, - "details.expiration": { - "label": "Valid Until", - "format": "date", - "params": { - "encoding": "timestamp" - } } }, "required": [ diff --git a/src/erc7730/display_format_checker/__init__.py b/src/erc7730/display_format_checker/__init__.py index daf4b53..5fea66b 100644 --- a/src/erc7730/display_format_checker/__init__.py +++ b/src/erc7730/display_format_checker/__init__.py @@ -3,44 +3,56 @@ from erc7730.model.display import Display, Format +def _fields_contain(word: str, fields: set[str]) -> bool: + # check if the word is contained in one of the fields + for field in fields: + if word.lower() in field.lower(): + return True + return False + + class DisplayFormatChecker: def __init__(self, c: TxClass, d: Display): self.c = c self.d = d - def _get_all_displayed_labels(self, formats: dict[str, Format]) -> set[str]: - labels: set[str] = set() - # for format in formats.values(): # format: Format has fields: Optional[Fields] - # if format.fields is not None: - # format.fields is a dict[str, Union[Reference, Field, StructFormats]] - # for field in format.fields.values(): - # labels.append(field.label.lower()) - return labels + def _get_all_displayed_fields(self, formats: dict[str, Format]) -> set[str]: + fields: set[str] = set() + for format in formats.values(): # format: Format has fields: Optional[Fields] + if format.fields is not None: + # format.fields is a dict[str, Union[FieldDescription, ...]] + for field in format.fields.root.keys(): + fields.add(str(field)) + return fields def check(self) -> list[Linter.Output]: + res: list[Linter.Output] = [] match self.c: case TxClass.PERMIT: formats = self.d.formats - labels = self._get_all_displayed_labels(formats) - if "spender" not in labels: - return [ + fields = self._get_all_displayed_fields(formats) + if not _fields_contain("spender", fields): + res.append( Linter.Output( title="Missing spender in displayed fields", message="", level=Linter.Output.Level.ERROR ) - ] - if "amount" not in labels: - return [ + ) + if not _fields_contain("amount", fields): + res.append( Linter.Output( title="Missing amount in displayed fields", message="", level=Linter.Output.Level.ERROR ) - ] - if "valid until" not in labels and "expiry" not in labels and "expiration" not in labels: - return [ + ) + if ( + not _fields_contain("valid until", fields) + and not _fields_contain("expiry", fields) + and not _fields_contain("expiration", fields) + ): + res.append( Linter.Output( title="Missing expiration date in displayed fields", message="", level=Linter.Output.Level.ERROR, ) - ] - return [] - return [] + ) + return res diff --git a/src/erc7730/linter/lint.py b/src/erc7730/linter/lint.py index 2e2895f..38faa90 100644 --- a/src/erc7730/linter/lint.py +++ b/src/erc7730/linter/lint.py @@ -3,8 +3,12 @@ from erc7730.common.pydantic import model_from_json_file from erc7730.linter import Linter from erc7730.linter.linter_base import MultiLinter +from erc7730.linter.linter_check_impacts import CheckImpactsLinter from erc7730.linter.linter_demo import DemoLinter from erc7730.linter.linter_transaction_type_classifier_ai import ClassifyTransactionTypeLinter +from erc7730.linter.linter_validate_abi import ValidateABILinter +from erc7730.linter.linter_validate_display_fields import ValidateDisplayFieldsLinter +from erc7730.linter.linter_validate_structure import ValidateStructureLinter from erc7730.model.erc7730_descriptor import ERC7730Descriptor from rich import print @@ -13,8 +17,11 @@ def lint_all(path: Path, demo: bool = False) -> list[Linter.Output]: linters: list[Linter] = [ - # DO_NO_COMMIT: add linters here + ValidateABILinter(), + ValidateStructureLinter(), + ValidateDisplayFieldsLinter(), ClassifyTransactionTypeLinter(), + CheckImpactsLinter(), ] if demo: diff --git a/src/erc7730/linter/linter_transaction_type_classifier_ai.py b/src/erc7730/linter/linter_transaction_type_classifier_ai.py index 7bb3e78..4b209b8 100644 --- a/src/erc7730/linter/linter_transaction_type_classifier_ai.py +++ b/src/erc7730/linter/linter_transaction_type_classifier_ai.py @@ -40,8 +40,15 @@ def lint(self, descriptor: ERC7730Descriptor, out: Linter.OutputAdder) -> None: return None c = determine_tx_class(descriptor) if c is None: + out( + Linter.Output( + title="Transaction type: ", + message="could not determine transaction type", + level=Linter.Output.Level.WARNING, + ) + ) return None - out(Linter.Output(title="Transaction Type: ", message=str(c), level=Linter.Output.Level.INFO)) + out(Linter.Output(title="Transaction type: ", message=str(c), level=Linter.Output.Level.INFO)) d: Display | None = descriptor.display if d is None: return None diff --git a/src/erc7730/model/display.py b/src/erc7730/model/display.py index eedd64a..aaabb32 100644 --- a/src/erc7730/model/display.py +++ b/src/erc7730/model/display.py @@ -74,7 +74,7 @@ class StructFormats(BaseLibraryModel): fields: ForwardRef("Fields") # type: ignore -class Fields(RootModel[dict[str, Union[Reference, Field, StructFormats]]]): +class Fields(RootModel[dict[str, Union[Reference, FieldDescription, Field, StructFormats]]]): """todo use StructFormats instead""" From 56bb100e5ebbc0710101a279f0de391d50fe7f9a Mon Sep 17 00:00:00 2001 From: Cherif Korchane Date: Thu, 19 Sep 2024 15:20:13 +0200 Subject: [PATCH 3/3] feat: cleanup --- src/erc7730/display_format_checker/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/erc7730/display_format_checker/__init__.py b/src/erc7730/display_format_checker/__init__.py index 5fea66b..7d5ea46 100644 --- a/src/erc7730/display_format_checker/__init__.py +++ b/src/erc7730/display_format_checker/__init__.py @@ -4,7 +4,9 @@ def _fields_contain(word: str, fields: set[str]) -> bool: - # check if the word is contained in one of the fields + """ + to check if the provided keyword is contained in one of the fields (case insensitive) + """ for field in fields: if word.lower() in field.lower(): return True @@ -18,9 +20,8 @@ def __init__(self, c: TxClass, d: Display): def _get_all_displayed_fields(self, formats: dict[str, Format]) -> set[str]: fields: set[str] = set() - for format in formats.values(): # format: Format has fields: Optional[Fields] + for format in formats.values(): if format.fields is not None: - # format.fields is a dict[str, Union[FieldDescription, ...]] for field in format.fields.root.keys(): fields.add(str(field)) return fields @@ -50,7 +51,7 @@ def check(self) -> list[Linter.Output]: ): res.append( Linter.Output( - title="Missing expiration date in displayed fields", + title="Missing expiration date in displayed fields for permit", message="", level=Linter.Output.Level.ERROR, )