From ed72e393ab2b76a26ab6ff4821e9085b3d706bd9 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Thu, 29 Jan 2026 14:14:33 +0100 Subject: [PATCH 1/4] 1st commit: add MLDSA sig verification --- rebar.config | 2 + rebar.lock | 22 ++++---- src/so_ast_infer_types.erl | 3 ++ src/so_ast_to_fcode.erl | 4 ++ test/contracts/qr_auth.aes | 21 ++++++++ test/contracts/qr_auth_tx.aes | 76 +++++++++++++++++++++++++++ test/contracts/unapplied_builtins.aes | 3 ++ 7 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 test/contracts/qr_auth.aes create mode 100644 test/contracts/qr_auth_tx.aes diff --git a/rebar.config b/rebar.config index 58f1a97..1d6ac99 100644 --- a/rebar.config +++ b/rebar.config @@ -1,5 +1,7 @@ %% -*- mode: erlang; indent-tabs-mode: nil -*- +{minimum_otp_vsn, "28.1"}. + {erl_opts, [debug_info]}. {deps, [ {gmbytecode, diff --git a/rebar.lock b/rebar.lock index 2bd61be..dd741ca 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,13 +1,4 @@ -{"1.2.0", -[{<<"gmbytecode">>, - {git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git", - {ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}}, - 0}, - {<<"gmserialization">>, - {git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git", - {ref,"ac64e01b0f675c1a34c70a827062f381920742db"}}, - 1}, - {<<"base58">>, +[{<<"base58">>, {git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git", {ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}}, 2}, @@ -23,8 +14,15 @@ {git,"https://git.qpq.swiss/QPQ-AG/getopt.git", {ref,"dbab6262a2430809430deda9d8650f58f9d80898"}}, 1}, + {<<"gmbytecode">>, + {git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git", + {ref,"97cea33be8f3a35d26055664da7aa59531ff5537"}}, + 0}, + {<<"gmserialization">>, + {git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git", + {ref,"ac64e01b0f675c1a34c70a827062f381920742db"}}, + 1}, {<<"jsx">>, {git,"https://github.com/talentdeficit/jsx.git", {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, - 0}]}. - + 0}]. diff --git a/src/so_ast_infer_types.erl b/src/so_ast_infer_types.erl index d8e982b..1c7ee1d 100644 --- a/src/so_ast_infer_types.erl +++ b/src/so_ast_infer_types.erl @@ -781,6 +781,9 @@ global_env() -> {"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)}, {"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)}, {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, + {"verify_sig_mldsa44", Fun([Hash, Bytes(any), SignId], Bool)}, + {"verify_sig_mldsa65", Fun([Hash, Bytes(any), SignId], Bool)}, + {"verify_sig_mldsa87", Fun([Hash, Bytes(any), SignId], Bool)}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, {"blake2b", Fun1(A, Hash)}, diff --git a/src/so_ast_to_fcode.erl b/src/so_ast_to_fcode.erl index a90fb35..62b0de4 100644 --- a/src/so_ast_to_fcode.erl +++ b/src/so_ast_to_fcode.erl @@ -40,6 +40,7 @@ contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 | crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_poseidon | crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 | + crypto_verify_sig_mldsa44 | crypto_verify_sig_mldsa65 | crypto_verify_sig_mldsa87 | mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid | mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul | mcl_bls12_381_g2_neg | mcl_bls12_381_g2_norm | mcl_bls12_381_g2_valid | @@ -285,6 +286,8 @@ builtins() -> {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2}, + {"verify_sig_mldsa44", 3}, {"verify_sig_mldsa65", 3}, + {"verify_sig_mldsa87", 3}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"poseidon", 2}]}, {["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2}, {"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2}, @@ -1163,6 +1166,7 @@ op_builtins() -> int_to_str, int_to_bytes, int_mulmod, address_to_str, address_to_bytes, address_to_contract, crypto_verify_sig, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_verify_sig_mldsa44, crypto_verify_sig_mldsa65, crypto_verify_sig_mldsa87, crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, diff --git a/test/contracts/qr_auth.aes b/test/contracts/qr_auth.aes new file mode 100644 index 0000000..10e8406 --- /dev/null +++ b/test/contracts/qr_auth.aes @@ -0,0 +1,21 @@ +// Contract using Quantum-Resistant signing (MLDSA65) +contract QrAuth = + record state = { nonce : int, owner : address, owner_pub : bytes } + + entrypoint init(pub : bytes) = { nonce = 1 + , owner = Call.caller + , owner_pub = pub } + + 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) => Crypto.verify_sig_mldsa65(to_sign(tx_hash, n), state.owner_pub, s) + + entrypoint to_sign(h : hash, n : int) = + Crypto.blake2b((h, n)) + + entrypoint weird_string() : string = + "\x19Weird String\x42\nMore\n" diff --git a/test/contracts/qr_auth_tx.aes b/test/contracts/qr_auth_tx.aes new file mode 100644 index 0000000..1c1a360 --- /dev/null +++ b/test/contracts/qr_auth_tx.aes @@ -0,0 +1,76 @@ +// 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 implementing Quantum-resistant (MLDSA65) authentication +contract QrAuthTx = + record state = { nonce : int, owner : address, owner_pub : bytes } + datatype foo = Bar | Baz() + + entrypoint init(pub : bytes) = { nonce = 1 + , owner = Call.caller + , owner_pub = pub } + + 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_mldsa65(to_sign(tx_hash, n), state.owner_pub, s) + + entrypoint to_sign(h : hash, n : int) = + Crypto.blake2b((h, n)) + + entrypoint weird_string() : string = + "\x19Weird String\x42\nMore\n" diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index de53a9f..5cd5d25 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -29,6 +29,9 @@ contract UnappliedBuiltins = function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1 function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1 function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1 + function crypto_verify_sig_mldsa44() = Crypto.verify_sig_mldsa44 + function crypto_verify_sig_mldsa65() = Crypto.verify_sig_mldsa65 + function crypto_verify_sig_mldsa87() = Crypto.verify_sig_mldsa87 function crypto_sha3() = Crypto.sha3 : t => _ function crypto_sha256() = Crypto.sha256 : t => _ function crypto_blake2b() = Crypto.blake2b : t => _ -- 2.30.2 From 4f6529ed9d5dcaf9fda96ae45fb7242c60883800 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 2 Feb 2026 20:05:07 +0100 Subject: [PATCH 2/4] update deps, add mldsa-related byte codes --- rebar.config | 3 +-- rebar.lock | 25 ++++++++++++++----------- src/so_ast_infer_types.erl | 6 +++--- src/so_fcode_to_fate.erl | 6 ++++++ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/rebar.config b/rebar.config index 1d6ac99..88873b9 100644 --- a/rebar.config +++ b/rebar.config @@ -5,8 +5,7 @@ {erl_opts, [debug_info]}. {deps, [ {gmbytecode, - {git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git", - {ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}}} + {git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git", {ref, "54dc140629"}}} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} ]}. diff --git a/rebar.lock b/rebar.lock index dd741ca..8e1c93d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,28 +1,31 @@ +{"1.2.0", [{<<"base58">>, {git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git", {ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}}, 2}, - {<<"eblake2">>, - {git,"https://git.qpq.swiss/QPQ-AG/eblake2.git", - {ref,"b29d585b8760746142014884007eb8441a3b6a14"}}, - 0}, + {<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, {<<"enacl">>, {git,"https://git.qpq.swiss/QPQ-AG/enacl.git", {ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}}, 2}, - {<<"getopt">>, - {git,"https://git.qpq.swiss/QPQ-AG/getopt.git", - {ref,"dbab6262a2430809430deda9d8650f58f9d80898"}}, - 1}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1}, {<<"gmbytecode">>, {git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git", - {ref,"97cea33be8f3a35d26055664da7aa59531ff5537"}}, + {ref,"54dc140629b3af5de2043f52ba9dc36aff6b949d"}}, 0}, {<<"gmserialization">>, {git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git", - {ref,"ac64e01b0f675c1a34c70a827062f381920742db"}}, + {ref,"9d2ecc00d32ea295309563e54a81636ecb597e96"}}, 1}, {<<"jsx">>, {git,"https://github.com/talentdeficit/jsx.git", {ref,"3074d4865b3385a050badf7828ad31490d860df5"}}, - 0}]. + 0}]}. +[ +{pkg_hash,[ + {<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>}, + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}, +{pkg_hash_ext,[ + {<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>}, + {<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]} +]. diff --git a/src/so_ast_infer_types.erl b/src/so_ast_infer_types.erl index 1c7ee1d..cc3bb20 100644 --- a/src/so_ast_infer_types.erl +++ b/src/so_ast_infer_types.erl @@ -781,9 +781,9 @@ global_env() -> {"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)}, {"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)}, {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, - {"verify_sig_mldsa44", Fun([Hash, Bytes(any), SignId], Bool)}, - {"verify_sig_mldsa65", Fun([Hash, Bytes(any), SignId], Bool)}, - {"verify_sig_mldsa87", Fun([Hash, Bytes(any), SignId], Bool)}, + {"verify_sig_mldsa44", Fun([Hash, Bytes(1312), Bytes(2420)], Bool)}, + {"verify_sig_mldsa65", Fun([Hash, Bytes(1952), Bytes(3309)], Bool)}, + {"verify_sig_mldsa87", Fun([Hash, Bytes(2592), Bytes(4627)], Bool)}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, {"blake2b", Fun1(A, Hash)}, diff --git a/src/so_fcode_to_fate.erl b/src/so_fcode_to_fate.erl index 43dfe10..c986577 100644 --- a/src/so_fcode_to_fate.erl +++ b/src/so_fcode_to_fate.erl @@ -693,6 +693,9 @@ op_to_scode(crypto_verify_sig) -> gmb_fate_ops:verify_sig(?a, ?a, ?a, op_to_scode(crypto_verify_sig_secp256k1) -> gmb_fate_ops:verify_sig_secp256k1(?a, ?a, ?a, ?a); op_to_scode(crypto_ecverify_secp256k1) -> gmb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a); op_to_scode(crypto_ecrecover_secp256k1) -> gmb_fate_ops:ecrecover_secp256k1(?a, ?a, ?a); +op_to_scode(crypto_verify_sig_mldsa44) -> gmb_fate_ops:verify_sig_mldsa44(?a, ?a, ?a, ?a); +op_to_scode(crypto_verify_sig_mldsa65) -> gmb_fate_ops:verify_sig_mldsa65(?a, ?a, ?a, ?a); +op_to_scode(crypto_verify_sig_mldsa87) -> gmb_fate_ops:verify_sig_mldsa87(?a, ?a, ?a, ?a); op_to_scode(crypto_sha3) -> gmb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> gmb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> gmb_fate_ops:blake2b(?a, ?a); @@ -1033,6 +1036,9 @@ attributes(I) -> {'POSEIDON', A, B, C} -> Pure(A, [B, C]); {'VERIFY_SIG', A, B, C, D} -> Pure(A, [B, C, D]); {'VERIFY_SIG_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); + {'VERIFY_SIG_MLDSA44', A, B, C, D} -> Pure(A, [B, C, D]); + {'VERIFY_SIG_MLDSA65', A, B, C, D} -> Pure(A, [B, C, D]); + {'VERIFY_SIG_MLDSA87', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); -- 2.30.2 From 8a635c2e7367e55eba1f624e861fa4f51db1707b Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Sun, 23 Aug 2026 00:19:06 +0200 Subject: [PATCH 3/4] Add Sophia AIC builtins and Auth.tx constructors Expose Chain.spend_currency, Chain.balance_currency, and Chain.currency_supply, compiling to the new FATE opcodes. Auth.tx inspection gains AccountCreateTx, MintTx, ExchangeTx, SpendCurrenciesTx, and BurnTx. Pin gmbytecode to c44313e. --- docs/sophia_stdlib.md | 29 +++++++++++++++++++++++++++++ rebar.config | 2 +- rebar.lock | 2 +- src/so_ast_infer_types.erl | 17 ++++++++++++++--- src/so_ast_to_fcode.erl | 21 +++++++++++++++++---- src/so_fcode_to_fate.erl | 10 ++++++++++ src/so_vm_decode.erl | 16 +++++++++++++++- test/contracts/basic_auth_tx.aes | 5 +++++ test/contracts/qr_auth_tx.aes | 5 +++++ test/so_calldata_tests.erl | 3 +++ 10 files changed, 100 insertions(+), 10 deletions(-) diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 4defd39..74e3a15 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -749,6 +749,35 @@ Spend `amount` tokens to `to`. Will fail (and abort the contract) if contract doesn't have `amount` tokens to transfer, or, if `to` is not `payable`. +##### spend\_currency +``` +Chain.spend_currency(to : address, issuer : address, amount : int) : unit +``` + +Transfer `amount` of the account-issued currency identified by `issuer` from +the contract to `to`. Aborts if the contract does not hold enough, or if the +recipient requires inbound AIC authorization and is not a signer of the outer +transaction. + + +##### balance\_currency +``` +Chain.balance_currency(holder : address, issuer : address) : int +``` + +The `holder`'s balance of the account-issued currency identified by `issuer` +(0 if none). + + +##### currency\_supply +``` +Chain.currency_supply(issuer : address) : int +``` + +Total supply of the account-issued currency identified by `issuer` (0 if the +account has not minted). + + ##### timestamp ``` Chain.timestamp : int diff --git a/rebar.config b/rebar.config index 88873b9..8d002c9 100644 --- a/rebar.config +++ b/rebar.config @@ -5,7 +5,7 @@ {erl_opts, [debug_info]}. {deps, [ {gmbytecode, - {git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git", {ref, "54dc140629"}}} + {git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git", {ref, "c44313efb9"}}} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} ]}. diff --git a/rebar.lock b/rebar.lock index 8e1c93d..428c5c5 100644 --- a/rebar.lock +++ b/rebar.lock @@ -11,7 +11,7 @@ {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1}, {<<"gmbytecode">>, {git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git", - {ref,"54dc140629b3af5de2043f52ba9dc36aff6b949d"}}, + {ref,"c44313efb9c8fecace217e799d90e3d91d7e54d1"}}, 0}, {<<"gmserialization">>, {git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git", diff --git a/src/so_ast_infer_types.erl b/src/so_ast_infer_types.erl index cc3bb20..8e83e3d 100644 --- a/src/so_ast_infer_types.erl +++ b/src/so_ast_infer_types.erl @@ -646,9 +646,12 @@ global_env() -> ChainScope = #scope { funs = MkDefs( %% Spend transaction. - [{"spend", StateFun([Address, Int], Unit)}, + [{"spend", StateFun([Address, Int], Unit)}, + {"spend_currency", StateFun([Address, Address, Int], Unit)}, %% Chain environment - {"balance", Fun1(Address, Int)}, + {"balance", Fun1(Address, Int)}, + {"balance_currency", Fun([Address, Address], Int)}, + {"currency_supply", Fun1(Address, Int)}, {"block_hash", Fun1(Int, Option(Hash))}, {"coinbase", Address}, {"timestamp", Int}, @@ -697,7 +700,15 @@ global_env() -> {"ChannelSnapshotSoloTx", Fun([Address], BaseTx)}, {"ContractCreateTx", Fun([Int], BaseTx)}, {"ContractCallTx", Fun([Address, Int], BaseTx)}, - {"GAAttachTx", BaseTx} + {"GAAttachTx", BaseTx}, + {"AccountCreateTx", Fun([Address, Int], BaseTx)}, + {"MintTx", Fun([Int], BaseTx)}, + {"ExchangeTx", Fun([List({tuple_t, Ann, + [Option(Address), Address, + Address, Int]})], BaseTx)}, + {"SpendCurrenciesTx", Fun([Address, Int, String, + List(Pair(Address, Int))], BaseTx)}, + {"BurnTx", Fun([Int], BaseTx)} ]) , types = MkDefs([{"ttl", 0}, {"tx", {[], TxType}}, {"base_tx", 0}, diff --git a/src/so_ast_to_fcode.erl b/src/so_ast_to_fcode.erl index 62b0de4..7840d94 100644 --- a/src/so_ast_to_fcode.erl +++ b/src/so_ast_to_fcode.erl @@ -212,7 +212,8 @@ optimize(FCode1, 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], + ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0, + 2, 1, 1, 4, 1], #{ type_env => init_type_env(), fun_env => #{}, builtins => builtins(), @@ -255,7 +256,12 @@ init_env(Options) -> ["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 } + ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }, + ["Chain", "AccountCreateTx"] => #con_tag{ tag = 22, arities = ChainTxArities }, + ["Chain", "MintTx"] => #con_tag{ tag = 23, arities = ChainTxArities }, + ["Chain", "ExchangeTx"] => #con_tag{ tag = 24, arities = ChainTxArities }, + ["Chain", "SpendCurrenciesTx"] => #con_tag{ tag = 25, arities = ChainTxArities }, + ["Chain", "BurnTx"] => #con_tag{ tag = 26, arities = ChainTxArities } }, options => Options, functions => #{}, @@ -271,7 +277,9 @@ builtins() -> list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_"))) end, Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]}, - {["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none}, + {["Chain"], [{"spend", 2}, {"spend_currency", 3}, {"balance", 1}, + {"balance_currency", 2}, {"currency_supply", 1}, + {"block_hash", 1}, {"coinbase", none}, {"timestamp", none}, {"block_height", none}, {"difficulty", none}, {"gas_limit", none}, {"network_id", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]}, {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, @@ -323,7 +331,12 @@ init_type_env() -> [{bytes, 32}], [{bytes, 32}], [address, {bytes, 32}], [address], [address, integer], [address, integer], [address], [address], [address], [address], [address], [address], - [integer], [address, integer], []]}, + [integer], [address, integer], [], + [address, integer], + [integer], + [{list, {tuple, [{variant, [[], [address]]}, address, address, integer]}}], + [address, integer, string, {list, {tuple, [address, integer]}}] + ]}, #{ ["int"] => ?type(integer), ["bool"] => ?type(boolean), ["bits"] => ?type(bits), diff --git a/src/so_fcode_to_fate.erl b/src/so_fcode_to_fate.erl index c986577..11c1bbf 100644 --- a/src/so_fcode_to_fate.erl +++ b/src/so_fcode_to_fate.erl @@ -553,8 +553,15 @@ builtin_to_scode(Env, exit, [_] = Args) -> builtin_to_scode(Env, chain_spend, [_, _] = Args) -> call_to_scode(Env, [gmb_fate_ops:spend(?a, ?a), tuple(0)], Args); +builtin_to_scode(Env, chain_spend_currency, [_, _, _] = Args) -> + call_to_scode(Env, [gmb_fate_ops:spend_currency(?a, ?a, ?a), + tuple(0)], Args); builtin_to_scode(Env, chain_balance, [_] = Args) -> call_to_scode(Env, gmb_fate_ops:balance_other(?a, ?a), Args); +builtin_to_scode(Env, chain_balance_currency, [_, _] = Args) -> + call_to_scode(Env, gmb_fate_ops:balance_currency(?a, ?a, ?a), Args); +builtin_to_scode(Env, chain_currency_supply, [_] = Args) -> + call_to_scode(Env, gmb_fate_ops:currency_supply(?a, ?a), Args); builtin_to_scode(Env, chain_block_hash, [_] = Args) -> call_to_scode(Env, gmb_fate_ops:blockhash(?a, ?a), Args); builtin_to_scode(_Env, chain_coinbase, []) -> @@ -1062,6 +1069,8 @@ attributes(I) -> {'ADDRESS', A} -> Pure(A, []); {'BALANCE', A} -> Pure(A, []); {'BALANCE_OTHER', A, B} -> Pure(A, [B]); + {'BALANCE_CURRENCY', A, B, C} -> Pure(A, [B, C]); + {'CURRENCY_SUPPLY', A, B} -> Pure(A, [B]); {'ORIGIN', A} -> Pure(A, []); {'CALLER', A} -> Pure(A, []); {'GASPRICE', A} -> Pure(A, []); @@ -1082,6 +1091,7 @@ attributes(I) -> {'LOG4', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]); 'DEACTIVATE' -> Impure(none, []); {'SPEND', A, B} -> Impure(none, [A, B]); + {'SPEND_CURRENCY', A, B, C} -> Impure(none, [A, B, C]); {'ORACLE_REGISTER', A, B, C, D, E, F, G} -> Impure(A, [B, C, D, E, F, G]); {'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]); {'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]); diff --git a/src/so_vm_decode.erl b/src/so_vm_decode.erl index f5ac61b..3bdc807 100644 --- a/src/so_vm_decode.erl +++ b/src/so_vm_decode.erl @@ -85,7 +85,8 @@ from_fate_builtin(QType, Val) -> Bts = {bytes_t, [], any}, Qid = fun(Name) -> {qid, [], Name} end, Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end, - ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0], + ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0, + 2, 1, 1, 4, 1], case {QType, Val} of {["Chain", "ttl"], {variant, [1, 1], 0, {X}}} -> App("RelativeTTL", [Chk(Int, X)]); @@ -169,6 +170,19 @@ from_fate_builtin(QType, Val) -> App(["Chain","ContractCallTx"], [Chk(Adr, Addr), Chk(Int, Amount)]); {["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} -> App(["Chain","GAAttachTx"], []); + {["Chain", "base_tx"], {variant, ChainTxArities, 22, {Addr, Amount}}} -> + App(["Chain","AccountCreateTx"], [Chk(Adr, Addr), Chk(Int, Amount)]); + {["Chain", "base_tx"], {variant, ChainTxArities, 23, {Amount}}} -> + App(["Chain","MintTx"], [Chk(Int, Amount)]); + {["Chain", "base_tx"], {variant, ChainTxArities, 24, {Legs}}} -> + LegT = {tuple_t, [], [{app_t, [], {id, [], "option"}, [Adr]}, Adr, Adr, Int]}, + App(["Chain","ExchangeTx"], [Chk({app_t, [], {id, [], "list"}, [LegT]}, Legs)]); + {["Chain", "base_tx"], {variant, ChainTxArities, 25, {Addr, Amount, Payload, Cs}}} -> + CsT = {app_t, [], {id, [], "list"}, [{tuple_t, [], [Adr, Int]}]}, + App(["Chain","SpendCurrenciesTx"], + [Chk(Adr, Addr), Chk(Int, Amount), Chk(Str, Payload), Chk(CsT, Cs)]); + {["Chain", "base_tx"], {variant, ChainTxArities, 26, {Amount}}} -> + App(["Chain","BurnTx"], [Chk(Int, Amount)]); {["MCL_BLS12_381", "fp"], X} -> App(["MCL_BLS12_381", "fp"], [Chk(I32, X)]); diff --git a/test/contracts/basic_auth_tx.aes b/test/contracts/basic_auth_tx.aes index b57c498..a717784 100644 --- a/test/contracts/basic_auth_tx.aes +++ b/test/contracts/basic_auth_tx.aes @@ -63,6 +63,11 @@ contract BasicAuthTx = Chain.ContractCreateTx(amount) => false Chain.ContractCallTx(ct_address, amount) => false Chain.GAAttachTx => false + Chain.AccountCreateTx(_, _) => false + Chain.MintTx(_) => false + Chain.ExchangeTx(_) => false + Chain.SpendCurrenciesTx(_, _, _, _) => false + Chain.BurnTx(_) => false function verify(tx_hash, n, s) = Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s) diff --git a/test/contracts/qr_auth_tx.aes b/test/contracts/qr_auth_tx.aes index 1c1a360..272efb1 100644 --- a/test/contracts/qr_auth_tx.aes +++ b/test/contracts/qr_auth_tx.aes @@ -65,6 +65,11 @@ contract QrAuthTx = Chain.ContractCreateTx(amount) => false Chain.ContractCallTx(ct_address, amount) => false Chain.GAAttachTx => false + Chain.AccountCreateTx(_, _) => false + Chain.MintTx(_) => false + Chain.ExchangeTx(_) => false + Chain.SpendCurrenciesTx(_, _, _, _) => false + Chain.BurnTx(_) => false function verify(tx_hash, n, s) = Crypto.verify_sig_mldsa65(to_sign(tx_hash, n), state.owner_pub, s) diff --git a/test/so_calldata_tests.erl b/test/so_calldata_tests.erl index 0f06807..6e74eba 100644 --- a/test/so_calldata_tests.erl +++ b/test/so_calldata_tests.erl @@ -115,6 +115,9 @@ 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", "chain_base_tx", ["Chain.MintTx(100)"]}, + {"funargs", "chain_base_tx", ["Chain.BurnTx(50)"]}, + {"funargs", "chain_base_tx", ["Chain.AccountCreateTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 50)"]}, {"funargs", "sig", ["sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj"]}, {"variant_types", "init", []}, {"basic_auth", "init", []}, -- 2.30.2 From 80c369b4d083058fd250392daa6857cd8cc274f5 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Sun, 23 Aug 2026 07:26:10 +0200 Subject: [PATCH 4/4] Add missing BurnTx type to Auth.tx fcode table init_type_env/0 listed five new Chain.base_tx constructors in con_env and arities but omitted BurnTx from the variant type, so compiling switches on Chain.BurnTx crashed in split_vars/2. --- src/so_ast_to_fcode.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/so_ast_to_fcode.erl b/src/so_ast_to_fcode.erl index 7840d94..35c6607 100644 --- a/src/so_ast_to_fcode.erl +++ b/src/so_ast_to_fcode.erl @@ -335,7 +335,8 @@ init_type_env() -> [address, integer], [integer], [{list, {tuple, [{variant, [[], [address]]}, address, address, integer]}}], - [address, integer, string, {list, {tuple, [address, integer]}}] + [address, integer, string, {list, {tuple, [address, integer]}}], + [integer] ]}, #{ ["int"] => ?type(integer), ["bool"] => ?type(boolean), -- 2.30.2