Add signature literals (#505)

* Add signature literals + handle system alias types

* Add tests for signature literals + encode/decode

* Add to CHANGELOG

* Add in documentation

* Additional documentation
This commit is contained in:
Hans Svensson 2024-04-10 16:34:29 +02:00 committed by GitHub
parent 31301911a2
commit 51bae61736
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 93 additions and 15 deletions

View File

@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Support for OTP-27 - no changes in behavior.
- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`).
### Changed
- System aliases are handled explicitly when converting to a Sophia value, this is only
observable for `signature` where a value of type `signature` is now represented as a
(new) signature literal.
### Removed
### Fixed
- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`.

View File

@ -573,7 +573,7 @@ Sophia has the following types:
| state | `state{ owner = Call.origin, magic_key = #a298105f }` |
| event | `EventX(0, "Hello")` |
| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
| signature | `sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj`, `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` |
| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` |
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |

View File

@ -30,6 +30,7 @@ interface main using as for hiding
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix
- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` prefix
Valid string escape codes are
@ -239,6 +240,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
| AccountAddress | ContractAddress // Chain identifiers
| OracleAddress | OracleQueryId // Chain identifiers
| Signature // Signature
| '???' // Hole expression 1 + ???
Generator ::= Pattern '<-' Expr // Generator

View File

@ -198,7 +198,8 @@ encode_expr({bytes, _, B}) ->
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
Lit == contract_pubkey; Lit == account_pubkey;
Lit == signature ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, {'-', _}, [{int, _, N}]}) ->
encode_expr({int, [], -N});

View File

@ -1935,6 +1935,8 @@ infer_expr(_Env, Body={bytes, As, Bin}) ->
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
infer_expr(_Env, Body={account_pubkey, As, _}) ->
{typed, As, Body, {id, As, "address"}};
infer_expr(_Env, Body={signature, As, Bin}) when byte_size(Bin) == 64 ->
{typed, As, Body, {bytes_t, As, 64}};
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
Q = fresh_uvar(As),
R = fresh_uvar(As),
@ -2173,6 +2175,8 @@ check_valid_const_expr({bytes, _, _}) ->
true;
check_valid_const_expr({account_pubkey, _, _}) ->
true;
check_valid_const_expr({signature, _, _}) ->
true;
check_valid_const_expr({oracle_pubkey, _, _}) ->
true;
check_valid_const_expr({oracle_query_id, _, _}) ->
@ -3060,7 +3064,8 @@ unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id)
unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) ->
%% Like the case above, but for types without parameters.
when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, 0) end),
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
UnfoldSysAlias = not proplists:get_value(not_unfold_system_alias_types, Options, false),
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
case lookup_type(Env, Id) of
{_, {_, {[], {record_t, Fields}}}} when UnfoldRecords ->
@ -3068,7 +3073,12 @@ unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) ->
{_, {_, {[], {variant_t, Constrs}}}} when UnfoldVariants ->
{variant_t, unfold_types_in_type(Env, Constrs, Options)};
{_, {_, {[], {alias_t, Type1}}}} ->
unfold_types_in_type(Env, Type1, Options);
case aeso_syntax:get_ann(Type1) of
[{origin, system}] when not UnfoldSysAlias ->
Id;
_ ->
unfold_types_in_type(Env, Type1, Options)
end;
_ ->
%% Not a record type, or ill-formed record type
Id

View File

@ -55,6 +55,7 @@
| {contract_pubkey, binary()}
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {signature, binary()}
| {bool, false | true}
| {contract_code, string()} %% for CREATE, by name
| {typerep, ftype()}.
@ -599,6 +600,7 @@ expr_to_fcode(_Env, _Type, {char, Ann, N}) -> {lit, to_fann(Ann), {in
expr_to_fcode(_Env, _Type, {bool, Ann, B}) -> {lit, to_fann(Ann), {bool, B}};
expr_to_fcode(_Env, _Type, {string, Ann, S}) -> {lit, to_fann(Ann), {string, S}};
expr_to_fcode(_Env, _Type, {account_pubkey, Ann, K}) -> {lit, to_fann(Ann), {account_pubkey, K}};
expr_to_fcode(_Env, _Type, {signature, Ann, K}) -> {lit, to_fann(Ann), {signature, K}};
expr_to_fcode(_Env, _Type, {contract_pubkey, Ann, K}) -> {lit, to_fann(Ann), {contract_pubkey, K}};
expr_to_fcode(_Env, _Type, {oracle_pubkey, Ann, K}) -> {lit, to_fann(Ann), {oracle_pubkey, K}};
expr_to_fcode(_Env, _Type, {oracle_query_id, Ann, K}) -> {lit, to_fann(Ann), {oracle_query_id, K}};

View File

@ -228,10 +228,13 @@ encode_value(Contract0, Type, Value, Options) ->
decode_value(Contract0, Type, FateValue, Options) ->
case add_extra_call(Contract0, {type, Type}, Options) of
{ok, CallName, Code} ->
#{ unfolded_typed_ast := TypedAst
, type_env := TypeEnv} = Code,
#{ folded_typed_ast := TypedAst
, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(CallName, TypedAst),
Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
[ unfold_record_types
, unfold_variant_types
, not_unfold_system_alias_types ]),
fate_data_to_sophia_value(Type0, Type1, FateValue);
Err = {error, _} ->
Err
@ -272,7 +275,7 @@ insert_call_function(Ast, Code, Call, {type, Type}) ->
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"entrypoint ", Call, "(val : ", Type, ") = val\n"
"entrypoint ", Call, "(val : ", Type, ") : ", Type, " = val\n"
]).
-spec insert_init_function(string(), options()) -> string().
@ -311,9 +314,12 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
try
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
#{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
[ unfold_record_types
, unfold_variant_types
, not_unfold_system_alias_types]),
fate_data_to_sophia_value(Type0, Type, Data)
catch
@ -360,14 +366,17 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
#{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst),
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(GetType, Args),
Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
[ unfold_record_types
, unfold_variant_types
, not_unfold_system_alias_types]),
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try

