diff --git a/eth/rlp/writer.nim b/eth/rlp/writer.nim index a30959d..545a14d 100644 --- a/eth/rlp/writer.nim +++ b/eth/rlp/writer.nim @@ -156,7 +156,7 @@ proc appendImpl[T](self: var RlpWriter, listOrBlob: openArray[T]) = for i in 0 ..< listOrBlob.len: self.append listOrBlob[i] -proc countOptionalFields(T: type): int = +proc countOptionalFields(T: type): int {.compileTime.} = mixin enumerateRlpFields var dummy: T @@ -165,6 +165,8 @@ proc countOptionalFields(T: type): int = template op(RT, fN, f) = when f is Option or f is Opt: inc result + else: # this will count only optional fields at the end + result = 0 enumerateRlpFields(dummy, op) @@ -205,14 +207,18 @@ macro genOptionalFieldsValidation(obj: untyped, T: type, num: static[int]): unty proc countFieldsRuntime(obj: object|tuple): int = mixin enumerateRlpFields + var numOptionals: int = 0 + template op(RT, fN, f) {.used.} = when f is Option or f is Opt: if f.isSome: # if optional and non empty - inc result + inc numOptionals else: # if mandatory field inc result + numOptionals = 0 # count only optionals at the end (after mandatory) enumerateRlpFields(obj, op) + result += numOptionals proc appendRecordType*(self: var RlpWriter, obj: object|tuple, wrapInList = wrapObjsInList) = mixin enumerateRlpFields, append diff --git a/tests/rlp/all_tests.nim b/tests/rlp/all_tests.nim index 783b27d..625768c 100644 --- a/tests/rlp/all_tests.nim +++ b/tests/rlp/all_tests.nim @@ -2,4 +2,5 @@ import ./test_api_usage, ./test_json_suite, ./test_empty_string, - ./test_object_serialization + ./test_object_serialization, + ./test_optional_fields diff --git a/tests/rlp/test_optional_fields.nim b/tests/rlp/test_optional_fields.nim new file mode 100644 index 0000000..b504176 --- /dev/null +++ b/tests/rlp/test_optional_fields.nim @@ -0,0 +1,65 @@ +{.used.} + +import + ../../eth/[rlp, common], + unittest2 + +# Optionals in between mandatory fields for the convenience of +# implementation. According to the spec all optionals appear +# after mandatory fields. Moreover, an empty optional field +# cannot and will not appear before a non-empty optional field + +type ObjectWithOptionals = object + a* : uint64 + b* : uint64 + c* : Opt[uint64] # should not count this as optional + d* : Opt[uint64] # should not count this as optional + e* : uint64 + f* : uint64 + g* : uint64 + h* : Opt[uint64] # should not count this as optional + i* : Opt[uint64] # should not count this as optional + j* : Opt[uint64] # should not count this as optional + k* : uint64 + l* : Opt[uint64] # should count this as an optional + m* : Opt[uint64] # should count this as an optional + n* : Opt[uint64] # should count this as an optional + +var + objWithEmptyOptional: ObjectWithOptionals + objWithNonEmptyOptional: ObjectWithOptionals + objWithNonEmptyTrailingOptionals: ObjectWithOptionals + objWithEmptyTrailingOptionals: ObjectWithOptionals + +objWithNonEmptyOptional.c = Opt.some(0'u64) +objWithNonEmptyOptional.d = Opt.some(0'u64) +objWithNonEmptyOptional.h = Opt.some(0'u64) +objWithNonEmptyOptional.i = Opt.some(0'u64) +objWithNonEmptyOptional.j = Opt.some(0'u64) +objWithNonEmptyOptional.l = Opt.some(0'u64) +objWithNonEmptyOptional.m = Opt.some(0'u64) +objWithNonEmptyOptional.n = Opt.some(0'u64) + +objWithNonEmptyTrailingOptionals.l = Opt.some(0'u64) +objWithNonEmptyTrailingOptionals.m = Opt.some(0'u64) +objWithNonEmptyTrailingOptionals.n = Opt.some(0'u64) + +objWithEmptyTrailingOptionals.c = Opt.some(0'u64) +objWithEmptyTrailingOptionals.d = Opt.some(0'u64) +objWithEmptyTrailingOptionals.h = Opt.some(0'u64) +objWithEmptyTrailingOptionals.i = Opt.some(0'u64) +objWithEmptyTrailingOptionals.j = Opt.some(0'u64) + +suite "test optional fields": + test "all optionals are empty": + let bytes = rlp.encode(objWithEmptyOptional) + + test "all optionals are non empty": + let bytes = rlp.encode(objWithNonEmptyOptional) + + test "Only trailing optionals are non empty": + let bytes = rlp.encode(objWithNonEmptyTrailingOptionals) + + test "Only trailing optionals are empty": + let bytes = rlp.encode(objWithEmptyTrailingOptionals) +