Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions database.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
\restrict AsCPTOt3U9KhJ9fUdZGWUtrBsH1prEN0qQZE48Juv4M8WNoa7mPKZojrPqmzFhv
\restrict 7mZpScgT6ftGUlRA0ATHWmg8xgbioRvZDlJHy9RkZPwcyJEcQw9LTLQE0Lu8V3h

-- 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;
Expand Down Expand Up @@ -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: -
--
Expand Down Expand Up @@ -381,8 +401,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', 'transientStorageLayout']
) AND
validate_compilation_artifacts_abi(obj -> 'abi') AND
validate_compilation_artifacts_sources(obj -> 'sources');
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -1731,7 +1753,7 @@ ALTER TABLE ONLY public.verified_contracts
-- PostgreSQL database dump complete
--

\unrestrict AsCPTOt3U9KhJ9fUdZGWUtrBsH1prEN0qQZE48Juv4M8WNoa7mPKZojrPqmzFhv
\unrestrict 7mZpScgT6ftGUlRA0ATHWmg8xgbioRvZDlJHy9RkZPwcyJEcQw9LTLQE0Lu8V3h


--
Expand All @@ -1743,4 +1765,7 @@ INSERT INTO public.schema_migrations (version) VALUES
('20250723145429'),
('20251023134207'),
('20251106144315'),
('20260126113330');
('20260126113330'),
('20260224135405'),
('20260224171441'),
('20260225083159');
41 changes: 41 additions & 0 deletions migrations/20260224135405_support_old_solc_output.sql
Original file line number Diff line number Diff line change
@@ -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;
39 changes: 39 additions & 0 deletions migrations/20260224171441_add_transientStorageLayout.sql
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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);
28 changes: 18 additions & 10 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class CompiledContract:
compilation_artifacts = dict()
creation_code_artifacts = dict()
runtime_code_artifacts = dict()
additional_input = Null

@staticmethod
def dummy():
Expand Down Expand Up @@ -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:
Expand Down
49 changes: 49 additions & 0 deletions tests/test_constraint_additional_input_json_schema.py
Original file line number Diff line number Diff line change
@@ -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):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test data here does not accurately reflect the actual Vyper storage_layout_overrides structure. Per vyper/pull/4370, the format maps file path → variable name → {slot, type, n_slots} (two nesting levels), but the test collapses this to one level (treating "x" as a file path with a direct {slot, type, n_slots} value instead of a variable name map).

Since validate_additional_input intentionally does not validate the inner structure of storage_layout_overrides, the constraint test itself is still correct — but the test data is misleading. Suggest updating to:

dummy_compiled_contract.additional_input = {
    "storage_layout_overrides": {
        "contracts/foo.vy": {
            "x": {"slot": "0x0", "type": "uint256", "n_slots": 1}
        }
    }
}

@kuzdogan kuzdogan Mar 3, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from Claude.

I think we are missing one level of mapping?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Claude is just being stupid here.

We have "contracts/foo.vy" -> "x" -> {"slot": "0x0", "type": "uint256", "n_slots": 1}
what matches Claude's suggested type file path → variable name → {slot, type, n_slots}

# 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')
35 changes: 24 additions & 11 deletions tests/test_constraint_compilation_artifacts_json_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down