View File

@ -235,6 +235,7 @@ lit_to_fate(Env, L) ->
{bytes, B} -> aeb_fate_data:make_bytes(B);
{bool, B} -> aeb_fate_data:make_boolean(B);
{account_pubkey, K} -> aeb_fate_data:make_address(K);
{signature, S} -> aeb_fate_data:make_bytes(S);
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);

View File

@ -525,7 +525,7 @@ id_or_addr() ->
?RULE(id(), parse_addr_literal(_1)).
parse_addr_literal(Id = {id, Ann, Name}) ->
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_", "sg_"]) of
false -> Id;
true ->
try aeser_api_encoder:decode(list_to_binary(Name)) of

View File

@ -387,7 +387,8 @@ expr_p(_, {Type, _, Bin})
when Type == account_pubkey;
Type == contract_pubkey;
Type == oracle_pubkey;
Type == oracle_query_id ->
Type == oracle_query_id;
Type == signature ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\"");
expr_p(_, {string, _, S}) ->

View File

@ -100,6 +100,7 @@
| {contract_pubkey, ann(), binary()}
| {oracle_pubkey, ann(), binary()}
| {oracle_query_id, ann(), binary()}
| {signature, ann(), binary()}
| {string, ann(), binary()}
| {char, ann(), integer()}.

View File

@ -12,6 +12,9 @@
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({id, _, "signature"}, ?FATE_BYTES(Bin)) -> {signature, [], Bin};
from_fate({id, _, "hash"}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin};
from_fate({id, _, "unit"}, ?FATE_UNIT) -> {tuple, [], []};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};

View File

@ -117,6 +117,7 @@ compilable_contracts() ->
{"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
{"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
{"funargs", "chain_base_tx", ["Chain.GAAttachTx"]},
{"funargs", "sig", ["sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},

View File

@ -0,0 +1,40 @@
-module(aeso_encode_decode_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
-define(EMPTY, "contract C =\n entrypoint f() = true").
encode_decode_test_() ->
[ {lists:flatten(io_lib:format("Testing encode-decode roundtrip for ~p : ~p", [Value, {EType, DType}])),
fun() ->
{ok, SerRes} = aeso_compiler:encode_value(?EMPTY, EType, Value, []),
{ok, Expr} = aeso_compiler:decode_value(?EMPTY, DType, SerRes, []),
Value2 = prettypr:format(aeso_pretty:expr(Expr)),
?assertEqual(Value, Value2)
end} || {Value, EType, DType} <- test_data() ].
test_data() ->
lists:map(fun({V, T}) -> {V, T, T};
({V, T1, T2}) -> {V, T1, T2} end, data()).
data() ->
[ {"42", "int"}
, {"- 42", "int"}
, {"true", "bool"}
, {"ak_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "address"}
, {"ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ", "C"}
, {"Some(42)", "option(int)"}
, {"None", "option(int)"}
, {"(true, 42)", "bool * int"}
, {"{[1] = true, [3] = false}", "map(int, bool)"}
, {"()", "unit"}
, {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "hash"}
, {"#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", "bytes(32)"}
, {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "signature"}
, {"sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj", "bytes(64)", "signature"}
, {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "bytes(64)"}
, {"#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637", "signature", "bytes(64)"}
].

View File

@ -11,4 +11,5 @@ contract C =
let i = Int.mulmod(a, b, h)
let j = Crypto.poseidon(i, a)
let k : bytes(32) = Address.to_bytes(Call.origin)
(a bor b band c bxor a << bnot b >> a, k)
let l = sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj
(a bor b band c bxor a << bnot b >> a, k, l)

View File

@ -59,3 +59,5 @@ contract FunctionArguments =
entrypoint chain_ga_meta_tx(tx : Chain.ga_meta_tx) = true
entrypoint chain_paying_for_tx(tx : Chain.paying_for_tx) = true
entrypoint chain_base_tx(tx : Chain.base_tx) = true
entrypoint sig(sg : signature) = true