Add Sophia AIC builtins and Auth.tx constructors
Sophia Tests / tests (push) Failing after 48m33s

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.
This commit is contained in:
Ulf Wiger
2026-08-23 00:19:06 +02:00
parent 4f6529ed9d
commit 8a635c2e73
10 changed files with 100 additions and 10 deletions
+29
View File
@@ -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
+1 -1
View File
@@ -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"}}}
]}.
+1 -1
View File
@@ -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",
+14 -3
View File
@@ -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},
+17 -4
View File
@@ -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),
+10
View File
@@ -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]);
+15 -1
View File
@@ -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)]);
+5
View File
@@ -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)
+5
View File
@@ -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)
+3
View File
@@ -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", []},