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:
parent
31301911a2
commit
51bae61736
@ -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`.
|
||||
|
@ -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` |
|
||||
|
@ -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
|
||||
|
@ -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});
|
||||
|
@ -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
|
||||
|
@ -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}};
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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}) ->
|
||||
|
@ -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()}.
|
||||
|
||||
|
@ -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};
|
||||
|
@ -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", []},
|
||||
|
40
test/aeso_encode_decode_tests.erl
Normal file
40
test/aeso_encode_decode_tests.erl
Normal 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)"}
|
||||
].
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user