diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_char.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_char.aes new file mode 100644 index 0000000..8a226b0 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_char.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_ABC'x' + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_digit_0.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_digit_0.aes new file mode 100644 index 0000000..98a70f4 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_digit_0.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_ABC0DEF + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_five_split.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_five_split.aes new file mode 100644 index 0000000..76087c3 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_five_split.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_AB0I'x'bar + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_hex.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_hex.aes new file mode 100644 index 0000000..16ca2c6 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_hex.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_AB0xDEF + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_kwd_let.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_kwd_let.aes new file mode 100644 index 0000000..e22d3dd --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_kwd_let.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_ABClet + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_lower_l.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_lower_l.aes new file mode 100644 index 0000000..dc5c1f2 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_lower_l.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_foolbar + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_tvar.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_tvar.aes new file mode 100644 index 0000000..2301efa --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ak_base58_tvar.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_ABC'foo + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ct_base58_upper_O.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ct_base58_upper_O.aes new file mode 100644 index 0000000..c7990a4 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/ct_base58_upper_O.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ct_fooObar + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div01_bytes.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div01_bytes.aes new file mode 100644 index 0000000..080fbea --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div01_bytes.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + let _ = #deadbeef + 42 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div02_char_hex_lower.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div02_char_hex_lower.aes new file mode 100644 index 0000000..355d3a8 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div02_char_hex_lower.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + let _ = '\xab' + 42 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div03_char_hexbrace_lower.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div03_char_hexbrace_lower.aes new file mode 100644 index 0000000..40baec1 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div03_char_hexbrace_lower.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + let _ = '\x{ff}' + 42 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div04_multiline_string.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div04_multiline_string.aes new file mode 100644 index 0000000..72f69a3 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div04_multiline_string.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : string = + "hello +world" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div05_bcom_eof.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div05_bcom_eof.aes new file mode 100644 index 0000000..44a710c --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div05_bcom_eof.aes @@ -0,0 +1,4 @@ +contract Test = + type state = unit + entrypoint init() : state = + () /* diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div06_bcom_in_expr.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div06_bcom_in_expr.aes new file mode 100644 index 0000000..a76fd70 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div06_bcom_in_expr.aes @@ -0,0 +1,3 @@ +contract Test = + entrypoint main() : int = + 1 + /* unclosed diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div07_bcom_nested.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div07_bcom_nested.aes new file mode 100644 index 0000000..e6ae12f --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div07_bcom_nested.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + /* outer /* inner */ outer still open + 42 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div08_bcom_simple.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div08_bcom_simple.aes new file mode 100644 index 0000000..fd9a41b --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div08_bcom_simple.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + /* unclosed block comment + 42 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div09_str_bslash_nl.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div09_str_bslash_nl.aes new file mode 100644 index 0000000..af731f0 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div09_str_bslash_nl.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : string = + "hello\ +world" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div10_char_nonascii.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div10_char_nonascii.aes new file mode 100644 index 0000000..0f314b3 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div10_char_nonascii.aes @@ -0,0 +1,5 @@ +contract Test = + type state = unit + entrypoint init() : state = + let _ = 'é' + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div11_char_high_codepoint.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div11_char_high_codepoint.aes new file mode 100644 index 0000000..391e359 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div11_char_high_codepoint.aes @@ -0,0 +1,4 @@ +contract Test = + entrypoint main() : int = + let c = 'Ā' + Char.to_int(c) diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div12_string_high_codepoint.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div12_string_high_codepoint.aes new file mode 100644 index 0000000..dfa7e3d --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div12_string_high_codepoint.aes @@ -0,0 +1,3 @@ +contract Test = + entrypoint greet() : string = + "∑∆∏" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div13_col_drift_2byte_str.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div13_col_drift_2byte_str.aes new file mode 100644 index 0000000..2768280 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div13_col_drift_2byte_str.aes @@ -0,0 +1,3 @@ +contract Col2Byte = + entrypoint greet() : string = + "Héllo" + " world" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div14_col_drift_3byte_str.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div14_col_drift_3byte_str.aes new file mode 100644 index 0000000..93ce384 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div14_col_drift_3byte_str.aes @@ -0,0 +1,3 @@ +contract Col3Byte = + entrypoint price() : string = + "Price: €10" + " incl." diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div15_col_drift_4byte_str.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div15_col_drift_4byte_str.aes new file mode 100644 index 0000000..d25df8c --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div15_col_drift_4byte_str.aes @@ -0,0 +1,3 @@ +contract Col4Byte = + entrypoint celebrate() : string = + "Hello 🎉" + " world" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div16_col_drift_bcom.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div16_col_drift_bcom.aes new file mode 100644 index 0000000..37e33f9 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div16_col_drift_bcom.aes @@ -0,0 +1,3 @@ +contract ColBcom = + entrypoint compute(x : int) : int = + /* résultat */ x + 1 diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div17_col_drift_multi.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div17_col_drift_multi.aes new file mode 100644 index 0000000..a8deac7 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/div17_col_drift_multi.aes @@ -0,0 +1,3 @@ +contract ColMulti = + entrypoint unicode_str() : string = + "Ünïcödé" + " ok" diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/id_quotes.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/id_quotes.aes new file mode 100644 index 0000000..c3341dc --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/id_quotes.aes @@ -0,0 +1,8 @@ +main contract Foo = + type state = unit + + entrypoint init(): state = + () + + entrypoint foo'(): unit = () + entrypoint foo'bar'(): unit = () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/naked_ak.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/naked_ak.aes new file mode 100644 index 0000000..c7e12c6 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/naked_ak.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = ak_ABC123 + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_underscore.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_underscore.aes new file mode 100644 index 0000000..5617b8a --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_underscore.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = sg_foo_bar + () diff --git a/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_upper_I.aes b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_upper_I.aes new file mode 100644 index 0000000..9924cd0 --- /dev/null +++ b/cli/test-data/gsc1_string_to_tokens/tokenizers_agree/sg_base58_upper_I.aes @@ -0,0 +1,5 @@ +contract Foo = + type state = unit + entrypoint init(): state = + let _ = sg_GHI + () diff --git a/cli/test-data/gsc2_tokens_to_ast/aegora_base_contract-1.aes b/cli/test-data/gsc2_tokens_to_ast/aegora_base_contract-1.aes new file mode 100644 index 0000000..c6c9197 --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/aegora_base_contract-1.aes @@ -0,0 +1,116 @@ +/* + * Aegora Base Contract + * + * Author: Craig Everett + * Copyright: Tsuriai Corporation (2022) + * License: GPLv3 + * Version: 1 + * + * This is the base contract for the market. + * It is responsible for: + * - A library of valid contracts by type (sales offers, auctions, etc.) + * - Managing the authorized key list of market masters + * - Providing a single known endpoint to discover deployed, active contracts + * - Managing the lifecycle of a contract + * + * Lifecycle of a sales offer: + * 1. The seller calls AegoraBase.post_sale() to create his SalesOffer + * 2. Sale proceeds + * IF it is succesful, the contract calls AegoraBase.close() + * IF it is revoked by seller, the contract calls AegoraBase.close() + * IF it times out or is killed by the maester, one calls AegoraBase.kill() + * + * Calls to this contract can come from one of five sources: + * 1. The Maester (essentially the administrator) + * - init(template : SalesOffer // Posts the initial contract + * maester : address // with a reference to a template + * tsuriai : address) + * - update_template(contract : SalesOffer) // Update the template address + * - epstein(id : bool) // Force a contract to close + * 2. Sellers + * - post_sale(id : int, price : int) // Create a new sale contract clone + * 3. The Aegora site (or anyone really, but mainly useful for the site backend) + * - lookup(id) // Looks up a sale contract's address + * - template() // Looks up the current sales contract template + * 4. A sales contract + * - close(id : int) // A closing sale contract may call to remove itself from the active index + * 5. The Tsuriai master Key + * - update_maester(key : address) // Change the Maester key for operational reasons + */ + +@compiler == 7.0.1 + +payable contract interface SalesOffer = + entrypoint init : (address, address, address, int, address, int, int) => void + payable entrypoint do_a_backflip : () => bool + + +contract AegoraBase = + record state = + {contracts : map(int, SalesOffer), + template : SalesOffer, + maester : address, + tsuriai : address} + + stateful entrypoint init(template : SalesOffer, + maester : address, + tsuriai : address) : state = + {contracts = {}, + template = template, + maester = maester, + tsuriai = tsuriai} + + public entrypoint template() : SalesOffer = + state.template + + public stateful entrypoint update_template(source : SalesOffer) : unit = + require(Call.caller == state.maester, "Nuh, uh uh! You didn't say the magic word!") + put(state{template = source}) + + public entrypoint lookup(id: int) : SalesOffer = + switch(Map.lookup(id, state.contracts)) + Some(target) => + target + None => + abort("Bad ID!") + + public stateful entrypoint post_sale(agent : address, portion : int, seller : address, id: int, price: int) : SalesOffer = + require(price > 0, "You cannot pay someone to buy things, Mr. Keynes.") + require(!Map.member(id, state.contracts), "People don't think it be like it is, but it do.") + switch(Chain.clone(ref = state.template, + protected = true, + state.tsuriai, + Contract.address, + agent, + portion, + seller, + id, + price)) + Some(posted) => + put(state{contracts = state.contracts{[id] = posted}}) + posted + None => + abort("Bad sale!") + + public stateful entrypoint close(id: int) : bool = + switch(Map.lookup(id, state.contracts)) + Some(target) => + require(target.address == Call.caller, "Bad caller") + put(state{contracts = Map.delete(id, state.contracts)}) + true + None => + false + + public stateful entrypoint update_maester(key: address) : unit = + require(Call.caller == state.tsuriai, "Nuh, uh uh! You didn't say the magic word!") + put(state{maester = key}) + + public stateful entrypoint epstein(id: int) : bool = + require(Call.caller == state.maester, "C'mon, man! You only got the husband and son!") + switch(Map.lookup(id, state.contracts)) + Some(target) => + put(state{contracts = Map.delete(id, state.contracts)}) + target.do_a_backflip() + true + None => + false diff --git a/cli/test-data/gsc2_tokens_to_ast/aegora_sales_contract-1.aes b/cli/test-data/gsc2_tokens_to_ast/aegora_sales_contract-1.aes new file mode 100644 index 0000000..7dae706 --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/aegora_sales_contract-1.aes @@ -0,0 +1,235 @@ +/* + * Aegora Sales Offer + * + * Author: Craig Everett + * Copyright: Tsuriai Corporation (2022) + * License: GPLv3 + * Version: 1 + * + * Sale offers at Aegora (aegora.psychobitch.party) are clones of this contract. + * (The name might be a little weird, but who looks at domain names anymore anyway?) + * When a sale offer is created on the site, the seller signs a contract call to + * + * + * Calls to clones of this contract can come from five sources: + * 1. The base contract (that cloned it in the first place) + * - init(tsuriai : address, // Service fee destination + * aegora : AegoraBase, // Get born + * agent : address, // Deployment agent address + * portion : int, // Deploying agent's fee portion + * seller : address, // Address of the seller + * id : int, + * price : int) + * - do_a_backflip() // Get the opposite of born + * 2. Sellers who posted clones of this contract + * - adjust(price : int) // update price + * - accept() // Sale is DONE + * - refuse() // Cancel a negotation + * - revoke() // Invalidate and disable this offer + * 3. Buyers who are interested in buying through this contract + * - bid(price : int) // Update the bid amout in negotiation + * - hold() // Set HOLD for price negotiation + * - cancel() // Back out of a purchase + * 4. Aegora (the base contract) + * - reassign(tsuriai : address) // Reset Tsuriai's payable address + * 5. The market's network service (or the public) + * - price() // Check current contract price + * - status() // Check contract status + * - agent() // Retrieve the agent's public key (usually the same as the seller) + * - seller() // Retrieve the seller's public key + * - buyer() // Retrieve the buyer's public key + */ + +@compiler == 7.0.1 + +include "Option.aes" + +contract interface AegoraBase = + stateful entrypoint close : (int) => bool + + +payable contract SalesOffer = + record state = + {id : int, + aegora : address, + agent : address, + portion : int, + price : int, + seller : address, + buyer : option(address), + status : status, + tsuriai : address} + + datatype status = OPEN | NEGO | HOLD | DONE + + +// Seller Interface + stateful entrypoint init(tsuriai : address, + aegora : address, + agent : address, + portion : int, + seller : address, + id : int, + price : int) : state = + {id = id, + aegora = aegora, + agent = Call.origin, + portion = portion, + price = price, + seller = seller, + buyer = None, + status = OPEN, + tsuriai = tsuriai} + + public stateful entrypoint adjust(price : int) : unit = + require(state.status != DONE, "HiLlArY wUz HeRe.") + require(Call.caller == state.seller, "Nacho shop!") + require(price > 0, "You can't pay people to buy things, Mr. Keynes.") + switch(state.buyer) + Some(buyer) => + if(Contract.balance > price) + Chain.spend(buyer, Contract.balance - price) + true + else + false + None => + dead_claim() + put(state{price = price}) + + public stateful entrypoint accept() : bool = + require(state.status == NEGO, "Sale is not under negotiation.") + require(Call.caller == state.seller, "Nacho shop!") + require(Contract.balance >= state.price, "Insufficient funds!") + let buyer = Option.force(state.buyer) + if(Contract.balance > state.price) + Chain.spend(buyer, Contract.balance - state.price) + Chain.spend(state.tsuriai, calc_fee()) + if(state.portion > 0) + Chain.spend(state.agent, (Contract.balance / state.portion)) + Chain.spend(state.seller, Contract.balance) + put(state{status = DONE}) + Address.to_contract(state.aegora).close(state.id) + + public stateful entrypoint refuse() : unit = + require(state.status == NEGO || state.status == HOLD, "Sale is not under negotiation.") + require(Call.caller == state.seller, "Nacho shop!") + refund() + put(state{buyer = None}) + put(state{status = OPEN}) + + public stateful entrypoint revoke() : bool = + require(state.status != DONE, "HiLlArY wUz HeRe.") + require(Call.caller == state.seller, "Nacho shop!") + switch(state.status) + OPEN => dead_claim() + NEGO => refund() + HOLD => refund() + put(state{status = DONE}) + Address.to_contract(state.aegora).close(state.id) + + +// Buyer Interface + public stateful payable entrypoint bid(amount: int) : unit = + require(state.status != DONE, "HiLlArY wUz HeRe.") + require(amount >= state.price, "Stop being poor.") + switch(state.status) + OPEN => + require(Call.value >= state.price, "Stop being poor.") + put(state{status = NEGO}) + put(state{buyer = Some(Call.caller)}) + NEGO => + switch(state.buyer) + Some(buyer) => + require(Call.caller == buyer, "Nacho bid.") + require(Contract.balance >= state.price, "Stop being poor.") + if(Contract.balance > amount) + Chain.spend(buyer, Contract.balance - amount) + HOLD => + switch(state.buyer) + Some(buyer) => + require(Call.caller == buyer, "Nacho bid.") + require(Contract.balance >= state.price, "Stop being poor.") + if(Contract.balance > amount) + Chain.spend(buyer, Contract.balance - amount) + put(state{status = NEGO}) + + public stateful entrypoint hold() : unit = + require(state.status == NEGO, "Sale is not in negotiation") + switch(state.buyer) + Some(buyer) => + require(Call.caller == buyer, "Nacho bid!") + put(state{status = HOLD}) + None => + abort("Nacho bid!") + + public stateful entrypoint cancel() : unit = + require(state.status == NEGO || state.status == HOLD, "Wrong status") + switch(state.buyer) + Some(buyer) => + require(Call.caller == buyer, "Nacho bid!") + Chain.spend(state.tsuriai, calc_fee()) + put(state{buyer = None}) + put(state{status = OPEN}) + Chain.spend(buyer, Contract.balance) + None => + abort("Nacho bid!") + + +// Aegora Interface + public stateful entrypoint reassign(tsuriai : address) : unit = + require(Call.caller == state.aegora || Call.origin == state.tsuriai, "Knock it off, stinky") + put(state{tsuriai = tsuriai}) + + public stateful entrypoint sweep_the_table() : bool = + require(state.status == DONE, "Hillary has not yet arrived.") + require(Call.caller == state.tsuriai, "Nope.") + dead_claim() + + public stateful entrypoint do_a_backflip() : bool = + require(Call.caller == state.aegora, "Too late, Nathan.") + put(state{status = DONE}) + dead_claim() + + +// Service/Public Network Interface + public entrypoint price() : int = + state.price + + public entrypoint status() : string = + switch(state.status) + OPEN => "open" + NEGO => "nego" + HOLD => "hold" + DONE => "done" + + public entrypoint agent() : address = + state.agent + + public entrypoint seller() : address = + state.seller + + public entrypoint buyer() : address = + require(state.status != OPEN, "No buyer!") + Option.force(state.buyer) + +// Utilities + function calc_fee() : int = + Contract.balance / 50 + + private stateful function refund() : bool = + if(Contract.balance > 0) + switch(state.buyer) + Some(buyer) => + Chain.spend(buyer, Contract.balance) + true + None => + false + else + false + + private stateful function dead_claim() : bool = + if(Contract.balance > 0) + Chain.spend(state.tsuriai, Contract.balance) + true + else + false diff --git a/cli/test-data/gsc2_tokens_to_ast/evil.aes b/cli/test-data/gsc2_tokens_to_ast/evil.aes new file mode 100644 index 0000000..45f48cf --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/evil.aes @@ -0,0 +1,354 @@ +include "List.aes" +include "Pair.aes" +namespace Miner = + type package_code = string + + record package = { + daily_cap : int, + price : int} + + record aggregated_package = { + daily_cap : int, + count : int} + + record worker = { + daily_cap : int, + can_withdraw_payout : bool, + packages : map(package_code, aggregated_package), + joined_pool_tmst : int} + + record transfer_packs = + { + worker : address, + packages_to_move : list(string * int), + new_address : address + } + + datatype approvable_action = Transfer(transfer_packs) + + function new_package(price : int, cap : int) : package = + {daily_cap = cap, + price = price} + + function claim(ps : list(package_code * (package * int)), joined_tmst : int) : worker = + let daily_cap = daily_cap_from_packs_list(ps) + let packs : map(package_code, aggregated_package) = + List.foldl( + (accum, t) => + let pack_id = Pair.fst(t) + let (pack, cnt) = Pair.snd(t) + let val = + switch(Map.lookup(pack_id, accum)) + None => {daily_cap = pack.daily_cap, count = cnt} + Some(v) => v{count = v.count + cnt} + accum{[pack_id] = val}, + {}, + ps) + {daily_cap = daily_cap, + can_withdraw_payout = false, + packages = packs, + joined_pool_tmst = joined_tmst} + + function split_packages(w : worker, split : transfer_packs) : worker * worker = + let (packages_left, packages_collected) = + List.foldl( + (accum, p) => + let accum_left = Pair.fst(accum) + let accum_collected = Pair.snd(accum) + let code = Pair.fst(p) + let count = Pair.snd(p) + switch(Map.lookup(code, accum_left)) + None => abort("Does not own enough packages") + Some(owned_packs) => + let left_packs = owned_packs.count - count + require(left_packs > -1, "Does not own enough packages") + (accum_left{[code] = owned_packs{count = left_packs}}, (code, owned_packs{count = count}) :: accum_collected), + (w.packages, []), + split.packages_to_move) + let daily_cap_delta = List.sum(List.map((t) => Pair.snd(t).daily_cap * Pair.snd(t).count, packages_collected)) + let new_w = {daily_cap = daily_cap_delta, + can_withdraw_payout = false, + packages = Map.from_list(packages_collected), + joined_pool_tmst = Chain.block_height} + require(Map.size(new_w.packages) == List.length(packages_collected), "Do not split counts of the same package code") + (w{daily_cap = w.daily_cap - daily_cap_delta, packages = packages_left}, new_w) + + function merge_workers(w1 : worker, w2: worker) = + let packages = + List.foldl( + (accum, t) => + let code = Pair.fst(t) + let aggr_pack = Pair.snd(t) + let updated_pack = + switch(Map.lookup(code, accum)) + None => aggr_pack + Some(p) => p{count = p.count + aggr_pack.count} + accum{[code] = updated_pack}, + w1.packages, + Map.to_list(w2.packages)) + // if one of them is allowed to withdraw, so is the resulting new account + let oldest_tmst = + switch(w1.joined_pool_tmst < w2.joined_pool_tmst) + true => w1.joined_pool_tmst + false => w2.joined_pool_tmst + let can_withdraw_payout = w1.can_withdraw_payout || w2.can_withdraw_payout + let daily_cap = daily_cap_from_packages(packages) + {daily_cap = daily_cap, + can_withdraw_payout = can_withdraw_payout, + packages = packages, + joined_pool_tmst = oldest_tmst} + + function daily_cap_from_packs_list(ps : list(package_code * (package * int))) = + List.sum(List.map((t) => Pair.fst(Pair.snd(t)).daily_cap * Pair.snd(Pair.snd(t)), ps)) + + function daily_cap_from_packages(ps : map(package_code, aggregated_package)) = + List.sum(List.map((t) => Pair.snd(t).daily_cap * Pair.snd(t).count, Map.to_list(ps))) + + + + + +contract interface Data = + stateful entrypoint set_pool : (address) => unit + stateful entrypoint add : (address, Miner.worker) => unit + stateful entrypoint remove : (address) => unit + payable stateful entrypoint give_rewards : (list(address * int)) => unit + entrypoint balance : (address) => int + entrypoint assert_is_payable : (address) => unit + stateful entrypoint payout : () => unit + stateful entrypoint payout_without_payable_check : (address) => unit + entrypoint all_balances : () => list(address * int) + entrypoint all_daily_caps : () => list(address * int) + entrypoint all : () => list(address) + entrypoint all_detailed : () => map(address, Miner.worker) + entrypoint member : (address) => bool + entrypoint get : (address) => Miner.worker + stateful entrypoint rename : (address, address) => unit + stateful entrypoint make_payable : (address) => unit + stateful entrypoint make_non_payable : (address) => unit + stateful entrypoint split_packages : (Miner.transfer_packs) => unit + entrypoint all_balances_and_daily_caps : () => list(address * int * int) + +contract interface Pool = + entrypoint leader : () => address + stateful entrypoint enroll : (address, Miner.worker) => unit + entrypoint member : (address) => bool + stateful entrypoint remove : (address) => unit + entrypoint get : (address) => Miner.worker + stateful entrypoint set_locked : (bool) => unit + stateful entrypoint set_leader : (address) => unit + entrypoint can_be_destroyed : () => bool + entrypoint info : () => address * string * address * string * string * string * string * list(string) + stateful entrypoint make_payable : (address) => unit + stateful entrypoint make_non_payable : (address) => unit + entrypoint assert_worker_is_payable : (address) => unit + stateful entrypoint force_payout : (address) => unit + stateful entrypoint change_worker_address : (address, address) => unit + entrypoint balance : (address) => int + stateful entrypoint set_data_ct : (Data) => unit + stateful entrypoint move_data_and_coins_to_new_pool : (Pool) => Data + payable entrypoint receive_coins : () => unit + stateful entrypoint evacuate_coins : (int, address) => unit + stateful entrypoint split_packages : (Miner.transfer_packs) => unit + + +include "Set.aes" +include "List.aes" +include "Pair.aes" + +main contract PoolInstance:Pool = + datatype pool_status = OPEN | LOCKED | MIGRATED + + datatype event + = Enroll(address) + | Remove(address) + + record state = + { + status : pool_status, + leader : address, + main_contract : address, + connect_addresses : Set.set(string), // IP addresses and ports + leader_name : string, + leader_url: string, + leader_avatar_url : string, + leader_description : string, + data_ct : Data + } + + entrypoint init(main_contract : address, leader : address, data_ct : Data) = + {status = OPEN, + leader = leader, + main_contract = main_contract, + connect_addresses = Set.new(), + leader_name = "", + leader_url = "", + leader_avatar_url = "", + leader_description = "", + data_ct = data_ct} + + entrypoint status() = + switch(state.status) + OPEN => "open" + LOCKED => "locked" + MIGRATED => "migrated" + + entrypoint info() = + (Contract.address, status(), state.leader, state.leader_name, state.leader_url, state.leader_avatar_url, state.leader_description, Set.to_list(state.connect_addresses)) + + entrypoint data_contract() = + state.data_ct + + entrypoint main_contract() = + state.main_contract + + entrypoint can_be_destroyed() : bool = + (empty() && state.status == LOCKED && Contract.balance == 0) || state.status == MIGRATED + + entrypoint leader() = + state.leader + + entrypoint miners() = + state.data_ct.all() + + entrypoint miners_detailed() = + state.data_ct.all_detailed() + + entrypoint miner_balances() : list(address * int) = + state.data_ct.all_balances() + + entrypoint miner_daily_caps() : list(address * int) = + state.data_ct.all_daily_caps() + + entrypoint miner_balances_and_caps() : list(address * int * int) = + state.data_ct.all_balances_and_daily_caps() + + entrypoint empty() : bool = + List.is_empty(miners()) + + stateful entrypoint enroll(worker_address : address, worker : Miner.worker) = + assert_caller_is_main_contract() + require(state.status == OPEN, "Pool is locked, can not join it") + Chain.event(Enroll(worker_address)) + add_worker(worker_address, worker) + + entrypoint member(worker_address : address) : bool = + state.data_ct.member(worker_address) + + + /* deletes a worker from the pool if present. Currently the accumulated coins remain in + pool. */ + stateful entrypoint remove(worker_address : address) = + assert_caller_is_main_contract() + Chain.event(Remove(worker_address)) + state.data_ct.remove(worker_address) + + entrypoint get(worker_address : address) = + state.data_ct.get(worker_address) + + // this can overwrite a MIGRATED state + stateful entrypoint set_locked(val : bool) = + assert_caller_is_main_contract() + let s = + switch(val) + true => LOCKED + false => OPEN + put(state{status = s}) + + stateful entrypoint set_leader(new_leader : address) = + assert_caller_is_main_contract() + put(state{leader = new_leader}) + + stateful entrypoint add_connect_address(conn_address : string) = + assert_leader() + put(state{connect_addresses = Set.insert(conn_address, state.connect_addresses)}) + + stateful entrypoint rm_connect_address(conn_address : string) = + assert_leader() + put(state{connect_addresses = Set.delete(conn_address, state.connect_addresses)}) + + stateful entrypoint set_name(name : string) = + assert_leader() + put(state{leader_name = name}) + + stateful entrypoint set_url(url : string) = + assert_leader() + put(state{leader_url = url}) + + stateful entrypoint set_avatar_url(avatar_url : string) = + assert_leader() + put(state{leader_avatar_url = avatar_url}) + + stateful entrypoint set_description(description : string) = + assert_leader() + put(state{leader_description = description}) + + /* NB: does not take into account one's daily limits! */ + stateful entrypoint simply_reward_work(amounts : list(address * int), _ : string) = + assert_leader() + let total_reward = List.sum(List.map((t) => Pair.snd(t), amounts)) + require(Contract.balance >= total_reward, "Not enough GAJU for that reward") + state.data_ct.give_rewards(amounts, value = total_reward) + + payable entrypoint receive_coins() = + () + + // for Eureka + entrypoint balance(addr : address) = + state.data_ct.balance(addr) + + entrypoint daily_cap(addr : address) = + let worker = get(addr) + worker.daily_cap + + stateful entrypoint payout() : unit = + state.data_ct.payout() + + stateful entrypoint force_payout(worker_addr : address) = + assert_caller_is_main_contract() + state.data_ct.payout_without_payable_check(worker_addr) + + stateful entrypoint change_worker_address(old_addr : address, new_addr : address) = + assert_caller_is_main_contract() + state.data_ct.rename(old_addr, new_addr) + + stateful entrypoint evacuate_coins(amount : int, safeheaven : address) = + assert_caller_is_main_contract() + Chain.spend(safeheaven, amount) + + stateful entrypoint make_payable(worker_address : address) = + assert_caller_is_main_contract() + state.data_ct.make_payable(worker_address) + + stateful entrypoint make_non_payable(worker_address : address) = + assert_caller_is_main_contract() + state.data_ct.make_non_payable(worker_address) + + stateful entrypoint set_data_ct(data_ct : Data) = + assert_caller_is_main_contract() + put(state{data_ct = data_ct}) + + stateful entrypoint move_data_and_coins_to_new_pool(new_pool : Pool) = + assert_caller_is_main_contract() + state.data_ct.set_pool(new_pool.address) + new_pool.receive_coins(value = Contract.balance) + put(state{status = MIGRATED}) + state.data_ct + + entrypoint assert_worker_is_payable(worker_address : address) = + state.data_ct.assert_is_payable(worker_address) + + stateful entrypoint split_packages(split : Miner.transfer_packs) = + assert_caller_is_main_contract() + state.data_ct.split_packages(split) + + // private functions + function assert_caller_is_main_contract() = + require(Call.caller == state.main_contract, "Call it through the main pool contract") + + function assert_leader() = + require(Call.origin == state.leader, "Must be called by the leader") + + stateful function add_worker(worker_address, worker) = + state.data_ct.add(worker_address, worker) + diff --git a/cli/test-data/gsc2_tokens_to_ast/gym.aes b/cli/test-data/gsc2_tokens_to_ast/gym.aes new file mode 100644 index 0000000..0c810c9 --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/gym.aes @@ -0,0 +1,145 @@ +/** + * Example gym subscription contract + * + * Copyright (C) 2025, QPQ AG. All Rights Reserved. + * + * Owner can + * - run "tick" to collect monthly fees -> spent to owner + * - unsubscribe anyone + * + * Anyone can + * - subscribe for a period of N months (debit system - all money paid up front) + * - extend their subscription for K months + * - cancel their subscription and withdraw any future money + * + * Owner must issue new contract if he wants to change monthly fee; otherwise + * data structure is too fucking complicated. + */ + +include "List.aes" + +payable contract Gym = + type pucks = int + type n_months = int + record state = {owner : address, + members : map(address, n_months), + monthly_fee : pucks} + + // SFP accessors + entrypoint + get_state : () => state + get_state() = state + stateful entrypoint + put_state : state => unit + put_state(s) = put(s) + + entrypoint + is_member : address => bool + is_member(whomst) = Map.member(whomst, state.members) + + entrypoint + owner : () => address + owner() = state.owner + + entrypoint + members : () => map(address, pucks) + members() = state.members + + entrypoint + monthly_fee : () => pucks + monthly_fee() = state.monthly_fee + + + // actual things + entrypoint + init : (pucks) => state + init(monthly_rate) = + require(monthly_rate >= 0, "monthly rate must be non-negative") + {owner = Call.caller, + members = {}, + monthly_fee = monthly_rate} + + //------------------------------------------------ + // OWNER ENTRYPOINTS: tick/ban + //------------------------------------------------ + + stateful entrypoint + tick : () => unit + tick() | Call.caller != state.owner = + abort("you are not allowed to do ticks!") + tick() = + // collect fees + // 1 monthly charge per member + let total_charge = state.monthly_fee * Map.size(state.members) + Chain.spend(state.owner, total_charge) + + // update members list (clean out everyone with just 1 month left) + let old_members_list : list(address * n_months) = Map.to_list(state.members) + let new_members_list : list(address * n_months) = List.foldl(deduct_month, [], old_members_list) + let new_members : map(address, n_months) = Map.from_list(new_members_list) + let new_state : state = state{members = new_members} + put(new_state) + + function + deduct_month : (list(address*n_months), address*n_months) => list(address*n_months) + // n>1 months left -> subtract a month but keep in membership list + deduct_month(acc, (patron, months_left)) | months_left > 1 = + (patron, months_left-1) :: acc + // only 1 month left -> remove from membership list + deduct_month(acc, (patron, 1)) = + acc + + stateful entrypoint + ban : (address) => unit + ban(_) | Call.caller != state.owner = + abort("you are not allowed to ban people!") + ban(patron) = + refund(patron) + + + //------------------------------------------------ + // PUBLIC ENTRYPOINTS: subscribe/cancel + //------------------------------------------------ + + payable stateful entrypoint + subscribe : (n_months) => unit + subscribe(n) | n < 1 = + abort("must subscribe for at least 1 month") + subscribe(n) | Call.value < n*state.monthly_fee = + abort("not enough money to subscribe for that many months!") + subscribe(n) = + let charge: pucks = n * state.monthly_fee + // call will be successful + // refund caller extra money + let extra_money: pucks = Call.value - charge + Chain.spend(Call.caller, extra_money) + // update membership + let old_months : n_months = Map.lookup_default(Call.caller, state.members, 0) + let new_months : n_months = old_months + n + let new_members : map(address, n_months) = state.members{[Call.caller] = new_months} + let new_state : state = state{members = new_members} + put(new_state) + + stateful entrypoint + cancel : () => unit + cancel() = + refund(Call.caller) + + //------------------------------------------------ + // INTERNAL: refund people + //------------------------------------------------ + + stateful function + refund : (address) => unit + refund(patron) = + let patron_months : n_months = + switch (Map.lookup(patron, state.members)) + None => abort("already not a member!") + Some(remaining_months) => remaining_months + let patron_balance : pucks = patron_months * state.monthly_fee + // update membership + let new_members : map(address, n_months) = Map.delete(patron, state.members) + let new_state : state = state{members = new_members} + put(new_state) + // refund balance to patron + Chain.spend(patron, patron_balance) diff --git a/cli/test-data/gsc2_tokens_to_ast/hello.aes b/cli/test-data/gsc2_tokens_to_ast/hello.aes new file mode 100644 index 0000000..bb943e6 --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/hello.aes @@ -0,0 +1,10 @@ +// Hello World Contract +// Copyright (c) 2025 QPQ AG + +contract Hello = + type state = unit + entrypoint init(): state = + () + + entrypoint hello(): string = + "hello, world" diff --git a/cli/test-data/gsc2_tokens_to_ast/types.aes b/cli/test-data/gsc2_tokens_to_ast/types.aes new file mode 100644 index 0000000..6646117 --- /dev/null +++ b/cli/test-data/gsc2_tokens_to_ast/types.aes @@ -0,0 +1,6 @@ +main contract Foo = + type state = unit + type foo = bar // id + type foo = Bar.baz // qid + type foo = 'bar // tvar + type foo = () => bar diff --git a/src/gsc_bst.erl b/scratch/gsc_bst.erl similarity index 100% rename from src/gsc_bst.erl rename to scratch/gsc_bst.erl