diff --git a/rebar.config b/rebar.config index eb173a0..7b515ec 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"8a9c9de"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"ff5a4c7"}}} , {getopt, "1.0.1"} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", diff --git a/rebar.lock b/rebar.lock index 9b84603..cf6a6be 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"8a9c9dec956b1d2322b38490b2759e53a91affab"}}, + {ref,"ff5a4c7dd54ec22d30f16152341fb9ffcd6cc135"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 802614c..c050226 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -397,6 +397,15 @@ global_env() -> G1 = {tuple_t, Ann, [Fp, Fp, Fp]}, G2 = {tuple_t, Ann, [Fp2, Fp2, Fp2]}, GT = {tuple_t, Ann, lists:duplicate(12, Fp)}, + Tx = {qid, Ann, ["Chain", "tx"]}, + GAMetaTx = {qid, Ann, ["Chain", "ga_meta_tx"]}, + BaseTx = {qid, Ann, ["Chain", "base_tx"]}, + PayForTx = {qid, Ann, ["Chain", "paying_for_tx"]}, + + FldT = fun(Id, T) -> {field_t, Ann, {id, Ann, Id}, T} end, + TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)}, + {"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}], + TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]}, Fee = Int, [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), @@ -435,8 +444,36 @@ global_env() -> {"timestamp", Int}, {"block_height", Int}, {"difficulty", Int}, - {"gas_limit", Int}]) - , types = MkDefs([{"ttl", 0}]) }, + {"gas_limit", Int}, + %% Tx constructors + {"GAMetaTx", Fun([Address, Int], GAMetaTx)}, + {"PayingForTx", Fun([Address, Int], PayForTx)}, + {"SpendTx", Fun([Address, Int, String], BaseTx)}, + {"OracleRegisterTx", BaseTx}, + {"OracleQueryTx", BaseTx}, + {"OracleResponseTx", BaseTx}, + {"OracleExtendTx", BaseTx}, + {"NamePreclaimTx", BaseTx}, + {"NameClaimTx", Fun([String], BaseTx)}, + {"NameUpdateTx", Fun([Hash], BaseTx)}, + {"NameRevokeTx", Fun([Hash], BaseTx)}, + {"NameTransferTx", Fun([Address, Hash], BaseTx)}, + {"ChannelCreateTx", Fun([Address], BaseTx)}, + {"ChannelDepositTx", Fun([Address, Int], BaseTx)}, + {"ChannelWithdrawTx", Fun([Address, Int], BaseTx)}, + {"ChannelForceProgressTx", Fun([Address], BaseTx)}, + {"ChannelCloseMutualTx", Fun([Address], BaseTx)}, + {"ChannelCloseSoloTx", Fun([Address], BaseTx)}, + {"ChannelSlashTx", Fun([Address], BaseTx)}, + {"ChannelSettleTx", Fun([Address], BaseTx)}, + {"ChannelSnapshotSoloTx", Fun([Address], BaseTx)}, + {"ContractCreateTx", Fun([Int], BaseTx)}, + {"ContractCallTx", Fun([Address, Int], BaseTx)}, + {"GAAttachTx", BaseTx} + ]) + , types = MkDefs([{"ttl", 0}, {"tx", {[], TxType}}, + {"base_tx", 0}, + {"paying_for_tx", 0}, {"ga_meta_tx", 0}]) }, ContractScope = #scope { funs = MkDefs( @@ -543,7 +580,8 @@ global_env() -> %% Authentication AuthScope = #scope { funs = MkDefs( - [{"tx_hash", Option(Hash)}]) }, + [{"tx_hash", Option(Hash)}, + {"tx", Option(Tx)} ]) }, %% Strings StringScope = #scope @@ -584,6 +622,7 @@ global_env() -> {"is_contract", Fun1(Address, Bool)}, {"is_payable", Fun1(Address, Bool)}]) }, + #env{ scopes = #{ [] => TopScope , ["Chain"] => ChainScope @@ -601,6 +640,9 @@ global_env() -> , ["Int"] => IntScope , ["Address"] => AddressScope } + , fields = + maps:from_list([{N, [#field_info{ ann = [], field_t = T, record_t = Tx, kind = record }]} + || {N, T} <- TxFlds ]) }. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index d4c1f0d..0abfb49 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -178,6 +178,7 @@ ast_to_fcode(Code, Options) -> -spec init_env([option()]) -> env(). init_env(Options) -> + ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0], #{ type_env => init_type_env(), fun_env => #{}, builtins => builtins(), @@ -189,7 +190,31 @@ init_env(Options) -> ["AENS", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1] }, ["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] }, ["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] }, - ["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] } + ["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] }, + ["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] }, + ["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] }, + ["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities }, + ["Chain", "OracleRegisterTx"] => #con_tag{ tag = 1, arities = ChainTxArities }, + ["Chain", "OracleQueryTx"] => #con_tag{ tag = 2, arities = ChainTxArities }, + ["Chain", "OracleResponseTx"] => #con_tag{ tag = 3, arities = ChainTxArities }, + ["Chain", "OracleExtendTx"] => #con_tag{ tag = 4, arities = ChainTxArities }, + ["Chain", "NamePreclaimTx"] => #con_tag{ tag = 5, arities = ChainTxArities }, + ["Chain", "NameClaimTx"] => #con_tag{ tag = 6, arities = ChainTxArities }, + ["Chain", "NameUpdateTx"] => #con_tag{ tag = 7, arities = ChainTxArities }, + ["Chain", "NameRevokeTx"] => #con_tag{ tag = 8, arities = ChainTxArities }, + ["Chain", "NameTransferTx"] => #con_tag{ tag = 9, arities = ChainTxArities }, + ["Chain", "ChannelCreateTx"] => #con_tag{ tag = 10, arities = ChainTxArities }, + ["Chain", "ChannelDepositTx"] => #con_tag{ tag = 11, arities = ChainTxArities }, + ["Chain", "ChannelWithdrawTx"] => #con_tag{ tag = 12, arities = ChainTxArities }, + ["Chain", "ChannelForceProgressTx"] => #con_tag{ tag = 13, arities = ChainTxArities }, + ["Chain", "ChannelCloseMutualTx"] => #con_tag{ tag = 14, arities = ChainTxArities }, + ["Chain", "ChannelCloseSoloTx"] => #con_tag{ tag = 15, arities = ChainTxArities }, + ["Chain", "ChannelSlashTx"] => #con_tag{ tag = 16, arities = ChainTxArities }, + ["Chain", "ChannelSettleTx"] => #con_tag{ tag = 17, arities = ChainTxArities }, + ["Chain", "ChannelSnapshotSoloTx"] => #con_tag{ tag = 18, arities = ChainTxArities }, + ["Chain", "ContractCreateTx"] => #con_tag{ tag = 19, arities = ChainTxArities }, + ["Chain", "ContractCallTx"] => #con_tag{ tag = 20, arities = ChainTxArities }, + ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities } }, options => Options, functions => #{} }. @@ -221,7 +246,7 @@ builtins() -> {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, - {["Auth"], [{"tx_hash", none}]}, + {["Auth"], [{"tx_hash", none}, {"tx", none}]}, {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, @@ -241,6 +266,11 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}). -spec init_type_env() -> type_env(). init_type_env() -> + BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string], + [hash], [hash], [address, hash], [address], + [address, integer], [address, integer], [address], + [address], [address], [address], [address], [address], + [integer], [address, integer], []]}, #{ ["int"] => ?type(integer), ["bool"] => ?type(boolean), ["bits"] => ?type(bits), @@ -257,6 +287,9 @@ init_type_env() -> ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}), ["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}), ["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}), + ["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}), + ["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}), + ["Chain", "base_tx"] => ?type(BaseTx), ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) }. diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 70cac15..a5a5139 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -544,7 +544,9 @@ builtin_to_scode(Env, aens_update, [_Sign, _Account, _NameString, _TTL, _ClientT builtin_to_scode(Env, aens_lookup, [_Name] = Args) -> call_to_scode(Env, aeb_fate_ops:aens_lookup(?a, ?a), Args); builtin_to_scode(_Env, auth_tx_hash, []) -> - [aeb_fate_ops:auth_tx_hash(?a)]. + [aeb_fate_ops:auth_tx_hash(?a)]; +builtin_to_scode(_Env, auth_tx, []) -> + [aeb_fate_ops:auth_tx(?a)]. %% -- Operators -- @@ -846,6 +848,7 @@ attributes(I) -> {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); + {'AUTH_TX', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 2e610b0..ee5991a 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -146,6 +146,7 @@ compilable_contracts() -> "events", "include", "basic_auth", + "basic_auth_tx", "bitcoin_auth", "address_literals", "bytes_equality", @@ -171,7 +172,7 @@ compilable_contracts() -> ]. not_yet_compilable(fate) -> []; -not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update"]. +not_yet_compilable(aevm) -> ["pairing_crypto", "aens_update", "basic_auth_tx"]. %% Contracts that should produce type errors diff --git a/test/contracts/basic_auth_tx.aes b/test/contracts/basic_auth_tx.aes new file mode 100644 index 0000000..b57c498 --- /dev/null +++ b/test/contracts/basic_auth_tx.aes @@ -0,0 +1,74 @@ +// namespace Chain = +// record tx = { paying_for : option(Chain.paying_for_tx) +// , ga_metas : list(Chain.ga_meta_tx) +// , actor : address +// , fee : int +// , ttl : int +// , tx : Chain.base_tx } + +// datatype ga_meta_tx = GAMetaTx(address, int) +// datatype paying_for_tx = PayingForTx(address, int) +// datatype base_tx = SpendTx(address, int, string) +// | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx +// | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string) +// | NameRevokeTx(hash) | NameTransferTx(address, string) +// | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) | +// | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address) +// | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address) +// | ContractCreateTx(int) | ContractCallTx(address, int) +// | GAAttachTx + + +// Contract replicating "normal" Aeternity authentication +contract BasicAuthTx = + record state = { nonce : int, owner : address } + datatype foo = Bar | Baz() + + entrypoint init() = { nonce = 1, owner = Call.caller } + + stateful entrypoint authorize(n : int, s : signature) : bool = + require(n >= state.nonce, "Nonce too low") + require(n =< state.nonce, "Nonce too high") + put(state{ nonce = n + 1 }) + switch(Auth.tx_hash) + None => abort("Not in Auth context") + Some(tx_hash) => + let Some(tx0) = Auth.tx + let x : option(Chain.paying_for_tx) = tx0.paying_for + let x : list(Chain.ga_meta_tx) = tx0.ga_metas + let x : int = tx0.fee + tx0.ttl + let x : address = tx0.actor + let x : Chain.tx = { tx = Chain.NamePreclaimTx, paying_for = None, ga_metas = [], + fee = 123, ttl = 0, actor = Call.caller } + switch(tx0.tx) + Chain.SpendTx(receiver, amount, payload) => verify(tx_hash, n, s) + Chain.OracleRegisterTx => false + Chain.OracleQueryTx => false + Chain.OracleResponseTx => false + Chain.OracleExtendTx => false + Chain.NamePreclaimTx => false + Chain.NameClaimTx(name) => false + Chain.NameUpdateTx(name) => false + Chain.NameRevokeTx(name) => false + Chain.NameTransferTx(to, name) => false + Chain.ChannelCreateTx(other_party) => false + Chain.ChannelDepositTx(channel, amount) => false + Chain.ChannelWithdrawTx(channel, amount) => false + Chain.ChannelForceProgressTx(channel) => false + Chain.ChannelCloseMutualTx(channel) => false + Chain.ChannelCloseSoloTx(channel) => false + Chain.ChannelSlashTx(channel) => false + Chain.ChannelSettleTx(channel) => false + Chain.ChannelSnapshotSoloTx(channel) => false + Chain.ContractCreateTx(amount) => false + Chain.ContractCallTx(ct_address, amount) => false + Chain.GAAttachTx => false + + function verify(tx_hash, n, s) = + Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s) + + entrypoint to_sign(h : hash, n : int) = + Crypto.blake2b((h, n)) + + entrypoint weird_string() : string = + "\x19Weird String\x42\nMore\n"