Skip to content

Commit 3b6a3c8

Browse files
Allow 'delete' type in CBOR auxdata transformations and update validation functions (#38)
* Allow 'delete' type in CBOR auxdata transformations and update validation functions * Address PR comments * Address PR comments * Length in auxdata transformation should always be greater than 0 * Add "optional length" comment also in runtime transformations * Add tests for new auxdata delete type and length property * Address comments in PR
1 parent af317b1 commit 3b6a3c8

6 files changed

Lines changed: 220 additions & 9 deletions

database.sql

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,32 @@ END;
614614
$$;
615615

616616

617+
--
618+
-- Name: validate_transformation_key_length(jsonb); Type: FUNCTION; Schema: public; Owner: -
619+
--
620+
621+
CREATE FUNCTION public.validate_transformation_key_length(object jsonb) RETURNS boolean
622+
LANGUAGE plpgsql
623+
AS $$
624+
BEGIN
625+
RETURN object ? 'length' AND is_jsonb_number(object -> 'length') AND (object ->> 'length')::integer > 0;
626+
END;
627+
$$;
628+
629+
--
630+
-- Name: validate_transformation_key_length_optional(jsonb); Type: FUNCTION; Schema: public; Owner: -
631+
--
632+
633+
CREATE FUNCTION public.validate_transformation_key_length_optional(object jsonb) RETURNS boolean
634+
LANGUAGE plpgsql
635+
AS $$
636+
BEGIN
637+
RETURN NOT object ? 'length'
638+
OR (is_jsonb_number(object -> 'length') AND (object ->> 'length')::integer > 0);
639+
END;
640+
$$;
641+
642+
617643
--
618644
-- Name: validate_transformation_key_type(jsonb, text); Type: FUNCTION; Schema: public; Owner: -
619645
--
@@ -691,8 +717,16 @@ CREATE FUNCTION public.validate_transformations_cbor_auxdata(object jsonb) RETUR
691717
LANGUAGE plpgsql
692718
AS $$
693719
BEGIN
694-
RETURN validate_transformation_key_type(object, 'replace') AND validate_transformation_key_offset(object)
695-
AND validate_transformation_key_id(object);
720+
RETURN (
721+
validate_transformation_key_type(object, 'replace')
722+
AND validate_transformation_key_offset(object)
723+
AND validate_transformation_key_length_optional(object)
724+
AND validate_transformation_key_id(object)
725+
) OR (
726+
validate_transformation_key_type(object, 'delete')
727+
AND validate_transformation_key_offset(object)
728+
AND validate_transformation_key_length(object)
729+
);
696730
END;
697731
$$;
698732

json-schemas/README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Apart from the specifications in each section below, the following rules apply t
2626

2727
- All hexadecimal value strings must be prefixed with `0x` such as addresses, constructor arguments etc.
2828
- `offset` values correspond to bytes in the bytecode and not string indexes. So `offset: 1` for the bytecode "0xab46fd" is the first byte in the bytecode corresponds to start from `46`
29+
- `length` values (for delete transformations) are in number of bytes.
2930

3031
## Transformations
3132

@@ -82,7 +83,8 @@ This object contains the transformation that will be applied to the creation byt
8283
The creation transformation can only contain these as `"reason"`s and `"type"`s:
8384

8485
- `{ "reason": "constructorArguments", "type": "insert", "offset": 999 }`
85-
- `{ "reason": "cborAuxdata", "type": "replace", "offset": 123, id: "0" }` Needs an `id` since there can be multiple auxdata transformations e.g. factories.
86+
- `{ "reason": "cborAuxdata", "type": "replace", "offset": 123, id: "0" }` Needs an `id` since there can be multiple auxdata transformations e.g. factories. Can also include optional `"length"`, if included it must be used instead of the length of the auxdata in the transformations.
87+
- `{ "reason": "cborAuxdata", "type": "delete", "offset": 123, "length": 45 }` Can also have `"type": "delete"` which means the onchain bytecode does not have the cborAuxdata while the recompiled bytecode has it.
8688
- `{ "reason": "library", "type": "replace", "offset": 123, id: "sources/lib/MyLib.sol:MyLib" }`
8789

8890
Example:
@@ -101,6 +103,12 @@ Example:
101103
"offset": 1269,
102104
"reason": "cborAuxdata"
103105
},
106+
{
107+
"type": "delete",
108+
"offset": 1295,
109+
"length": 47,
110+
"reason": "cborAuxdata"
111+
},
104112
{
105113
"type": "insert",
106114
"offset": 1322,
@@ -133,7 +141,8 @@ Similar to `creation_transformation`. But runtime code does not contain construc
133141

134142
The runtime transformations can only contain these as `"reason"`s and `"type"`s:
135143

136-
- `{ "reason": "cborAuxdata", "type": "replace", "offset": 123, id: "0" }` Needs an `id` since there can be multiple auxdata transformations e.g. factories.
144+
- `{ "reason": "cborAuxdata", "type": "replace", "offset": 123, id: "0" }` Needs an `id` since there can be multiple auxdata transformations e.g. factories. Can also include optional `"length"`, if included it must be used instead of the length of the auxdata in the transformations.
145+
- `{ "reason": "cborAuxdata", "type": "delete", "offset": 123, "length": 45 }` Can also have `"type": "delete"` which means the onchain bytecode does not have the cborAuxdata while the recompiled bytecode has it.
137146
- `{ "reason": "library", "type": "replace", "offset": 123, id: "contracts/order/OrderUtils.sol:OrderUtilsLib" }`
138147
- `{ "reason": "immutable", "type": "replace", "offset": 999, id: "2473" }` Needs an `id` for referencing multiple times and there can be multiple immutable transformations. Solidity contracts have `"replace"` type, while Vyper ones have `"insert"` because they are appended to the runtime bytecode.
139148
- `{ "reason": "callProtection", "type": "replace", "offset": 1 }`
@@ -220,7 +229,14 @@ Compiler settings as passed to the compiler in JSON format
220229
},
221230
"outputSelection": {
222231
"*": {
223-
"*": ["evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi"]
232+
"*": [
233+
"evm.bytecode",
234+
"evm.deployedBytecode",
235+
"devdoc",
236+
"userdoc",
237+
"metadata",
238+
"abi"
239+
]
224240
},
225241
"contracts/order/OrderUtils.sol": {
226242
"OrderUtils": ["*"]

json-schemas/verified_contracts-transformations.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@
3232
"required": [
3333
"type",
3434
"reason",
35-
"offset",
36-
"id"
35+
"offset"
3736
],
3837
"additionalProperties": false,
3938
"properties": {
4039
"type": {
4140
"type": "string",
42-
"value": "replace"
41+
"enum": [
42+
"replace",
43+
"delete"
44+
]
4345
},
4446
"reason": {
4547
"type": "string",
@@ -54,6 +56,10 @@
5456
"id": {
5557
"type": "string",
5658
"minLength": 1
59+
},
60+
"length": {
61+
"type": "number",
62+
"minimum": 1
5763
}
5864
}
5965
},
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
-- migrate:up
2+
CREATE OR REPLACE FUNCTION validate_transformation_key_length(object jsonb)
3+
RETURNS boolean AS
4+
$$
5+
BEGIN
6+
RETURN object ? 'length' AND is_jsonb_number(object -> 'length') AND (object ->> 'length')::integer > 0;
7+
END;
8+
$$ LANGUAGE plpgsql;
9+
10+
CREATE OR REPLACE FUNCTION validate_transformation_key_length_optional(object jsonb)
11+
RETURNS boolean AS
12+
$$
13+
BEGIN
14+
RETURN NOT object ? 'length'
15+
OR (is_jsonb_number(object -> 'length') AND (object ->> 'length')::integer > 0);
16+
END;
17+
$$ LANGUAGE plpgsql;
18+
19+
CREATE OR REPLACE FUNCTION validate_transformations_cbor_auxdata(object jsonb)
20+
RETURNS boolean AS
21+
$$
22+
BEGIN
23+
RETURN (
24+
validate_transformation_key_type(object, 'replace')
25+
AND validate_transformation_key_offset(object)
26+
AND validate_transformation_key_length_optional(object)
27+
AND validate_transformation_key_id(object)
28+
) OR (
29+
validate_transformation_key_type(object, 'delete')
30+
AND validate_transformation_key_offset(object)
31+
AND validate_transformation_key_length(object)
32+
);
33+
END;
34+
$$ LANGUAGE plpgsql;
35+
36+
-- migrate:down
37+
DROP FUNCTION IF EXISTS validate_transformation_key_length_optional(jsonb);
38+
DROP FUNCTION IF EXISTS validate_transformation_key_length(jsonb);
39+
40+
CREATE OR REPLACE FUNCTION validate_transformations_cbor_auxdata(object jsonb)
41+
RETURNS boolean AS
42+
$$
43+
BEGIN
44+
RETURN validate_transformation_key_type(object, 'replace') AND validate_transformation_key_offset(object)
45+
AND validate_transformation_key_id(object);
46+
END;
47+
$$ LANGUAGE plpgsql;

tests/test_constraint_creation_transformations_json_schema.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def test_expected_type_values(self, connection, dummy_code, dummy_contract, dumm
2222
{"reason": "constructorArguments", "type": "insert", "offset": 0},
2323
{"reason": "library", "type": "replace",
2424
"offset": 0, "id": "file1:lib1"},
25-
{"reason": "cborAuxdata", "type": "replace", "offset": 0, "id": "0"}
25+
{"reason": "cborAuxdata", "type": "replace", "offset": 0, "id": "0"},
26+
{"reason": "cborAuxdata", "type": "delete", "offset": 2, "length": 4}
2627
]
2728
dummy_verified_contract.insert(
2829
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
@@ -267,6 +268,20 @@ def test_valid_value(self, connection, dummy_code, dummy_contract, dummy_contrac
267268
dummy_verified_contract.insert(
268269
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
269270

271+
def test_valid_value_with_length(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
272+
dummy_verified_contract.creation_transformations = [
273+
{"reason": "cborAuxdata", "type": "replace", "offset": 6, "id": "0", "length": 12}
274+
]
275+
dummy_verified_contract.insert(
276+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
277+
278+
def test_valid_value_delete(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
279+
dummy_verified_contract.creation_transformations = [
280+
{"reason": "cborAuxdata", "type": "delete", "offset": 6, "length": 12}
281+
]
282+
dummy_verified_contract.insert(
283+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
284+
270285
def test_missing_key_type_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
271286
dummy_verified_contract.creation_transformations = [
272287
{"reason": "cborAuxdata", "offset": 0, "id": "0"}
@@ -325,6 +340,45 @@ def test_invalid_key_offset_value_fails(self, connection, dummy_code, dummy_cont
325340
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
326341
"creation_transformations_json_schema")
327342

343+
def test_missing_key_length_for_delete_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
344+
dummy_verified_contract.creation_transformations = [
345+
{"reason": "cborAuxdata", "type": "delete", "offset": 6}
346+
]
347+
check_constraint_fails(
348+
lambda: dummy_verified_contract.insert(
349+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
350+
"creation_transformations_json_schema")
351+
352+
@pytest.mark.parametrize("value", [None, "", [], dict()], ids=["null", "string", "array", "object"])
353+
def test_invalid_key_length_type_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
354+
dummy_verified_contract.creation_transformations = [
355+
{"reason": "cborAuxdata", "type": "replace", "offset": 6, "id": "0", "length": value}
356+
]
357+
check_constraint_fails(
358+
lambda: dummy_verified_contract.insert(
359+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
360+
"creation_transformations_json_schema")
361+
362+
@pytest.mark.parametrize("value", [None, "", [], dict()], ids=["null", "string", "array", "object"])
363+
def test_invalid_key_length_type_for_delete_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
364+
dummy_verified_contract.creation_transformations = [
365+
{"reason": "cborAuxdata", "type": "delete", "offset": 0, "length": value}
366+
]
367+
check_constraint_fails(
368+
lambda: dummy_verified_contract.insert(
369+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
370+
"creation_transformations_json_schema")
371+
372+
@pytest.mark.parametrize("value", [0, -1], ids=["zero", "negative"])
373+
def test_invalid_key_length_value_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
374+
dummy_verified_contract.creation_transformations = [
375+
{"reason": "cborAuxdata", "type": "delete", "offset": 6, "length": value}
376+
]
377+
check_constraint_fails(
378+
lambda: dummy_verified_contract.insert(
379+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
380+
"creation_transformations_json_schema")
381+
328382
def test_missing_key_id_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
329383
dummy_verified_contract.creation_transformations = [
330384
{"reason": "cborAuxdata", "type": "replace", "offset": 0}

tests/test_constraint_runtime_transformations_json_schema.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def test_expected_type_values(self, connection, dummy_code, dummy_contract, dumm
2323
"offset": 0, "id": "file1:lib1"},
2424
{"reason": "immutable", "type": "replace", "offset": 0, "id": "0"},
2525
{"reason": "cborAuxdata", "type": "replace", "offset": 0, "id": "0"},
26+
{"reason": "cborAuxdata", "type": "delete", "offset": 1, "length": 2},
2627
{"reason": "callProtection", "type": "replace", "offset": 0}
2728
]
2829
dummy_verified_contract.insert(
@@ -287,6 +288,20 @@ def test_valid_value(self, connection, dummy_code, dummy_contract, dummy_contrac
287288
dummy_verified_contract.insert(
288289
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
289290

291+
def test_valid_value_with_length(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
292+
dummy_verified_contract.runtime_transformations = [
293+
{"reason": "cborAuxdata", "type": "replace", "offset": 0, "id": "0", "length": 12}
294+
]
295+
dummy_verified_contract.insert(
296+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
297+
298+
def test_valid_value_delete(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
299+
dummy_verified_contract.runtime_transformations = [
300+
{"reason": "cborAuxdata", "type": "delete", "offset": 0, "length": 12}
301+
]
302+
dummy_verified_contract.insert(
303+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id)
304+
290305
def test_missing_key_type_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
291306
dummy_verified_contract.runtime_transformations = [
292307
{"reason": "cborAuxdata", "offset": 0, "id": "0"}
@@ -345,6 +360,45 @@ def test_invalid_key_offset_value_fails(self, connection, dummy_code, dummy_cont
345360
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
346361
"runtime_transformations_json_schema")
347362

363+
def test_missing_key_length_for_delete_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
364+
dummy_verified_contract.runtime_transformations = [
365+
{"reason": "cborAuxdata", "type": "delete", "offset": 0}
366+
]
367+
check_constraint_fails(
368+
lambda: dummy_verified_contract.insert(
369+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
370+
"runtime_transformations_json_schema")
371+
372+
@pytest.mark.parametrize("value", [None, "", [], dict()], ids=["null", "string", "array", "object"])
373+
def test_invalid_key_length_type_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
374+
dummy_verified_contract.runtime_transformations = [
375+
{"reason": "cborAuxdata", "type": "replace", "offset": 0, "id": "0", "length": value}
376+
]
377+
check_constraint_fails(
378+
lambda: dummy_verified_contract.insert(
379+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
380+
"runtime_transformations_json_schema")
381+
382+
@pytest.mark.parametrize("value", [None, "", [], dict()], ids=["null", "string", "array", "object"])
383+
def test_invalid_key_length_type_for_delete_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
384+
dummy_verified_contract.runtime_transformations = [
385+
{"reason": "cborAuxdata", "type": "delete", "offset": 0, "length": value}
386+
]
387+
check_constraint_fails(
388+
lambda: dummy_verified_contract.insert(
389+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
390+
"runtime_transformations_json_schema")
391+
392+
@pytest.mark.parametrize("value", [0, -1], ids=["zero", "negative"])
393+
def test_invalid_key_length_value_fails(self, value, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
394+
dummy_verified_contract.runtime_transformations = [
395+
{"reason": "cborAuxdata", "type": "delete", "offset": 0, "length": value}
396+
]
397+
check_constraint_fails(
398+
lambda: dummy_verified_contract.insert(
399+
connection, dummy_contract_deployment.id, dummy_compiled_contract.id),
400+
"runtime_transformations_json_schema")
401+
348402
def test_missing_key_id_fails(self, connection, dummy_code, dummy_contract, dummy_contract_deployment, dummy_compiled_contract, dummy_verified_contract):
349403
dummy_verified_contract.runtime_transformations = [
350404
{"reason": "cborAuxdata", "type": "replace", "offset": 0}

0 commit comments

Comments
 (0)