From 637c7217a6268466912ab680f2f1073b0032fa9c Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Wed, 25 Feb 2026 09:27:35 +0100 Subject: [PATCH 1/4] Add migration `20260224135405_support_old_solc_output.sql` --- database.sql | 15 +++---- ...20260224135405_support_old_solc_output.sql | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 migrations/20260224135405_support_old_solc_output.sql diff --git a/database.sql b/database.sql index 792a94d..7621ca6 100644 --- a/database.sql +++ b/database.sql @@ -1,7 +1,7 @@ -\restrict AsCPTOt3U9KhJ9fUdZGWUtrBsH1prEN0qQZE48Juv4M8WNoa7mPKZojrPqmzFhv +\restrict rqFMSSTdh1xleP8vBtgWGfqkxcysKZzmCexIY6QPwSGZXxsDCrIV0BfKUvaEefZ --- Dumped from database version 16.11 (Ubuntu 16.11-1.pgdg24.04+1) --- Dumped by pg_dump version 16.11 (Ubuntu 16.11-1.pgdg24.04+1) +-- Dumped from database version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) +-- Dumped by pg_dump version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -381,8 +381,8 @@ BEGIN is_jsonb_object(obj) AND validate_json_object_keys( obj, - array ['abi', 'userdoc', 'devdoc', 'sources', 'storageLayout'], - array []::text[] + array ['abi', 'sources'], + array ['userdoc', 'devdoc', 'storageLayout'] ) AND validate_compilation_artifacts_abi(obj -> 'abi') AND validate_compilation_artifacts_sources(obj -> 'sources'); @@ -1731,7 +1731,7 @@ ALTER TABLE ONLY public.verified_contracts -- PostgreSQL database dump complete -- -\unrestrict AsCPTOt3U9KhJ9fUdZGWUtrBsH1prEN0qQZE48Juv4M8WNoa7mPKZojrPqmzFhv +\unrestrict rqFMSSTdh1xleP8vBtgWGfqkxcysKZzmCexIY6QPwSGZXxsDCrIV0BfKUvaEefZ -- @@ -1743,4 +1743,5 @@ INSERT INTO public.schema_migrations (version) VALUES ('20250723145429'), ('20251023134207'), ('20251106144315'), - ('20260126113330'); + ('20260126113330'), + ('20260224135405'); diff --git a/migrations/20260224135405_support_old_solc_output.sql b/migrations/20260224135405_support_old_solc_output.sql new file mode 100644 index 0000000..259aac0 --- /dev/null +++ b/migrations/20260224135405_support_old_solc_output.sql @@ -0,0 +1,41 @@ +-- migrate:up + +/* + Make 'userdoc', 'devdoc' and 'storageLayout' optional in the compilation artifacts, + because older solc versions do not output them. + See https://github.com/argotorg/sourcify/issues/2296 +*/ + +CREATE OR REPLACE FUNCTION validate_compilation_artifacts(obj jsonb) + RETURNS boolean AS +$$ +BEGIN + RETURN + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array ['abi', 'sources'], + array ['userdoc', 'devdoc', 'storageLayout'] + ) AND + validate_compilation_artifacts_abi(obj -> 'abi') AND + validate_compilation_artifacts_sources(obj -> 'sources'); +END; +$$ LANGUAGE plpgsql; + +-- migrate:down + +CREATE OR REPLACE FUNCTION validate_compilation_artifacts(obj jsonb) + RETURNS boolean AS +$$ +BEGIN + RETURN + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array ['abi', 'userdoc', 'devdoc', 'sources', 'storageLayout'], + array []::text[] + ) AND + validate_compilation_artifacts_abi(obj -> 'abi') AND + validate_compilation_artifacts_sources(obj -> 'sources'); +END; +$$ LANGUAGE plpgsql; From 6165611cb61c85773c55bf06dcfbceb79f10c960 Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Wed, 25 Feb 2026 09:30:36 +0100 Subject: [PATCH 2/4] Add migration `20260224171441_add_transientStorageLayout.sql` --- database.sql | 9 +++-- ...60224171441_add_transientStorageLayout.sql | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 migrations/20260224171441_add_transientStorageLayout.sql diff --git a/database.sql b/database.sql index 7621ca6..86b406f 100644 --- a/database.sql +++ b/database.sql @@ -1,4 +1,4 @@ -\restrict rqFMSSTdh1xleP8vBtgWGfqkxcysKZzmCexIY6QPwSGZXxsDCrIV0BfKUvaEefZ +\restrict pbViNAGNy06IgsZrLYssEf8nHcmLjNQjeTBkhkhp0rHRqbCQObLRIy7P0MxiETv -- Dumped from database version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) -- Dumped by pg_dump version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) @@ -382,7 +382,7 @@ BEGIN validate_json_object_keys( obj, array ['abi', 'sources'], - array ['userdoc', 'devdoc', 'storageLayout'] + array ['userdoc', 'devdoc', 'storageLayout', 'transientStorageLayout'] ) AND validate_compilation_artifacts_abi(obj -> 'abi') AND validate_compilation_artifacts_sources(obj -> 'sources'); @@ -1731,7 +1731,7 @@ ALTER TABLE ONLY public.verified_contracts -- PostgreSQL database dump complete -- -\unrestrict rqFMSSTdh1xleP8vBtgWGfqkxcysKZzmCexIY6QPwSGZXxsDCrIV0BfKUvaEefZ +\unrestrict pbViNAGNy06IgsZrLYssEf8nHcmLjNQjeTBkhkhp0rHRqbCQObLRIy7P0MxiETv -- @@ -1744,4 +1744,5 @@ INSERT INTO public.schema_migrations (version) VALUES ('20251023134207'), ('20251106144315'), ('20260126113330'), - ('20260224135405'); + ('20260224135405'), + ('20260224171441'); diff --git a/migrations/20260224171441_add_transientStorageLayout.sql b/migrations/20260224171441_add_transientStorageLayout.sql new file mode 100644 index 0000000..ab80f16 --- /dev/null +++ b/migrations/20260224171441_add_transientStorageLayout.sql @@ -0,0 +1,39 @@ +-- migrate:up + +/* + Allow storing the new compiler output field `transientStorageLayout`. +*/ + +CREATE OR REPLACE FUNCTION validate_compilation_artifacts(obj jsonb) + RETURNS boolean AS +$$ +BEGIN + RETURN + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array ['abi', 'sources'], + array ['userdoc', 'devdoc', 'storageLayout', 'transientStorageLayout'] + ) AND + validate_compilation_artifacts_abi(obj -> 'abi') AND + validate_compilation_artifacts_sources(obj -> 'sources'); +END; +$$ LANGUAGE plpgsql; + +-- migrate:down + +CREATE OR REPLACE FUNCTION validate_compilation_artifacts(obj jsonb) + RETURNS boolean AS +$$ +BEGIN + RETURN + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array ['abi', 'sources'], + array ['userdoc', 'devdoc', 'storageLayout'] + ) AND + validate_compilation_artifacts_abi(obj -> 'abi') AND + validate_compilation_artifacts_sources(obj -> 'sources'); +END; +$$ LANGUAGE plpgsql; From 10c245433a4398f4effdfe9ab66b4100f4092725 Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Wed, 25 Feb 2026 09:54:24 +0100 Subject: [PATCH 3/4] Add migration `20260225083159_add_additional_input_to_compiled_contracts.sql` --- database.sql | 29 +++++++++++++-- ...additional_input_to_compiled_contracts.sql | 36 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 migrations/20260225083159_add_additional_input_to_compiled_contracts.sql diff --git a/database.sql b/database.sql index 86b406f..720a219 100644 --- a/database.sql +++ b/database.sql @@ -1,4 +1,4 @@ -\restrict pbViNAGNy06IgsZrLYssEf8nHcmLjNQjeTBkhkhp0rHRqbCQObLRIy7P0MxiETv +\restrict 7mZpScgT6ftGUlRA0ATHWmg8xgbioRvZDlJHy9RkZPwcyJEcQw9LTLQE0Lu8V3h -- Dumped from database version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) -- Dumped by pg_dump version 16.12 (Ubuntu 16.12-1.pgdg24.04+1) @@ -195,6 +195,26 @@ END; $$; +-- +-- Name: validate_additional_input(jsonb); Type: FUNCTION; Schema: public; Owner: - +-- + +CREATE FUNCTION public.validate_additional_input(obj jsonb) RETURNS boolean + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN obj IS NULL OR ( + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array []::text[], + array ['storage_layout_overrides'] + ) + ); +END; +$$; + + -- -- Name: validate_code_artifacts_cbor_auxdata(jsonb); Type: FUNCTION; Schema: public; Owner: - -- @@ -933,6 +953,8 @@ CREATE TABLE public.compiled_contracts ( creation_code_artifacts jsonb NOT NULL, runtime_code_hash bytea NOT NULL, runtime_code_artifacts jsonb NOT NULL, + additional_input jsonb, + CONSTRAINT additional_input_json_schema CHECK (public.validate_additional_input(additional_input)), CONSTRAINT compilation_artifacts_json_schema CHECK (public.validate_compilation_artifacts(compilation_artifacts)), CONSTRAINT creation_code_artifacts_json_schema CHECK (public.validate_creation_code_artifacts(creation_code_artifacts)), CONSTRAINT runtime_code_artifacts_json_schema CHECK (public.validate_runtime_code_artifacts(runtime_code_artifacts)) @@ -1731,7 +1753,7 @@ ALTER TABLE ONLY public.verified_contracts -- PostgreSQL database dump complete -- -\unrestrict pbViNAGNy06IgsZrLYssEf8nHcmLjNQjeTBkhkhp0rHRqbCQObLRIy7P0MxiETv +\unrestrict 7mZpScgT6ftGUlRA0ATHWmg8xgbioRvZDlJHy9RkZPwcyJEcQw9LTLQE0Lu8V3h -- @@ -1745,4 +1767,5 @@ INSERT INTO public.schema_migrations (version) VALUES ('20251106144315'), ('20260126113330'), ('20260224135405'), - ('20260224171441'); + ('20260224171441'), + ('20260225083159'); diff --git a/migrations/20260225083159_add_additional_input_to_compiled_contracts.sql b/migrations/20260225083159_add_additional_input_to_compiled_contracts.sql new file mode 100644 index 0000000..3080942 --- /dev/null +++ b/migrations/20260225083159_add_additional_input_to_compiled_contracts.sql @@ -0,0 +1,36 @@ +-- migrate:up + +/* + Add `additional_input` column to `compiled_contracts` to store compiler options + that appear at the top level of standard JSON input but are not part of the `settings` field. + Currently restricted to `storage_layout_overrides`, used by Vyper. + See https://github.com/vyperlang/vyper/pull/4370 +*/ + +CREATE OR REPLACE FUNCTION validate_additional_input(obj jsonb) + RETURNS boolean AS +$$ +BEGIN + RETURN obj IS NULL OR ( + is_jsonb_object(obj) AND + validate_json_object_keys( + obj, + array []::text[], + array ['storage_layout_overrides'] + ) + ); +END; +$$ LANGUAGE plpgsql; + +ALTER TABLE compiled_contracts + ADD COLUMN additional_input jsonb; + +ALTER TABLE compiled_contracts + ADD CONSTRAINT additional_input_json_schema + CHECK (validate_additional_input(additional_input)); + +-- migrate:down + +ALTER TABLE compiled_contracts DROP CONSTRAINT IF EXISTS additional_input_json_schema; +ALTER TABLE compiled_contracts DROP COLUMN IF EXISTS additional_input; +DROP FUNCTION IF EXISTS validate_additional_input(jsonb); From 46926f20655d671bf2c27af2255f94c55d1ed8af Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Wed, 25 Feb 2026 10:12:25 +0100 Subject: [PATCH 4/4] Add tests for new migrations --- tests/helpers.py | 28 +++++++---- ...constraint_additional_input_json_schema.py | 49 +++++++++++++++++++ ...raint_compilation_artifacts_json_schema.py | 35 ++++++++----- 3 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 tests/test_constraint_additional_input_json_schema.py diff --git a/tests/helpers.py b/tests/helpers.py index 2132839..ed88d3f 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -101,6 +101,7 @@ class CompiledContract: compilation_artifacts = dict() creation_code_artifacts = dict() runtime_code_artifacts = dict() + additional_input = Null @staticmethod def dummy(): @@ -128,17 +129,24 @@ def dummy(): return instance def insert(self, connection, creation_code_hash, runtime_code_hash): + query_columns = ("id, compiler, version, language, name, fully_qualified_name, " + "compiler_settings, compilation_artifacts, creation_code_hash, creation_code_artifacts, " + "runtime_code_hash, runtime_code_artifacts") + values = [self.id, self.compiler, self.version, self.language, self.name, self.fully_qualified_name, + json.dumps(self.compiler_settings), json.dumps(self.compilation_artifacts), + creation_code_hash, json.dumps(self.creation_code_artifacts), + runtime_code_hash, json.dumps(self.runtime_code_artifacts)] + + if self.additional_input != Null: + query_columns += ", additional_input" + values.append(json.dumps(self.additional_input)) + + query_values = ", ".join(["%s"] * len(values)) with connection.cursor() as cursor: - cursor.execute(""" - INSERT INTO compiled_contracts ( - id, compiler, version, language, name, fully_qualified_name, - compiler_settings, compilation_artifacts, creation_code_hash, creation_code_artifacts, - runtime_code_hash, runtime_code_artifacts) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, (self.id, self.compiler, self.version, self.language, self.name, self.fully_qualified_name, - json.dumps(self.compiler_settings), json.dumps( - self.compilation_artifacts), creation_code_hash, json.dumps(self.creation_code_artifacts), - runtime_code_hash, json.dumps(self.runtime_code_artifacts))) + cursor.execute(f""" + INSERT INTO compiled_contracts ({query_columns}) + VALUES ({query_values}) + """, values) class VerifiedContract: diff --git a/tests/test_constraint_additional_input_json_schema.py b/tests/test_constraint_additional_input_json_schema.py new file mode 100644 index 0000000..e208e3a --- /dev/null +++ b/tests/test_constraint_additional_input_json_schema.py @@ -0,0 +1,49 @@ +from helpers import * + + +@pytest.fixture(scope='function', autouse=True) +def setup(connection, dummy_code): + dummy_code.insert(connection) + + +class TestAdditionalInput: + def test_null_succeeds(self, connection, dummy_code, dummy_compiled_contract): + # additional_input defaults to SQL NULL when not set + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_storage_layout_overrides_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.additional_input = { + "storage_layout_overrides": { + "x": {"slot": "0x0", "type": "uint256", "n_slots": 1} + } + } + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_empty_object_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.additional_input = {} + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_unknown_key_fails(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.additional_input = {"unknown_key": {}} + check_constraint_fails( + lambda: dummy_compiled_contract.insert( + connection, dummy_code.code_hash, dummy_code.code_hash), + 'additional_input_json_schema') + + def test_unknown_key_alongside_valid_key_fails(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.additional_input = { + "storage_layout_overrides": {}, + "unknown_key": {} + } + check_constraint_fails( + lambda: dummy_compiled_contract.insert( + connection, dummy_code.code_hash, dummy_code.code_hash), + 'additional_input_json_schema') + + @pytest.mark.parametrize("value", ["a string", [1, 2], 42], ids=["string", "array", "number"]) + def test_non_object_type_fails(self, value, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.additional_input = value + check_constraint_fails( + lambda: dummy_compiled_contract.insert( + connection, dummy_code.code_hash, dummy_code.code_hash), + 'additional_input_json_schema') diff --git a/tests/test_constraint_compilation_artifacts_json_schema.py b/tests/test_constraint_compilation_artifacts_json_schema.py index e94ed10..0a03a7d 100644 --- a/tests/test_constraint_compilation_artifacts_json_schema.py +++ b/tests/test_constraint_compilation_artifacts_json_schema.py @@ -59,32 +59,45 @@ def test_missing_abi_fails(self, connection, dummy_code, dummy_compiled_contract check_constraint_fails( lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') - def test_missing_userdoc_fails(self, connection, dummy_code, dummy_compiled_contract): + def test_missing_userdoc_succeeds(self, connection, dummy_code, dummy_compiled_contract): dummy_compiled_contract.compilation_artifacts.pop("userdoc") - check_constraint_fails( - lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) - def test_missing_devdoc_fails(self, connection, dummy_code, dummy_compiled_contract): + def test_missing_devdoc_succeeds(self, connection, dummy_code, dummy_compiled_contract): dummy_compiled_contract.compilation_artifacts.pop("devdoc") - check_constraint_fails( - lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_missing_storage_layout_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.compilation_artifacts.pop("storageLayout") + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_only_mandatory_fields_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.compilation_artifacts = {"abi": None, "sources": None} + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) def test_missing_sources_fails(self, connection, dummy_code, dummy_compiled_contract): dummy_compiled_contract.compilation_artifacts.pop("sources") check_constraint_fails( lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') - def test_missing_storage_layout_fails(self, connection, dummy_code, dummy_compiled_contract): - dummy_compiled_contract.compilation_artifacts.pop("storageLayout") - check_constraint_fails( - lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') - def test_unknown_field_fails(self, connection, dummy_code, dummy_compiled_contract): dummy_compiled_contract.compilation_artifacts['unknown_key'] = None check_constraint_fails( lambda: dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash), 'compilation_artifacts_json_schema') +class TestTransientStorageLayout: + # transientStorageLayout was added as an optional field by the add_transientStorageLayout migration + + def test_with_transient_storage_layout_as_none_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.compilation_artifacts["transientStorageLayout"] = None + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + def test_with_transient_storage_layout_as_object_succeeds(self, connection, dummy_code, dummy_compiled_contract): + dummy_compiled_contract.compilation_artifacts["transientStorageLayout"] = {"MyVar": {"slot": "0x0", "type": "uint256"}} + dummy_compiled_contract.insert(connection, dummy_code.code_hash, dummy_code.code_hash) + + class TestAbi: @pytest.mark.parametrize("value", [0, "", {}], ids=["number", "string", "object"]) def test_invalid_type_fails(self, value, connection, dummy_code, dummy_compiled_contract):