From e8915fba199f733ed6e4204bdab507fcb4462585 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 4 Sep 2024 11:28:25 +0300 Subject: [PATCH 01/12] audit changed + part 1 test fixes --- ...y-all-tickets-different-accounts.scen.json | 3 +- .../buy-more-tickets-than-allowed.scen.json | 3 +- .../buy-ticket-after-deadline.scen.json | 3 +- .../buy-ticket-after-sold-out.scen.json | 3 +- .../buy-ticket-all-options.scen.json | 3 +- .../buy-ticket-another-account.scen.json | 3 +- .../buy-ticket-not-on-whitelist.scen.json | 3 +- .../buy-ticket-same-account.scen.json | 3 +- .../buy-ticket-second-lottery.scen.json | 6 +- .../scenarios/buy-ticket-wrong-fee.scen.json | 3 +- .../scenarios/buy-ticket.scen.json | 3 +- .../complex-prize-distribution.scen.json | 5 +- ...erent-ticket-holders-winner-acc1.scen.json | 19 ++ .../determine-winner-early.scen.json | 3 +- ...ermine-winner-same-ticket-holder.scen.json | 2 +- ...etermine-winner-split-prize-pool.scen.json | 2 +- .../lottery-with-burn-percentage.scen.json | 12 +- .../start-after-announced-winner.scen.json | 5 +- ...art-all-options-bigger-whitelist.scen.json | 5 +- .../start-alternative-function-name.scen.json | 3 +- .../scenarios/start-fixed-deadline.scen.json | 5 +- ...-fixed-deadline-invalid-deadline.scen.json | 2 +- ...eadline-invalid-ticket-price-arg.scen.json | 2 +- ...mited-tickets-and-fixed-deadline.scen.json | 5 +- .../scenarios/start-limited-tickets.scen.json | 5 +- .../scenarios/start-second-lottery.scen.json | 8 +- .../start-with-all-options.scen.json | 5 +- .../scenarios/start-with-no-options.scen.json | 5 +- .../lottery-esdt/src/awarding_status.rs | 8 + .../examples/lottery-esdt/src/lottery.rs | 246 ++++++++++++++---- .../examples/lottery-esdt/src/lottery_info.rs | 1 + .../examples/lottery-esdt/wasm/src/lib.rs | 2 +- contracts/examples/nft-minter/src/lib.rs | 2 +- 33 files changed, 287 insertions(+), 101 deletions(-) create mode 100644 contracts/examples/lottery-esdt/src/awarding_status.rs diff --git a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json index 97ac4a1238..838750ead9 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json @@ -223,7 +223,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:500" + "6-prize_pool": "biguint:500", + "7-unawarded_amount": "biguint:500" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "5", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json index 0a4db9473b..120e2ab51e 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json @@ -80,7 +80,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:100" + "6-prize_pool": "biguint:100", + "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json index 275c1ebbdb..3e2744b7b1 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json @@ -83,7 +83,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:100" + "6-prize_pool": "biguint:100", + "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json index 9f3b23542b..c3f2fae8c6 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json @@ -80,7 +80,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:200" + "6-prize_pool": "biguint:200", + "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json index 181bb2b6ae..3610f1f969 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json @@ -76,7 +76,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:100" + "6-prize_pool": "biguint:100", + "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json index 98d9e37bf2..806eb99ae4 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json @@ -76,7 +76,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:200" + "6-prize_pool": "biguint:200", + "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json index 0fa30523d7..4bf4cdb6a6 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json @@ -82,7 +82,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "+": "" }, diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json index 485b5746ad..e7ad9792ab 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json @@ -76,7 +76,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:200" + "6-prize_pool": "biguint:200", + "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json index 84b5e87b55..8bcd2e926d 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json @@ -76,7 +76,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "str:lotteryInfo|nested:str:lottery_$$$$": { "0-token_identifier": "nested:str:LOTTO-123456", @@ -85,7 +86,8 @@ "3-deadline": "u64:234,567", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:500" + "6-prize_pool": "biguint:500", + "7-unawarded_amount": "biguint:500" }, "str:ticketHolder|nested:str:lottery_$$$$|str:.len": "1", "str:ticketHolder|nested:str:lottery_$$$$|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json index f07677239e..793dee37ab 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json @@ -77,7 +77,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json index fec1ff000c..56f4f0e556 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json @@ -76,7 +76,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:100" + "6-prize_pool": "biguint:100", + "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json index 60d817e065..9f6bd357df 100644 --- a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json @@ -64,7 +64,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:10|u8:50|u8:25|u8:10|u8:5|u8:5|u8:1|u8:1|u8:1|u8:1|u8:1", - "6-prize_pool": "biguint:60700" + "6-prize_pool": "biguint:60700", + "7-unawarded_amount": "biguint:60700" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "10", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", @@ -109,7 +110,7 @@ "gasPrice": "0" }, "expect": { - "out": [], + "out": ["1"], "status": "0", "gas": "*", "refund": "*" diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json index c835331c1a..02298323d6 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json @@ -24,6 +24,25 @@ "gasLimit": "100,000,000", "gasPrice": "0" }, + "expect": { + "out": ["1"], + "status": "0", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "tx": { + "from": "address:acc1", + "to": "sc:lottery", + "function": "claim_rewards", + "arguments": [ + "str:lottery_name" + ], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, "expect": { "out": [], "status": "0", diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json index 0fcad25ec0..6274b9292f 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json @@ -87,7 +87,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:100" + "6-prize_pool": "biguint:100", + "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json index 64f4d475ab..75c2a42ec6 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json @@ -26,7 +26,7 @@ "gasPrice": "0" }, "expect": { - "out": [], + "out": ["1"], "status": "0", "gas": "*", "refund": "*" diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json index 7071e71f3c..e6aea04e48 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json @@ -27,7 +27,7 @@ "gasPrice": "0" }, "expect": { - "out": [], + "out": ["1"], "status": "0", "gas": "*", "refund": "*" diff --git a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json index 9d4876f388..cae45a6f7b 100644 --- a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json @@ -13,7 +13,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -68,7 +68,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -112,7 +112,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "str:burnPercentageForLottery|nested:str:lottery_name": "50" }, @@ -210,7 +211,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:200" + "6-prize_pool": "biguint:200", + "7-unawarded_amount": "biguint:200" }, "str:burnPercentageForLottery|nested:str:lottery_name": "50", "str:ticketHolder|nested:str:lottery_name|str:.len": "2", @@ -237,7 +239,7 @@ "gasPrice": "0" }, "expect": { - "out": [], + "out": ["1"], "status": "0", "message": "", "gas": "*", diff --git a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json index 1d5e5c5a1f..8b78c97348 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -65,7 +65,8 @@ "3-deadline": "u64:12345678905", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json b/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json index 05fc6dcd4f..61eb1a2a3a 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -94,7 +94,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "+": "" }, diff --git a/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json b/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json index 234780df81..cd80ef16a0 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json @@ -62,7 +62,8 @@ "3-deadline": "u64:2592000", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json index 8f0506c3be..67c2076bdd 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json index 353bb29ad7..d733f5df00 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json @@ -18,7 +18,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json index 284841d451..9334ca8288 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json index 583f7db682..739a17650f 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json index e1fbfd225e..19f9971a99 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:2592000", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json b/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json index ee6aa28aa8..7851d7e6ee 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:acc1", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_$$$$", "str:LOTTO-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "str:lotteryInfo|nested:str:lottery_$$$$": { "0-token_identifier": "nested:str:LOTTO-123456", @@ -71,7 +72,8 @@ "3-deadline": "u64:234,567", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json index 7c2ea2c097..f5953adb53 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:123,456", "4-max_entries_per_user": "u32:1", "5-prize_distribution": "u32:2|u8:75|u8:25", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" }, "+": "" }, diff --git a/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json b/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json index 14c9deae2f..2065410821 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json @@ -12,7 +12,7 @@ "tx": { "from": "address:my_address", "to": "sc:lottery", - "function": "start", + "function": "createLotteryPool", "arguments": [ "str:lottery_name", "str:LOTTERY-123456", @@ -62,7 +62,8 @@ "3-deadline": "u64:2592000", "4-max_entries_per_user": "u32:800", "5-prize_distribution": "nested:u8:100", - "6-prize_pool": "biguint:0" + "6-prize_pool": "biguint:0", + "7-unawarded_amount": "biguint:0" } }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/src/awarding_status.rs b/contracts/examples/lottery-esdt/src/awarding_status.rs new file mode 100644 index 0000000000..9d195509d3 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/awarding_status.rs @@ -0,0 +1,8 @@ +use multiversx_sc::derive_imports::*; + +#[type_abi] +#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, PartialEq)] +pub enum AwardingStatus { + Ongoing, + Finished, +} diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index 3c5d9040b1..eb298aece8 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -2,48 +2,24 @@ use multiversx_sc::imports::*; +mod awarding_status; mod lottery_info; mod status; +use awarding_status::AwardingStatus; use lottery_info::LotteryInfo; use status::Status; const PERCENTAGE_TOTAL: u32 = 100; const THIRTY_DAYS_IN_SECONDS: u64 = 60 * 60 * 24 * 30; const MAX_TICKETS: usize = 800; +const MAX_OPERATIONS: usize = 50; #[multiversx_sc::contract] pub trait Lottery { #[init] fn init(&self) {} - #[allow_multiple_var_args] - #[endpoint] - fn start( - &self, - lottery_name: ManagedBuffer, - token_identifier: EgldOrEsdtTokenIdentifier, - ticket_price: BigUint, - opt_total_tickets: Option, - opt_deadline: Option, - opt_max_entries_per_user: Option, - opt_prize_distribution: ManagedOption>, - opt_whitelist: ManagedOption>, - opt_burn_percentage: OptionalValue, - ) { - self.start_lottery( - lottery_name, - token_identifier, - ticket_price, - opt_total_tickets, - opt_deadline, - opt_max_entries_per_user, - opt_prize_distribution, - opt_whitelist, - opt_burn_percentage, - ); - } - #[allow_multiple_var_args] #[endpoint(createLotteryPool)] fn create_lottery_pool( @@ -94,11 +70,14 @@ pub trait Lottery { let prize_distribution = opt_prize_distribution .unwrap_or_else(|| ManagedVec::from_single_item(PERCENTAGE_TOTAL as u8)); + require!( + total_tickets > prize_distribution.len(), + "Number of winners should be smaller than the number of available tickets" + ); require!( self.status(&lottery_name) == Status::Inactive, "Lottery is already active!" ); - require!(!lottery_name.is_empty(), "Can't have empty lottery name!"); require!(token_identifier.is_valid(), "Invalid token name provided!"); require!(ticket_price > 0, "Ticket price must be higher than 0!"); require!( @@ -160,6 +139,7 @@ pub trait Lottery { max_entries_per_user, prize_distribution, prize_pool: BigUint::zero(), + unawarded_amount: BigUint::zero(), }; self.lottery_info(&lottery_name).set(&info); @@ -182,15 +162,21 @@ pub trait Lottery { } #[endpoint] - fn determine_winner(&self, lottery_name: ManagedBuffer) { + fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { match self.status(&lottery_name) { Status::Inactive => sc_panic!("Lottery is inactive!"), Status::Running => sc_panic!("Lottery is still running!"), Status::Ended => { - self.distribute_prizes(&lottery_name); - self.clear_storage(&lottery_name); + if self.total_winning_tickets(&lottery_name).is_empty() { + self.prepare_awarding(&lottery_name); + } + if self.distribute_prizes(&lottery_name) == AwardingStatus::Finished { + self.clear_storage(&lottery_name); + return AwardingStatus::Finished; + } + AwardingStatus::Ongoing }, - }; + } } #[view] @@ -240,12 +226,13 @@ pub trait Lottery { entries += 1; info.tickets_left -= 1; info.prize_pool += &info.ticket_price; + info.unawarded_amount += &info.ticket_price; entries_mapper.set(entries); info_mapper.set(&info); } - fn distribute_prizes(&self, lottery_name: &ManagedBuffer) { + fn prepare_awarding(&self, lottery_name: &ManagedBuffer) { let mut info = self.lottery_info(lottery_name).get(); let ticket_holders_mapper = self.ticket_holders(lottery_name); let total_tickets = ticket_holders_mapper.len(); @@ -266,7 +253,8 @@ pub trait Lottery { self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); } - info.prize_pool -= burn_amount; + info.prize_pool -= &burn_amount; + info.unawarded_amount -= burn_amount; } // if there are less tickets than the distributed prize pool, @@ -277,33 +265,156 @@ pub trait Lottery { } else { info.prize_distribution.len() }; - let total_prize = info.prize_pool.clone(); - let winning_tickets = self.get_distinct_random(1, total_tickets, total_winning_tickets); - - // distribute to the first place last. Laws of probability say that order doesn't matter. - // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times - // 1st place will get the spare money instead. - for i in (1..total_winning_tickets).rev() { - let winning_ticket_id = winning_tickets[i]; - let winner_address = ticket_holders_mapper.get(winning_ticket_id); - let prize = self.calculate_percentage_of( - &total_prize, - &BigUint::from(info.prize_distribution.get(i)), - ); + self.total_winning_tickets(&lottery_name) + .set(total_winning_tickets); + self.index_last_winner(lottery_name).set(1); + } + + fn distribute_prizes(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { + let mut info = self.lottery_info(lottery_name).get(); + let ticket_holders_mapper = self.ticket_holders(lottery_name); + let total_tickets = ticket_holders_mapper.len(); + + let mut index_last_winner = self.index_last_winner(&lottery_name).get(); + let total_winning_tickets = self.total_winning_tickets(lottery_name).get(); + require!( + index_last_winner <= total_winning_tickets, + "Awarding has ended" + ); + + let mut iterations = 0; + while index_last_winner <= total_winning_tickets && iterations < MAX_OPERATIONS { + let rand_index = self.get_distinct_random(index_last_winner, total_tickets, 1)[0]; + + // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper + let winner_address = self.ticket_holders(lottery_name).get(rand_index).clone(); + let last_index_winner_address = + self.ticket_holders(lottery_name).get(index_last_winner); + self.ticket_holders(lottery_name) + .set(rand_index, &last_index_winner_address); + self.ticket_holders(lottery_name) + .set(index_last_winner, &winner_address); + + // distribute to the first place last. Laws of probability say that order doesn't matter. + // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times + // 1st place will get the spare money instead. + if index_last_winner < total_winning_tickets { + let prize = self.calculate_percentage_of( + &info.prize_pool, + &BigUint::from(info.prize_distribution.get(index_last_winner)), + ); + + self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); + + info.unawarded_amount -= prize; + } else { + // insert token in accumulated rewards first place + let first_place_winner = ticket_holders_mapper.get(index_last_winner); + + self.assign_prize_to_winner( + info.token_identifier.clone(), + &info.unawarded_amount, + &first_place_winner, + ); + } + index_last_winner += 1; + iterations += 1; + } + self.lottery_info(lottery_name).set(info); + self.index_last_winner(lottery_name).set(&index_last_winner); + if index_last_winner > total_winning_tickets { + return AwardingStatus::Finished; + } + AwardingStatus::Ongoing + } + + fn assign_prize_to_winner( + &self, + token_id: EgldOrEsdtTokenIdentifier, + amount: &BigUint, + winner: &ManagedAddress, + ) { + self.accumulated_rewards(&token_id, winner) + .update(|value| *value += amount); + + self.user_accumulated_token_rewards(winner).insert(token_id); + } + + #[endpoint] + fn claim_rewards(&self, tokens: MultiValueEncoded) { + let caller = self.blockchain().get_caller(); + require!( + !self.user_accumulated_token_rewards(&caller).is_empty(), + "You have no rewards to claim" + ); + + let mut accumulated_egld_rewards = BigUint::zero(); + let mut accumulated_esdt_rewards = ManagedVec::::new(); + + // to save reviewers time, these 2 iterators have different generics, so it was not possible to make just 1 for loop + + if tokens.is_empty() { + // if wanted tokens were not specified claim all, and clear user_accumulated_token_rewards storage mapper + + for token_id in self.user_accumulated_token_rewards(&caller).iter() { + require!( + !self.accumulated_rewards(&token_id, &caller).is_empty(), + "Token requested not available for claim" + ); + + self.prepare_token_for_claim( + token_id, + &caller, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ); + } + self.user_accumulated_token_rewards(&caller).clear(); + } else { + // otherwise claim just what was requested and remove those tokens from the user_accumulated_token_rewards storage mapper + for token_id in tokens { + let _ = &self + .user_accumulated_token_rewards(&caller) + .swap_remove(&token_id); + + self.prepare_token_for_claim( + token_id, + &caller, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ); + } + }; + + if !accumulated_esdt_rewards.is_empty() { self.tx() - .to(&winner_address) - .egld_or_single_esdt(&info.token_identifier, 0, &prize) + .to(&caller) + .multi_esdt(accumulated_esdt_rewards) .transfer(); - info.prize_pool -= prize; } - // send leftover to first place - let first_place_winner = ticket_holders_mapper.get(winning_tickets[0]); - self.tx() - .to(&first_place_winner) - .egld_or_single_esdt(&info.token_identifier, 0, &info.prize_pool) - .transfer(); + if accumulated_egld_rewards > 0u64 { + self.tx() + .to(&caller) + .egld(accumulated_egld_rewards) + .transfer(); + } + } + + fn prepare_token_for_claim( + &self, + token_id: EgldOrEsdtTokenIdentifier, + caller: &ManagedAddress, + accumulated_egld_rewards: &mut BigUint, + accumulated_esdt_rewards: &mut ManagedVec, + ) { + let value = self.accumulated_rewards(&token_id, &caller).take(); + if token_id.is_egld() { + *accumulated_egld_rewards += value; + } else { + accumulated_esdt_rewards.push(EsdtTokenPayment::new(token_id.unwrap_esdt(), 0, value)); + } } fn clear_storage(&self, lottery_name: &ManagedBuffer) { @@ -318,6 +429,8 @@ pub trait Lottery { ticket_holders_mapper.clear(); self.lottery_info(lottery_name).clear(); self.lottery_whitelist(lottery_name).clear(); + self.total_winning_tickets(lottery_name).clear(); + self.index_last_winner(lottery_name).clear(); self.burn_percentage_for_lottery(lottery_name).clear(); } @@ -348,7 +461,7 @@ pub trait Lottery { let mut rand = RandomnessSource::new(); for i in 0..amount { - let rand_index = rand.next_usize_in_range(0, total_numbers); + let rand_index = rand.next_usize_in_range(i, total_numbers); rand_numbers.swap(i, rand_index); } @@ -376,6 +489,25 @@ pub trait Lottery { #[storage_mapper("ticketHolder")] fn ticket_holders(&self, lottery_name: &ManagedBuffer) -> VecMapper; + #[storage_mapper("accumulatedRewards")] + fn accumulated_rewards( + &self, + token_id: &EgldOrEsdtTokenIdentifier, + user: &ManagedAddress, + ) -> SingleValueMapper; + + #[storage_mapper("totalWinning_tickets")] + fn total_winning_tickets(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; + + #[storage_mapper("indexLastWinner")] + fn index_last_winner(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; + + #[storage_mapper("accumulatedRewards")] + fn user_accumulated_token_rewards( + &self, + user: &ManagedAddress, + ) -> UnorderedSetMapper; + #[storage_mapper("numberOfEntriesForUser")] fn number_of_entries_for_user( &self, diff --git a/contracts/examples/lottery-esdt/src/lottery_info.rs b/contracts/examples/lottery-esdt/src/lottery_info.rs index 4ec2a6055f..319f2ec5c2 100644 --- a/contracts/examples/lottery-esdt/src/lottery_info.rs +++ b/contracts/examples/lottery-esdt/src/lottery_info.rs @@ -15,4 +15,5 @@ pub struct LotteryInfo { pub max_entries_per_user: usize, pub prize_distribution: ManagedVec, pub prize_pool: BigUint, + pub unawarded_amount: BigUint, } diff --git a/contracts/examples/lottery-esdt/wasm/src/lib.rs b/contracts/examples/lottery-esdt/wasm/src/lib.rs index a1184cee72..b5f5528b54 100644 --- a/contracts/examples/lottery-esdt/wasm/src/lib.rs +++ b/contracts/examples/lottery-esdt/wasm/src/lib.rs @@ -18,11 +18,11 @@ multiversx_sc_wasm_adapter::endpoints! { lottery_esdt ( init => init - start => start createLotteryPool => create_lottery_pool buy_ticket => buy_ticket determine_winner => determine_winner status => status + claim_rewards => claim_rewards getLotteryInfo => lottery_info getLotteryWhitelist => lottery_whitelist ) diff --git a/contracts/examples/nft-minter/src/lib.rs b/contracts/examples/nft-minter/src/lib.rs index 4148f368c2..d80a54e500 100644 --- a/contracts/examples/nft-minter/src/lib.rs +++ b/contracts/examples/nft-minter/src/lib.rs @@ -47,7 +47,7 @@ pub trait NftMinter: nft_module::NftModule { } }; - let attributes = ExampleAttributes { + let attributes: ExampleAttributes = ExampleAttributes { creation_timestamp: self.blockchain().get_block_timestamp(), }; self.create_nft_with_attributes( From da3272d94ff59d5035f77bf44073c3b801a8b048 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 4 Sep 2024 11:38:18 +0300 Subject: [PATCH 02/12] clippy --- contracts/examples/lottery-esdt/src/lottery.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index eb298aece8..c506b685f6 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -266,7 +266,7 @@ pub trait Lottery { info.prize_distribution.len() }; - self.total_winning_tickets(&lottery_name) + self.total_winning_tickets(lottery_name) .set(total_winning_tickets); self.index_last_winner(lottery_name).set(1); } @@ -276,7 +276,7 @@ pub trait Lottery { let ticket_holders_mapper = self.ticket_holders(lottery_name); let total_tickets = ticket_holders_mapper.len(); - let mut index_last_winner = self.index_last_winner(&lottery_name).get(); + let mut index_last_winner = self.index_last_winner(lottery_name).get(); let total_winning_tickets = self.total_winning_tickets(lottery_name).get(); require!( index_last_winner <= total_winning_tickets, @@ -322,7 +322,7 @@ pub trait Lottery { iterations += 1; } self.lottery_info(lottery_name).set(info); - self.index_last_winner(lottery_name).set(&index_last_winner); + self.index_last_winner(lottery_name).set(index_last_winner); if index_last_winner > total_winning_tickets { return AwardingStatus::Finished; } @@ -409,7 +409,7 @@ pub trait Lottery { accumulated_egld_rewards: &mut BigUint, accumulated_esdt_rewards: &mut ManagedVec, ) { - let value = self.accumulated_rewards(&token_id, &caller).take(); + let value = self.accumulated_rewards(&token_id, caller).take(); if token_id.is_egld() { *accumulated_egld_rewards += value; } else { From f6e2f8e7801445e9bc514601108e311f91b79059 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 25 Sep 2024 09:44:04 +0300 Subject: [PATCH 03/12] caller of Determine_winner on remote shard (disabled) --- ...erent-ticket-holders-winner-acc1.scen.json | 11 +++++--- .../examples/lottery-esdt/src/lottery.rs | 26 ++++++++++++++----- .../examples/lottery-esdt/wasm/Cargo.lock | 4 +-- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json index 02298323d6..82c9a3c059 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json @@ -25,7 +25,9 @@ "gasPrice": "0" }, "expect": { - "out": ["1"], + "out": [ + "1" + ], "status": "0", "gas": "*", "refund": "*" @@ -59,16 +61,19 @@ "storage": {} }, "address:acc1": { - "nonce": "1", + "nonce": "2", "balance": "1,000,000", "esdt": { - "str:LOTTERY-123456": "200" + "str:LOTTERY-123456": "0" }, "storage": {} }, "address:acc2": { "nonce": "1", "balance": "1,000,000", + "esdt": { + "str:LOTTERY-123456": "0" + }, "storage": {} }, "sc:lottery": { diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index c506b685f6..680e22ce31 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -163,6 +163,15 @@ pub trait Lottery { #[endpoint] fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { + let sc_address = self.blockchain().get_sc_address(); + let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); + let caller = self.blockchain().get_caller(); + let caller_shard = self.blockchain().get_shard_of_address(&caller); + // require!( + // sc_address_shard != caller_shard, + // "Caller needs to be on a remote shard" + // ); + match self.status(&lottery_name) { Status::Inactive => sc_panic!("Lottery is inactive!"), Status::Running => sc_panic!("Lottery is still running!"), @@ -302,12 +311,17 @@ pub trait Lottery { if index_last_winner < total_winning_tickets { let prize = self.calculate_percentage_of( &info.prize_pool, - &BigUint::from(info.prize_distribution.get(index_last_winner)), + &BigUint::from(info.prize_distribution.get(1)), ); - - self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); - - info.unawarded_amount -= prize; + if prize > 0 { + self.assign_prize_to_winner( + info.token_identifier.clone(), + &prize, + &winner_address, + ); + + info.unawarded_amount -= prize; + } } else { // insert token in accumulated rewards first place let first_place_winner = ticket_holders_mapper.get(index_last_winner); @@ -386,7 +400,6 @@ pub trait Lottery { ); } }; - if !accumulated_esdt_rewards.is_empty() { self.tx() .to(&caller) @@ -410,6 +423,7 @@ pub trait Lottery { accumulated_esdt_rewards: &mut ManagedVec, ) { let value = self.accumulated_rewards(&token_id, caller).take(); + sc_print!("caller {:x} has rewards {}", caller, value); if token_id.is_egld() { *accumulated_egld_rewards += value; } else { diff --git a/contracts/examples/lottery-esdt/wasm/Cargo.lock b/contracts/examples/lottery-esdt/wasm/Cargo.lock index 1793c18657..2434926590 100755 --- a/contracts/examples/lottery-esdt/wasm/Cargo.lock +++ b/contracts/examples/lottery-esdt/wasm/Cargo.lock @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unwrap-infallible" From 0232fdaea1f47bd0181eba2a885bca0f34730ab2 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 25 Sep 2024 09:45:51 +0300 Subject: [PATCH 04/12] index last winner update --- contracts/examples/lottery-esdt/src/lottery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index 680e22ce31..0a1a25eeb4 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -311,7 +311,7 @@ pub trait Lottery { if index_last_winner < total_winning_tickets { let prize = self.calculate_percentage_of( &info.prize_pool, - &BigUint::from(info.prize_distribution.get(1)), + &BigUint::from(info.prize_distribution.get(index_last_winner)), ); if prize > 0 { self.assign_prize_to_winner( From 7fd591874b03c3197f414269f5952e84bc55d41b Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 2 Oct 2024 09:52:34 +0300 Subject: [PATCH 05/12] fix after Dorin's review --- .../examples/lottery-esdt/src/lottery.rs | 271 ++++++++++-------- 1 file changed, 155 insertions(+), 116 deletions(-) diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index 0a1a25eeb4..e70da6dc3e 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -127,7 +127,8 @@ pub trait Lottery { if let Some(whitelist) = opt_whitelist.as_option() { let mut mapper = self.lottery_whitelist(&lottery_name); for addr in &*whitelist { - mapper.insert(addr); + let addr_id = self.addres_to_id_mapper().get_id_or_insert(&addr); + mapper.insert(addr_id); } } @@ -163,10 +164,10 @@ pub trait Lottery { #[endpoint] fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { - let sc_address = self.blockchain().get_sc_address(); - let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); - let caller = self.blockchain().get_caller(); - let caller_shard = self.blockchain().get_shard_of_address(&caller); + // let sc_address = self.blockchain().get_sc_address(); + // let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); + // let caller = self.blockchain().get_caller(); + // let caller_shard = self.blockchain().get_shard_of_address(&caller); // require!( // sc_address_shard != caller_shard, // "Caller needs to be on a remote shard" @@ -177,6 +178,7 @@ pub trait Lottery { Status::Running => sc_panic!("Lottery is still running!"), Status::Ended => { if self.total_winning_tickets(&lottery_name).is_empty() { + require!(self.lottery_info(&lottery_name).is_empty(), "whoops!"); self.prepare_awarding(&lottery_name); } if self.distribute_prizes(&lottery_name) == AwardingStatus::Finished { @@ -212,10 +214,11 @@ pub trait Lottery { let info_mapper = self.lottery_info(lottery_name); let mut info = info_mapper.get(); let caller = self.blockchain().get_caller(); + let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); let whitelist = self.lottery_whitelist(lottery_name); require!( - whitelist.is_empty() || whitelist.contains(&caller), + whitelist.is_empty() || whitelist.contains(&caller_id), "You are not allowed to participate in this lottery!" ); require!( @@ -223,14 +226,14 @@ pub trait Lottery { "Wrong ticket fee!" ); - let entries_mapper = self.number_of_entries_for_user(lottery_name, &caller); + let entries_mapper = self.number_of_entries_for_user(lottery_name, &caller_id); let mut entries = entries_mapper.get(); require!( entries < info.max_entries_per_user, "Ticket limit exceeded for this lottery!" ); - self.ticket_holders(lottery_name).push(&caller); + self.ticket_holders(lottery_name).push(&caller_id); entries += 1; info.tickets_left -= 1; @@ -250,21 +253,7 @@ pub trait Lottery { return; } - let burn_percentage = self.burn_percentage_for_lottery(lottery_name).get(); - if burn_percentage > 0 { - let burn_amount = self.calculate_percentage_of(&info.prize_pool, &burn_percentage); - - // Prevent crashing if the role was unset while the lottery was running - // The tokens will simply remain locked forever - let esdt_token_id = info.token_identifier.clone().unwrap_esdt(); - let roles = self.blockchain().get_esdt_local_roles(&esdt_token_id); - if roles.has_role(&EsdtLocalRole::Burn) { - self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); - } - - info.prize_pool -= &burn_amount; - info.unawarded_amount -= burn_amount; - } + self.burn_prize_percentage(lottery_name, &mut info); // if there are less tickets than the distributed prize pool, // the 1st place gets the leftover, maybe could split between the remaining @@ -280,6 +269,35 @@ pub trait Lottery { self.index_last_winner(lottery_name).set(1); } + fn burn_prize_percentage( + &self, + lottery_name: &ManagedBuffer, + info: &mut LotteryInfo, + ) { + let burn_percentage = self.burn_percentage_for_lottery(lottery_name).get(); + if burn_percentage == 0 { + sc_print!("no burns occured {}", 0); + return; + } + + let burn_amount = self.calculate_percentage_of(&info.prize_pool, &burn_percentage); + + // Prevent crashing if the role was unset while the lottery was running + // The tokens will simply remain locked forever + let esdt_token_id = info.token_identifier.clone().unwrap_esdt(); + let roles = self.blockchain().get_esdt_local_roles(&esdt_token_id); + if roles.has_role(&EsdtLocalRole::Burn) { + self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); + } + + sc_print!("amount to burn: {}", burn_amount); + sc_print!("prize left after burn: {}", info.prize_pool); + info.prize_pool -= &burn_amount; + info.unawarded_amount -= burn_amount; + sc_print!("prize left after burn: {}", info.prize_pool); + sc_print!("unawarded amount after burn: {}", info.unawarded_amount); + } + fn distribute_prizes(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { let mut info = self.lottery_info(lottery_name).get(); let ticket_holders_mapper = self.ticket_holders(lottery_name); @@ -294,44 +312,13 @@ pub trait Lottery { let mut iterations = 0; while index_last_winner <= total_winning_tickets && iterations < MAX_OPERATIONS { - let rand_index = self.get_distinct_random(index_last_winner, total_tickets, 1)[0]; - - // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper - let winner_address = self.ticket_holders(lottery_name).get(rand_index).clone(); - let last_index_winner_address = - self.ticket_holders(lottery_name).get(index_last_winner); - self.ticket_holders(lottery_name) - .set(rand_index, &last_index_winner_address); - self.ticket_holders(lottery_name) - .set(index_last_winner, &winner_address); - - // distribute to the first place last. Laws of probability say that order doesn't matter. - // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times - // 1st place will get the spare money instead. - if index_last_winner < total_winning_tickets { - let prize = self.calculate_percentage_of( - &info.prize_pool, - &BigUint::from(info.prize_distribution.get(index_last_winner)), - ); - if prize > 0 { - self.assign_prize_to_winner( - info.token_identifier.clone(), - &prize, - &winner_address, - ); - - info.unawarded_amount -= prize; - } - } else { - // insert token in accumulated rewards first place - let first_place_winner = ticket_holders_mapper.get(index_last_winner); - - self.assign_prize_to_winner( - info.token_identifier.clone(), - &info.unawarded_amount, - &first_place_winner, - ); - } + self.award_winner( + lottery_name, + &index_last_winner, + total_tickets, + total_winning_tickets, + &mut info, + ); index_last_winner += 1; iterations += 1; } @@ -343,23 +330,72 @@ pub trait Lottery { AwardingStatus::Ongoing } + fn award_winner( + &self, + lottery_name: &ManagedBuffer, + index_last_winner: &usize, + total_tickets: usize, + total_winning_tickets: usize, + info: &mut LotteryInfo, + ) { + let rand_index = self.get_distinct_random(*index_last_winner, total_tickets); + let ticket_holders_mapper = self.ticket_holders(lottery_name); + + // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper + let winner_address = self.ticket_holders(lottery_name).get(rand_index); + let last_index_winner_address = self.ticket_holders(lottery_name).get(*index_last_winner); + self.ticket_holders(lottery_name) + .set(rand_index, &last_index_winner_address); + self.ticket_holders(lottery_name) + .set(*index_last_winner, &winner_address); + + // distribute to the first place last. Laws of probability say that order doesn't matter. + // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times + // 1st place will get the spare money instead. + if *index_last_winner < total_winning_tickets { + let prize = self.calculate_percentage_of( + &info.prize_pool, + &BigUint::from( + info.prize_distribution + .get(total_winning_tickets - *index_last_winner), + ), + ); + if prize > 0 { + self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); + + info.unawarded_amount -= prize; + } + } else { + // insert token in accumulated rewards first place + let first_place_winner = ticket_holders_mapper.get(*index_last_winner); + + self.assign_prize_to_winner( + info.token_identifier.clone(), + &info.unawarded_amount, + &first_place_winner, + ); + } + } + fn assign_prize_to_winner( &self, token_id: EgldOrEsdtTokenIdentifier, amount: &BigUint, - winner: &ManagedAddress, + winner_id: &u64, ) { - self.accumulated_rewards(&token_id, winner) + self.accumulated_rewards(&token_id, winner_id) .update(|value| *value += amount); - self.user_accumulated_token_rewards(winner).insert(token_id); + self.user_accumulated_token_rewards(winner_id) + .insert(token_id); } #[endpoint] fn claim_rewards(&self, tokens: MultiValueEncoded) { let caller = self.blockchain().get_caller(); + let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); require!( - !self.user_accumulated_token_rewards(&caller).is_empty(), + !self.user_accumulated_token_rewards(&caller_id).is_empty(), "You have no rewards to claim" ); @@ -371,34 +407,32 @@ pub trait Lottery { if tokens.is_empty() { // if wanted tokens were not specified claim all, and clear user_accumulated_token_rewards storage mapper - for token_id in self.user_accumulated_token_rewards(&caller).iter() { + let mut all_tokens: ManagedVec = + ManagedVec::new(); + + for token_id in self.user_accumulated_token_rewards(&caller_id).iter() { require!( - !self.accumulated_rewards(&token_id, &caller).is_empty(), + !self.accumulated_rewards(&token_id, &caller_id).is_empty(), "Token requested not available for claim" ); - - self.prepare_token_for_claim( - token_id, - &caller, - &mut accumulated_egld_rewards, - &mut accumulated_esdt_rewards, - ); + all_tokens.push(token_id); } - self.user_accumulated_token_rewards(&caller).clear(); + + self.claim_rewards_user( + all_tokens, + &caller_id, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ) } else { // otherwise claim just what was requested and remove those tokens from the user_accumulated_token_rewards storage mapper - for token_id in tokens { - let _ = &self - .user_accumulated_token_rewards(&caller) - .swap_remove(&token_id); - - self.prepare_token_for_claim( - token_id, - &caller, - &mut accumulated_egld_rewards, - &mut accumulated_esdt_rewards, - ); - } + + self.claim_rewards_user( + tokens.to_vec(), + &caller_id, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ) }; if !accumulated_esdt_rewards.is_empty() { self.tx() @@ -415,15 +449,36 @@ pub trait Lottery { } } + fn claim_rewards_user( + &self, + tokens: ManagedVec, + caller_id: &u64, + accumulated_egld_rewards: &mut BigUint, + accumulated_esdt_rewards: &mut ManagedVec, + ) { + for token_id in tokens.iter().rev() { + let _ = &self + .user_accumulated_token_rewards(caller_id) + .swap_remove(&token_id); + + self.prepare_token_for_claim( + token_id, + caller_id, + accumulated_egld_rewards, + accumulated_esdt_rewards, + ); + } + } + fn prepare_token_for_claim( &self, token_id: EgldOrEsdtTokenIdentifier, - caller: &ManagedAddress, + caller_id: &u64, accumulated_egld_rewards: &mut BigUint, accumulated_esdt_rewards: &mut ManagedVec, ) { - let value = self.accumulated_rewards(&token_id, caller).take(); - sc_print!("caller {:x} has rewards {}", caller, value); + let value = self.accumulated_rewards(&token_id, caller_id).take(); + sc_print!("caller {:x} has rewards {}", caller_id, value); if token_id.is_egld() { *accumulated_egld_rewards += value; } else { @@ -459,27 +514,9 @@ pub trait Lottery { } /// does not check if max - min >= amount, that is the caller's job - fn get_distinct_random( - &self, - min: usize, - max: usize, - amount: usize, - ) -> ArrayVec { - let mut rand_numbers = ArrayVec::new(); - - for num in min..=max { - rand_numbers.push(num); - } - - let total_numbers = rand_numbers.len(); + fn get_distinct_random(&self, min: usize, max: usize) -> usize { let mut rand = RandomnessSource::new(); - - for i in 0..amount { - let rand_index = rand.next_usize_in_range(i, total_numbers); - rand_numbers.swap(i, rand_index); - } - - rand_numbers + rand.next_usize_in_range(min, max) } fn calculate_percentage_of(&self, value: &BigUint, percentage: &BigUint) -> BigUint { @@ -497,17 +534,16 @@ pub trait Lottery { #[view(getLotteryWhitelist)] #[storage_mapper("lotteryWhitelist")] - fn lottery_whitelist(&self, lottery_name: &ManagedBuffer) - -> UnorderedSetMapper; + fn lottery_whitelist(&self, lottery_name: &ManagedBuffer) -> UnorderedSetMapper; #[storage_mapper("ticketHolder")] - fn ticket_holders(&self, lottery_name: &ManagedBuffer) -> VecMapper; + fn ticket_holders(&self, lottery_name: &ManagedBuffer) -> VecMapper; #[storage_mapper("accumulatedRewards")] fn accumulated_rewards( &self, token_id: &EgldOrEsdtTokenIdentifier, - user: &ManagedAddress, + user_id: &u64, ) -> SingleValueMapper; #[storage_mapper("totalWinning_tickets")] @@ -519,16 +555,19 @@ pub trait Lottery { #[storage_mapper("accumulatedRewards")] fn user_accumulated_token_rewards( &self, - user: &ManagedAddress, + user_id: &u64, ) -> UnorderedSetMapper; #[storage_mapper("numberOfEntriesForUser")] fn number_of_entries_for_user( &self, lottery_name: &ManagedBuffer, - user: &ManagedAddress, + user_id: &u64, ) -> SingleValueMapper; + #[storage_mapper("addressToIdMapper")] + fn addres_to_id_mapper(&self) -> AddressToIdMapper; + #[storage_mapper("burnPercentageForLottery")] fn burn_percentage_for_lottery( &self, From c9c2a98bcbf369369740a96a070684ec4745fc4b Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 2 Oct 2024 16:55:20 +0300 Subject: [PATCH 06/12] fix tests part 1 --- ...y-all-tickets-different-accounts.scen.json | 31 ++++++--- .../buy-more-tickets-than-allowed.scen.json | 7 +- .../buy-ticket-after-deadline.scen.json | 7 +- .../buy-ticket-after-sold-out.scen.json | 13 ++-- .../buy-ticket-all-options.scen.json | 6 +- .../buy-ticket-another-account.scen.json | 13 ++-- .../buy-ticket-same-account.scen.json | 9 ++- .../buy-ticket-second-lottery.scen.json | 7 +- .../scenarios/buy-ticket.scen.json | 7 +- .../complex-prize-distribution.scen.json | 65 +++++++++++++------ .../determine-winner-early.scen.json | 7 +- .../lottery-with-burn-percentage.scen.json | 13 ++-- .../examples/lottery-esdt/src/lottery.rs | 1 + 13 files changed, 128 insertions(+), 58 deletions(-) diff --git a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json index 838750ead9..b348baeae6 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json @@ -227,16 +227,27 @@ "7-unawarded_amount": "biguint:500" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "5", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:3": "address:acc3", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:4": "address:acc4", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:5": "address:acc5", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc2": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc3": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc4": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc5": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "2", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:3": "3", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:4": "4", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:5": "5", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:2": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:3": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:4": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:5": "1", + "str:addressToIdMapper|str:lastId": "5", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2", + "str:addressToIdMapper|str:addr|address:acc3": "3", + "str:addressToIdMapper|str:addrId|u64:3": "address:acc3", + "str:addressToIdMapper|str:addr|address:acc4": "4", + "str:addressToIdMapper|str:addrId|u64:4": "address:acc4", + "str:addressToIdMapper|str:addr|address:acc5": "5", + "str:addressToIdMapper|str:addrId|u64:5": "address:acc5", "+": "" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json index 120e2ab51e..c6faa40d52 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json @@ -84,8 +84,11 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", "+": "" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json index 3e2744b7b1..d1a95f326a 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json @@ -87,8 +87,11 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json index c3f2fae8c6..d8e6e14f1b 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json @@ -84,10 +84,15 @@ "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc2", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc2": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "2", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:2": "1", + "str:addressToIdMapper|str:lastId": "2", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json index 3610f1f969..9fd379f21f 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json @@ -80,8 +80,10 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", "+": "" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json index 806eb99ae4..c25ff2f048 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json @@ -80,10 +80,15 @@ "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc2", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc2": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "2", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:2": "1", + "str:addressToIdMapper|str:lastId": "2", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json index e7ad9792ab..257c1ae2a0 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json @@ -80,9 +80,12 @@ "7-unawarded_amount": "biguint:200" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "2" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "2", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json index 8bcd2e926d..550eecac6f 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json @@ -90,8 +90,11 @@ "7-unawarded_amount": "biguint:500" }, "str:ticketHolder|nested:str:lottery_$$$$|str:.len": "1", - "str:ticketHolder|nested:str:lottery_$$$$|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_$$$$|address:acc1": "1" + "str:ticketHolder|nested:str:lottery_$$$$|str:.item|u32:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_$$$$|u64:1": "1", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json index 56f4f0e556..1684c7fa3c 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json @@ -80,8 +80,11 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json index 9f6bd357df..5fb50a4ed5 100644 --- a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json @@ -68,26 +68,47 @@ "7-unawarded_amount": "biguint:60700" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "10", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:3": "address:acc3", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:4": "address:acc4", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:5": "address:acc5", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:6": "address:acc6", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:7": "address:acc7", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:8": "address:acc8", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:9": "address:acc9", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:10": "address:acc10", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc2": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc3": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc4": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc5": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc6": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc7": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc8": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc9": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc10": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "2", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:3": "3", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:4": "4", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:5": "5", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:6": "6", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:7": "7", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:8": "8", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:9": "9", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:10": "10", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:2": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:3": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:4": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:5": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:6": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:7": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:8": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:9": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:10": "1", + "str:addressToIdMapper|str:lastId": "10", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2", + "str:addressToIdMapper|str:addr|address:acc3": "3", + "str:addressToIdMapper|str:addrId|u64:3": "address:acc3", + "str:addressToIdMapper|str:addr|address:acc4": "4", + "str:addressToIdMapper|str:addrId|u64:4": "address:acc4", + "str:addressToIdMapper|str:addr|address:acc5": "5", + "str:addressToIdMapper|str:addrId|u64:5": "address:acc5", + "str:addressToIdMapper|str:addr|address:acc6": "6", + "str:addressToIdMapper|str:addrId|u64:6": "address:acc6", + "str:addressToIdMapper|str:addr|address:acc7": "7", + "str:addressToIdMapper|str:addrId|u64:7": "address:acc7", + "str:addressToIdMapper|str:addr|address:acc8": "8", + "str:addressToIdMapper|str:addrId|u64:8": "address:acc8", + "str:addressToIdMapper|str:addr|address:acc9": "9", + "str:addressToIdMapper|str:addrId|u64:9": "address:acc9", + "str:addressToIdMapper|str:addr|address:acc10": "01", + "str:addressToIdMapper|str:addrId|u64:10": "address:acc10" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } @@ -110,7 +131,9 @@ "gasPrice": "0" }, "expect": { - "out": ["1"], + "out": [ + "1" + ], "status": "0", "gas": "*", "refund": "*" diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json index 6274b9292f..f2e2dc4896 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json @@ -91,8 +91,11 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "1" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } diff --git a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json index cae45a6f7b..780593093a 100644 --- a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json @@ -216,9 +216,12 @@ }, "str:burnPercentageForLottery|nested:str:lottery_name": "50", "str:ticketHolder|nested:str:lottery_name|str:.len": "2", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "address:acc1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "address:acc1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|address:acc1": "2" + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:2": "1", + "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "2", + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" }, @@ -239,7 +242,9 @@ "gasPrice": "0" }, "expect": { - "out": ["1"], + "out": [ + "1" + ], "status": "0", "message": "", "gas": "*", diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index e70da6dc3e..519adb8b83 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -344,6 +344,7 @@ pub trait Lottery { // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper let winner_address = self.ticket_holders(lottery_name).get(rand_index); let last_index_winner_address = self.ticket_holders(lottery_name).get(*index_last_winner); + self.ticket_holders(lottery_name) .set(rand_index, &last_index_winner_address); self.ticket_holders(lottery_name) From 381f5de2c20d01f2b04767d01bc907d747488d25 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Wed, 2 Oct 2024 17:09:23 +0300 Subject: [PATCH 07/12] test fixes part 2 --- .../buy-more-tickets-than-allowed.scen.json | 13 ++++++++----- .../scenarios/buy-ticket-all-options.scen.json | 7 +++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json index c6faa40d52..b81b6688f1 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json @@ -84,11 +84,14 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "1", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", - "str:addressToIdMapper|str:lastId": "1", - "str:addressToIdMapper|str:addr|address:acc1": "1", - "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "2", + "str:addressToIdMapper|str:lastId": "3", + "str:addressToIdMapper|str:addr|address:my_address": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:my_address", + "str:addressToIdMapper|str:addr|address:acc1": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "3", + "str:addressToIdMapper|str:addrId|u64:3": "address:acc2", "+": "" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json index 9fd379f21f..abb8945225 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json @@ -80,10 +80,9 @@ "7-unawarded_amount": "biguint:100" }, "str:ticketHolder|nested:str:lottery_name|str:.len": "1", - "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "", - "str:numberOfEntriesForUser|u32:12|str:lottery_name|u64:1": "1", - "str:addressToIdMapper|str:addr|address:acc1": "1", - "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:ticketHolder|nested:str:lottery_name|str:.item|u32:1": "2", + "str:addressToIdMapper|str:addr|address:my_address": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:my_address", "+": "" }, "code": "mxsc:../output/lottery-esdt.mxsc.json" From 5431cd50a0c04ef955cf7a54e0ca26c8db4fd275 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Tue, 19 Nov 2024 07:11:59 +0200 Subject: [PATCH 08/12] different shard call tests update --- ...uy-all-tickets-different-accounts.scen.json | 17 +++++++++++------ .../buy-more-tickets-than-allowed.scen.json | 9 +++++++-- .../buy-ticket-after-deadline.scen.json | 9 +++++++-- ...uy-ticket-after-determined-winner.scen.json | 9 +++++++-- .../buy-ticket-after-sold-out.scen.json | 9 +++++++-- .../scenarios/buy-ticket-all-options.scen.json | 9 +++++++-- .../buy-ticket-another-account.scen.json | 9 +++++++-- .../buy-ticket-not-on-whitelist.scen.json | 9 +++++++-- .../buy-ticket-same-account.scen.json | 9 +++++++-- .../buy-ticket-second-lottery.scen.json | 9 +++++++-- .../scenarios/buy-ticket-wrong-fee.scen.json | 9 +++++++-- .../scenarios/buy-ticket.scen.json | 9 +++++++-- .../complex-prize-distribution.scen.json | 11 ++++++++--- ...ferent-ticket-holders-winner-acc1.scen.json | 13 +++++++++---- .../scenarios/determine-winner-early.scen.json | 11 ++++++++--- ...termine-winner-same-ticket-holder.scen.json | 13 ++++++++++--- ...determine-winner-split-prize-pool.scen.json | 13 ++++++++++--- .../scenarios/lottery-init.scen.json | 13 +++++++++++-- .../lottery-with-burn-percentage.scen.json | 18 +++++++++--------- .../start-after-announced-winner.scen.json | 9 +++++++-- ...tart-all-options-bigger-whitelist.scen.json | 9 +++++++-- .../start-alternative-function-name.scen.json | 9 +++++++-- .../scenarios/start-fixed-deadline.scen.json | 9 +++++++-- ...d-fixed-deadline-invalid-deadline.scen.json | 9 +++++++-- ...deadline-invalid-ticket-price-arg.scen.json | 9 +++++++-- ...imited-tickets-and-fixed-deadline.scen.json | 9 +++++++-- .../scenarios/start-limited-tickets.scen.json | 9 +++++++-- .../scenarios/start-second-lottery.scen.json | 9 +++++++-- .../scenarios/start-with-all-options.scen.json | 9 +++++++-- .../scenarios/start-with-no-options.scen.json | 9 +++++++-- contracts/examples/lottery-esdt/src/lottery.rs | 17 ++++++++--------- 31 files changed, 238 insertions(+), 86 deletions(-) diff --git a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json index b348baeae6..e2922a0e62 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-all-tickets-different-accounts.scen.json @@ -51,7 +51,7 @@ "id": "buy-ticket-acc1", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -77,7 +77,7 @@ "id": "buy-ticket-acc2", "tx": { "from": "address:acc2", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -103,7 +103,7 @@ "id": "buy-ticket-acc3", "tx": { "from": "address:acc3", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -129,7 +129,7 @@ "id": "buy-ticket-acc4", "tx": { "from": "address:acc4", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -155,7 +155,7 @@ "id": "buy-ticket-acc5", "tx": { "from": "address:acc5", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -184,6 +184,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -209,7 +214,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json index b81b6688f1..d126467bf5 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-more-tickets-than-allowed.scen.json @@ -23,7 +23,7 @@ "id": "buy-more-tickets-than-allowed", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -53,6 +53,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -66,7 +71,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json index d1a95f326a..dd31f101fe 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-deadline.scen.json @@ -26,7 +26,7 @@ "id": "buy-ticket-after-deadline", "tx": { "from": "address:acc2", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -56,6 +56,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -69,7 +74,7 @@ }, "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json index f1fe7fbc7f..987e5c1ef6 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-after-announced-winner", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -53,6 +53,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "1", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -66,7 +71,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json index d8e6e14f1b..6830a698dc 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-sold-out.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-after-sold-out", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -53,6 +53,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -66,7 +71,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json index abb8945225..441006ab70 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-all-options.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -52,6 +52,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -62,7 +67,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json index c25ff2f048..1fbcd6f725 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-another-account.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-2-another-account", "tx": { "from": "address:acc2", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -52,6 +52,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -62,7 +67,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json index 4bf4cdb6a6..fe4b320a9d 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-not-on-whitelist.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-not-on-whitelist", "tx": { "from": "address:acc3", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -53,6 +53,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -71,7 +76,7 @@ }, "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json index 257c1ae2a0..c5a24adaad 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-same-account.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-2-same-account", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -52,6 +52,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -62,7 +67,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json index 550eecac6f..8743c3accb 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-second-lottery.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket-2nd-lottery", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTO-123456", @@ -52,6 +52,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -62,7 +67,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json index 793dee37ab..6ae0e7e1a8 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-wrong-fee.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -53,6 +53,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -66,7 +71,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json index 1684c7fa3c..df2fd13eaa 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket.scen.json @@ -23,7 +23,7 @@ "id": "buy-ticket", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -52,6 +52,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -62,7 +67,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json index 5fb50a4ed5..eb3a7927a8 100644 --- a/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/complex-prize-distribution.scen.json @@ -13,6 +13,11 @@ "nonce": "1", "balance": "0" }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "0" @@ -53,7 +58,7 @@ "nonce": "0", "balance": "0" }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "60700", "storage": { @@ -122,7 +127,7 @@ "id": "determine-winner-same-ticket-holder", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -197,7 +202,7 @@ "balance": "3,035", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json index 82c9a3c059..bfcb0c1270 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json @@ -15,8 +15,8 @@ { "step": "scCall", "tx": { - "from": "address:my_address", - "to": "sc:lottery", + "from": "address:other_shard_address#41", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -37,7 +37,7 @@ "step": "scCall", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "claim_rewards", "arguments": [ "str:lottery_name" @@ -60,6 +60,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "1", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -76,7 +81,7 @@ }, "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json index f2e2dc4896..d19ab4da87 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-early.scen.json @@ -17,7 +17,7 @@ "id": "status-test", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "status", "arguments": [ "str:lottery_name" @@ -39,7 +39,7 @@ "id": "determine-winner-early", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -63,6 +63,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -73,7 +78,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json index 75c2a42ec6..cd657d5b9f 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json @@ -17,7 +17,7 @@ "id": "determine-winner-same-ticket-holder", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -26,7 +26,9 @@ "gasPrice": "0" }, "expect": { - "out": ["1"], + "out": [ + "1" + ], "status": "0", "gas": "*", "refund": "*" @@ -40,6 +42,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "2", "balance": "1,000,000", @@ -53,7 +60,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json index e6aea04e48..bd29bd11e5 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-split-prize-pool.scen.json @@ -18,7 +18,7 @@ "id": "determine-winner-with-split-prize-pool", "tx": { "from": "address:acc4", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -27,7 +27,9 @@ "gasPrice": "0" }, "expect": { - "out": ["1"], + "out": [ + "1" + ], "status": "0", "gas": "*", "refund": "*" @@ -41,6 +43,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -72,7 +79,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/lottery-init.scen.json b/contracts/examples/lottery-esdt/scenarios/lottery-init.scen.json index 607af2736e..dc6fd2e377 100644 --- a/contracts/examples/lottery-esdt/scenarios/lottery-init.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/lottery-init.scen.json @@ -9,6 +9,10 @@ "nonce": "0", "balance": "1,000,000" }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000" + }, "address:acc1": { "nonce": "0", "balance": "1,000,000" @@ -22,7 +26,7 @@ { "creatorAddress": "address:my_address", "creatorNonce": "0", - "newAddress": "sc:lottery" + "newAddress": "sc:lottery#42" } ] }, @@ -51,6 +55,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -61,7 +70,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json index 780593093a..b05b03e4d4 100644 --- a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json @@ -12,7 +12,7 @@ "comment": "try starting lottery without setting local burn role", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -47,7 +47,7 @@ "str:LOTTERY-123456": "200" } }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { @@ -67,7 +67,7 @@ "comment": "start lottery after setting local burn role", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -94,7 +94,7 @@ { "step": "checkState", "accounts": { - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { @@ -127,7 +127,7 @@ "id": "buy first ticket", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -154,7 +154,7 @@ "id": "buy second ticket", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "esdtValue": [ { "tokenIdentifier": "str:LOTTERY-123456", @@ -187,7 +187,7 @@ }, "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { @@ -233,7 +233,7 @@ "id": "determine-winner-same-ticket-holder", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "determine_winner", "arguments": [ "str:lottery_name" @@ -263,7 +263,7 @@ }, "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "esdt": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json index 8b78c97348..320f6c7dcb 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json @@ -11,7 +11,7 @@ "id": "start limited tickets, fixed deadline. Again.", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "1", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -54,7 +59,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json b/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json index 61eb1a2a3a..347cc3d7a1 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-all-options-bigger-whitelist.scen.json @@ -11,7 +11,7 @@ "id": "start with all options, bigger whitelist", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -58,6 +58,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -83,7 +88,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json b/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json index cd80ef16a0..2533120853 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-alternative-function-name.scen.json @@ -11,7 +11,7 @@ "id": "start with no options altenrative name", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json index 67c2076bdd..8a995e16a4 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-fixed-deadline.scen.json @@ -11,7 +11,7 @@ "id": "start fixed deadline", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json index d733f5df00..4aecdbffa0 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-deadline.scen.json @@ -17,7 +17,7 @@ "id": "start limited tickets, fixed deadline, invalid deadline arg", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -48,6 +48,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -58,7 +63,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json index 9334ca8288..d65bb92326 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline-invalid-ticket-price-arg.scen.json @@ -11,7 +11,7 @@ "id": "start limited tickets, fixed deadline, invalid ticket price arg", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -42,6 +42,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -52,7 +57,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": {}, diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json index 739a17650f..0b064d8931 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets-and-fixed-deadline.scen.json @@ -11,7 +11,7 @@ "id": "start limited tickets, fixed deadline", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json index 19f9971a99..5ae7d2b1d0 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-limited-tickets.scen.json @@ -11,7 +11,7 @@ "id": "start limited tickets", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json b/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json index 7851d7e6ee..680220a9ff 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-second-lottery.scen.json @@ -11,7 +11,7 @@ "id": "start 2nd limited tickets, fixed deadline", "tx": { "from": "address:acc1", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_$$$$", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "1", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json b/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json index f5953adb53..6f80e128af 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-with-all-options.scen.json @@ -11,7 +11,7 @@ "id": "start with all options", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json b/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json index 2065410821..e04359aef4 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-with-no-options.scen.json @@ -11,7 +11,7 @@ "id": "start with no options", "tx": { "from": "address:my_address", - "to": "sc:lottery", + "to": "sc:lottery#42", "function": "createLotteryPool", "arguments": [ "str:lottery_name", @@ -41,6 +41,11 @@ "balance": "1,000,000", "storage": {} }, + "address:other_shard_address#41": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, "address:acc1": { "nonce": "0", "balance": "1,000,000", @@ -51,7 +56,7 @@ "balance": "1,000,000", "storage": {} }, - "sc:lottery": { + "sc:lottery#42": { "nonce": "0", "balance": "0", "storage": { diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index 519adb8b83..b05157d7ed 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -164,21 +164,20 @@ pub trait Lottery { #[endpoint] fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { - // let sc_address = self.blockchain().get_sc_address(); - // let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); - // let caller = self.blockchain().get_caller(); - // let caller_shard = self.blockchain().get_shard_of_address(&caller); - // require!( - // sc_address_shard != caller_shard, - // "Caller needs to be on a remote shard" - // ); + let sc_address = self.blockchain().get_sc_address(); + let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); + let caller = self.blockchain().get_caller(); + let caller_shard = self.blockchain().get_shard_of_address(&caller); + require!( + sc_address_shard != caller_shard, + "Caller needs to be on a remote shard" + ); match self.status(&lottery_name) { Status::Inactive => sc_panic!("Lottery is inactive!"), Status::Running => sc_panic!("Lottery is still running!"), Status::Ended => { if self.total_winning_tickets(&lottery_name).is_empty() { - require!(self.lottery_info(&lottery_name).is_empty(), "whoops!"); self.prepare_awarding(&lottery_name); } if self.distribute_prizes(&lottery_name) == AwardingStatus::Finished { From 15f9b2931928f6e46dd555164d679bb5b1ac5c0d Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Mon, 16 Dec 2024 12:15:19 +0200 Subject: [PATCH 09/12] fix reward amount not getting changed --- ...y-ticket-after-determined-winner.scen.json | 10 +++++-- ...erent-ticket-holders-winner-acc1.scen.json | 14 ++++++--- ...ermine-winner-same-ticket-holder.scen.json | 27 +++++++++++++++-- .../lottery-with-burn-percentage.scen.json | 24 ++++++++++++++- .../start-after-announced-winner.scen.json | 9 ++++-- .../examples/lottery-esdt/src/lottery.rs | 30 ++++++++----------- 6 files changed, 85 insertions(+), 29 deletions(-) diff --git a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json index 987e5c1ef6..c7e5f29e0f 100644 --- a/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/buy-ticket-after-determined-winner.scen.json @@ -49,7 +49,7 @@ "step": "checkState", "accounts": { "address:my_address": { - "nonce": "3", + "nonce": "2", "balance": "1,000,000", "storage": {} }, @@ -74,7 +74,13 @@ "sc:lottery#42": { "nonce": "0", "balance": "0", - "storage": {}, + "storage": { + "str:addressToIdMapper|str:lastId": "2", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2" + }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } } diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json index bfcb0c1270..4cce58a60b 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-different-ticket-holders-winner-acc1.scen.json @@ -40,7 +40,7 @@ "to": "sc:lottery#42", "function": "claim_rewards", "arguments": [ - "str:lottery_name" + "str:LOTTERY-123456" ], "gasLimit": "100,000,000", "gasPrice": "0" @@ -56,7 +56,7 @@ "step": "checkState", "accounts": { "address:my_address": { - "nonce": "3", + "nonce": "2", "balance": "1,000,000", "storage": {} }, @@ -69,7 +69,7 @@ "nonce": "2", "balance": "1,000,000", "esdt": { - "str:LOTTERY-123456": "0" + "str:LOTTERY-123456": "200" }, "storage": {} }, @@ -84,7 +84,13 @@ "sc:lottery#42": { "nonce": "0", "balance": "0", - "storage": {}, + "storage": { + "str:addressToIdMapper|str:lastId": "2", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2" + }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } } diff --git a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json index cd657d5b9f..14c4ffa741 100644 --- a/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/determine-winner-same-ticket-holder.scen.json @@ -34,6 +34,25 @@ "refund": "*" } }, + { + "step": "scCall", + "tx": { + "from": "address:acc1", + "to": "sc:lottery#42", + "function": "claim_rewards", + "arguments": [ + "str:LOTTERY-123456" + ], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "accounts": { @@ -48,7 +67,7 @@ "storage": {} }, "address:acc1": { - "nonce": "2", + "nonce": "3", "balance": "1,000,000", "esdt": { "str:LOTTERY-123456": "200" @@ -63,7 +82,11 @@ "sc:lottery#42": { "nonce": "0", "balance": "0", - "storage": {}, + "storage": { + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1" + }, "code": "mxsc:../output/lottery-esdt.mxsc.json" } } diff --git a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json index b05b03e4d4..4922763e37 100644 --- a/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/lottery-with-burn-percentage.scen.json @@ -251,12 +251,31 @@ "refund": "*" } }, + { + "step": "scCall", + "tx": { + "from": "address:acc1", + "to": "sc:lottery#42", + "function": "claim_rewards", + "arguments": [ + "str:LOTTERY-123456" + ], + "gasLimit": "100,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "gas": "*", + "refund": "*" + } + }, { "step": "checkState", "comment": "check that 50% was burned, and 50% returned to acc1", "accounts": { "address:acc1": { - "nonce": "2", + "nonce": "3", "balance": "1,000,000", "esdt": { "str:LOTTERY-123456": "100" @@ -274,6 +293,9 @@ } }, "storage": { + "str:addressToIdMapper|str:lastId": "1", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", "str:lotteryInfo|nested:str:lottery_name": "", "str:burnPercentageForLottery|nested:str:lottery_name": "0" }, diff --git a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json index 320f6c7dcb..7e4d6475a8 100644 --- a/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json +++ b/contracts/examples/lottery-esdt/scenarios/start-after-announced-winner.scen.json @@ -37,7 +37,7 @@ "step": "checkState", "accounts": { "address:my_address": { - "nonce": "4", + "nonce": "3", "balance": "1,000,000", "storage": {} }, @@ -47,7 +47,7 @@ "storage": {} }, "address:acc1": { - "nonce": "1", + "nonce": "2", "balance": "1,000,000", "esdt": { "str:LOTTERY-123456": "200" @@ -63,6 +63,11 @@ "nonce": "0", "balance": "0", "storage": { + "str:addressToIdMapper|str:lastId": "2", + "str:addressToIdMapper|str:addr|address:acc1": "1", + "str:addressToIdMapper|str:addrId|u64:1": "address:acc1", + "str:addressToIdMapper|str:addr|address:acc2": "2", + "str:addressToIdMapper|str:addrId|u64:2": "address:acc2", "str:lotteryInfo|nested:str:lottery_name": { "0-token_identifier": "nested:str:LOTTERY-123456", "1-ticket_price": "biguint:1000", diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index b05157d7ed..4accfc9cb7 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -176,17 +176,15 @@ pub trait Lottery { match self.status(&lottery_name) { Status::Inactive => sc_panic!("Lottery is inactive!"), Status::Running => sc_panic!("Lottery is still running!"), - Status::Ended => { - if self.total_winning_tickets(&lottery_name).is_empty() { - self.prepare_awarding(&lottery_name); - } - if self.distribute_prizes(&lottery_name) == AwardingStatus::Finished { - self.clear_storage(&lottery_name); - return AwardingStatus::Finished; - } - AwardingStatus::Ongoing - }, + Status::Ended => self.handle_awarding(&lottery_name), + } + } + + fn handle_awarding(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { + if self.total_winning_tickets(&lottery_name).is_empty() { + self.prepare_awarding(&lottery_name); } + self.distribute_prizes(&lottery_name) } #[view] @@ -266,6 +264,8 @@ pub trait Lottery { self.total_winning_tickets(lottery_name) .set(total_winning_tickets); self.index_last_winner(lottery_name).set(1); + + self.lottery_info(lottery_name).set(info); } fn burn_prize_percentage( @@ -275,7 +275,6 @@ pub trait Lottery { ) { let burn_percentage = self.burn_percentage_for_lottery(lottery_name).get(); if burn_percentage == 0 { - sc_print!("no burns occured {}", 0); return; } @@ -289,12 +288,8 @@ pub trait Lottery { self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); } - sc_print!("amount to burn: {}", burn_amount); - sc_print!("prize left after burn: {}", info.prize_pool); info.prize_pool -= &burn_amount; info.unawarded_amount -= burn_amount; - sc_print!("prize left after burn: {}", info.prize_pool); - sc_print!("unawarded amount after burn: {}", info.unawarded_amount); } fn distribute_prizes(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { @@ -324,6 +319,7 @@ pub trait Lottery { self.lottery_info(lottery_name).set(info); self.index_last_winner(lottery_name).set(index_last_winner); if index_last_winner > total_winning_tickets { + self.clear_storage(&lottery_name); return AwardingStatus::Finished; } AwardingStatus::Ongoing @@ -352,7 +348,7 @@ pub trait Lottery { // distribute to the first place last. Laws of probability say that order doesn't matter. // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times // 1st place will get the spare money instead. - if *index_last_winner < total_winning_tickets { + if *index_last_winner <= total_winning_tickets { let prize = self.calculate_percentage_of( &info.prize_pool, &BigUint::from( @@ -385,7 +381,6 @@ pub trait Lottery { ) { self.accumulated_rewards(&token_id, winner_id) .update(|value| *value += amount); - self.user_accumulated_token_rewards(winner_id) .insert(token_id); } @@ -478,7 +473,6 @@ pub trait Lottery { accumulated_esdt_rewards: &mut ManagedVec, ) { let value = self.accumulated_rewards(&token_id, caller_id).take(); - sc_print!("caller {:x} has rewards {}", caller_id, value); if token_id.is_egld() { *accumulated_egld_rewards += value; } else { From 00b5a4e220686600bbb1e468632c6bae244b5762 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Mon, 16 Dec 2024 12:20:41 +0200 Subject: [PATCH 10/12] clippy --- contracts/examples/lottery-esdt/src/lottery.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index 4accfc9cb7..c1e801c72b 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -181,10 +181,10 @@ pub trait Lottery { } fn handle_awarding(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { - if self.total_winning_tickets(&lottery_name).is_empty() { - self.prepare_awarding(&lottery_name); + if self.total_winning_tickets(lottery_name).is_empty() { + self.prepare_awarding(lottery_name); } - self.distribute_prizes(&lottery_name) + self.distribute_prizes(lottery_name) } #[view] @@ -319,7 +319,7 @@ pub trait Lottery { self.lottery_info(lottery_name).set(info); self.index_last_winner(lottery_name).set(index_last_winner); if index_last_winner > total_winning_tickets { - self.clear_storage(&lottery_name); + self.clear_storage(lottery_name); return AwardingStatus::Finished; } AwardingStatus::Ongoing From 3da60f8b70c1269c46f56fa73249e3544563bb6e Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Mon, 16 Dec 2024 12:57:02 +0200 Subject: [PATCH 11/12] reorgazing --- .../lottery-esdt/src/basics/constants.rs | 4 + .../examples/lottery-esdt/src/basics/mod.rs | 4 + .../lottery-esdt/src/basics/storage.rs | 42 ++ .../examples/lottery-esdt/src/basics/utils.rs | 25 + .../examples/lottery-esdt/src/basics/views.rs | 31 + .../examples/lottery-esdt/src/lottery.rs | 570 +----------------- .../lottery-esdt/src/specific/award.rs | 194 ++++++ .../src/{ => specific}/awarding_status.rs | 0 .../examples/lottery-esdt/src/specific/buy.rs | 63 ++ .../lottery-esdt/src/specific/claim.rs | 101 ++++ .../src/{ => specific}/lottery_info.rs | 0 .../examples/lottery-esdt/src/specific/mod.rs | 7 + .../lottery-esdt/src/specific/setup.rs | 138 +++++ .../lottery-esdt/src/{ => specific}/status.rs | 0 .../examples/lottery-esdt/wasm/src/lib.rs | 6 +- 15 files changed, 625 insertions(+), 560 deletions(-) create mode 100644 contracts/examples/lottery-esdt/src/basics/constants.rs create mode 100644 contracts/examples/lottery-esdt/src/basics/mod.rs create mode 100644 contracts/examples/lottery-esdt/src/basics/storage.rs create mode 100644 contracts/examples/lottery-esdt/src/basics/utils.rs create mode 100644 contracts/examples/lottery-esdt/src/basics/views.rs create mode 100644 contracts/examples/lottery-esdt/src/specific/award.rs rename contracts/examples/lottery-esdt/src/{ => specific}/awarding_status.rs (100%) create mode 100644 contracts/examples/lottery-esdt/src/specific/buy.rs create mode 100644 contracts/examples/lottery-esdt/src/specific/claim.rs rename contracts/examples/lottery-esdt/src/{ => specific}/lottery_info.rs (100%) create mode 100644 contracts/examples/lottery-esdt/src/specific/mod.rs create mode 100644 contracts/examples/lottery-esdt/src/specific/setup.rs rename contracts/examples/lottery-esdt/src/{ => specific}/status.rs (100%) diff --git a/contracts/examples/lottery-esdt/src/basics/constants.rs b/contracts/examples/lottery-esdt/src/basics/constants.rs new file mode 100644 index 0000000000..0bd62754d9 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/basics/constants.rs @@ -0,0 +1,4 @@ +pub const PERCENTAGE_TOTAL: u32 = 100; +pub const THIRTY_DAYS_IN_SECONDS: u64 = 60 * 60 * 24 * 30; +pub const MAX_TICKETS: usize = 800; +pub const MAX_OPERATIONS: usize = 50; diff --git a/contracts/examples/lottery-esdt/src/basics/mod.rs b/contracts/examples/lottery-esdt/src/basics/mod.rs new file mode 100644 index 0000000000..9efc59385f --- /dev/null +++ b/contracts/examples/lottery-esdt/src/basics/mod.rs @@ -0,0 +1,4 @@ +pub mod constants; +pub mod storage; +pub mod utils; +pub mod views; diff --git a/contracts/examples/lottery-esdt/src/basics/storage.rs b/contracts/examples/lottery-esdt/src/basics/storage.rs new file mode 100644 index 0000000000..aff1483a2d --- /dev/null +++ b/contracts/examples/lottery-esdt/src/basics/storage.rs @@ -0,0 +1,42 @@ +use multiversx_sc::imports::*; + +#[multiversx_sc::module] +pub trait StorageModule { + #[storage_mapper("ticketHolder")] + fn ticket_holders(&self, lottery_name: &ManagedBuffer) -> VecMapper; + + #[storage_mapper("accumulatedRewards")] + fn accumulated_rewards( + &self, + token_id: &EgldOrEsdtTokenIdentifier, + user_id: &u64, + ) -> SingleValueMapper; + + #[storage_mapper("totalWinning_tickets")] + fn total_winning_tickets(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; + + #[storage_mapper("indexLastWinner")] + fn index_last_winner(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; + + #[storage_mapper("accumulatedRewards")] + fn user_accumulated_token_rewards( + &self, + user_id: &u64, + ) -> UnorderedSetMapper; + + #[storage_mapper("numberOfEntriesForUser")] + fn number_of_entries_for_user( + &self, + lottery_name: &ManagedBuffer, + user_id: &u64, + ) -> SingleValueMapper; + + #[storage_mapper("addressToIdMapper")] + fn addres_to_id_mapper(&self) -> AddressToIdMapper; + + #[storage_mapper("burnPercentageForLottery")] + fn burn_percentage_for_lottery( + &self, + lottery_name: &ManagedBuffer, + ) -> SingleValueMapper; +} diff --git a/contracts/examples/lottery-esdt/src/basics/utils.rs b/contracts/examples/lottery-esdt/src/basics/utils.rs new file mode 100644 index 0000000000..d944c56c0d --- /dev/null +++ b/contracts/examples/lottery-esdt/src/basics/utils.rs @@ -0,0 +1,25 @@ +use multiversx_sc::imports::*; + +use crate::constants::PERCENTAGE_TOTAL; + +#[multiversx_sc::module] +pub trait UtilsModule { + fn sum_array(&self, array: &ManagedVec) -> u32 { + let mut sum = 0; + + for item in array { + sum += item as u32; + } + + sum + } + + /// does not check if max - min >= amount, that is the caller's job + fn get_distinct_random(&self, min: usize, max: usize) -> usize { + let mut rand = RandomnessSource::new(); + rand.next_usize_in_range(min, max) + } + fn calculate_percentage_of(&self, value: &BigUint, percentage: &BigUint) -> BigUint { + value * percentage / PERCENTAGE_TOTAL + } +} diff --git a/contracts/examples/lottery-esdt/src/basics/views.rs b/contracts/examples/lottery-esdt/src/basics/views.rs new file mode 100644 index 0000000000..1dbd73cda3 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/basics/views.rs @@ -0,0 +1,31 @@ +use crate::{LotteryInfo, Status}; +use multiversx_sc::imports::*; + +#[multiversx_sc::module] +pub trait ViewsModule { + #[view] + fn status(&self, lottery_name: &ManagedBuffer) -> Status { + if self.lottery_info(lottery_name).is_empty() { + return Status::Inactive; + } + + let info = self.lottery_info(lottery_name).get(); + let current_time = self.blockchain().get_block_timestamp(); + if current_time > info.deadline || info.tickets_left == 0 { + return Status::Ended; + } + + Status::Running + } + + #[view(getLotteryInfo)] + #[storage_mapper("lotteryInfo")] + fn lottery_info( + &self, + lottery_name: &ManagedBuffer, + ) -> SingleValueMapper>; + + #[view(getLotteryWhitelist)] + #[storage_mapper("lotteryWhitelist")] + fn lottery_whitelist(&self, lottery_name: &ManagedBuffer) -> UnorderedSetMapper; +} diff --git a/contracts/examples/lottery-esdt/src/lottery.rs b/contracts/examples/lottery-esdt/src/lottery.rs index c1e801c72b..c0542a03e8 100644 --- a/contracts/examples/lottery-esdt/src/lottery.rs +++ b/contracts/examples/lottery-esdt/src/lottery.rs @@ -1,570 +1,26 @@ #![no_std] +use basics::{constants, storage, utils, views}; use multiversx_sc::imports::*; +use specific::{award, awarding_status, buy, claim, lottery_info, setup, status}; -mod awarding_status; -mod lottery_info; -mod status; +mod basics; +mod specific; use awarding_status::AwardingStatus; use lottery_info::LotteryInfo; use status::Status; -const PERCENTAGE_TOTAL: u32 = 100; -const THIRTY_DAYS_IN_SECONDS: u64 = 60 * 60 * 24 * 30; -const MAX_TICKETS: usize = 800; -const MAX_OPERATIONS: usize = 50; - #[multiversx_sc::contract] -pub trait Lottery { +pub trait Lottery: + award::AwardingModule + + views::ViewsModule + + storage::StorageModule + + utils::UtilsModule + + claim::ClaimModule + + buy::BuyTicketModule + + setup::SetupModule +{ #[init] fn init(&self) {} - - #[allow_multiple_var_args] - #[endpoint(createLotteryPool)] - fn create_lottery_pool( - &self, - lottery_name: ManagedBuffer, - token_identifier: EgldOrEsdtTokenIdentifier, - ticket_price: BigUint, - opt_total_tickets: Option, - opt_deadline: Option, - opt_max_entries_per_user: Option, - opt_prize_distribution: ManagedOption>, - opt_whitelist: ManagedOption>, - opt_burn_percentage: OptionalValue, - ) { - self.start_lottery( - lottery_name, - token_identifier, - ticket_price, - opt_total_tickets, - opt_deadline, - opt_max_entries_per_user, - opt_prize_distribution, - opt_whitelist, - opt_burn_percentage, - ); - } - - #[allow_multiple_var_args] - #[allow(clippy::too_many_arguments)] - fn start_lottery( - &self, - lottery_name: ManagedBuffer, - token_identifier: EgldOrEsdtTokenIdentifier, - ticket_price: BigUint, - opt_total_tickets: Option, - opt_deadline: Option, - opt_max_entries_per_user: Option, - opt_prize_distribution: ManagedOption>, - opt_whitelist: ManagedOption>, - opt_burn_percentage: OptionalValue, - ) { - require!(!lottery_name.is_empty(), "Name can't be empty!"); - - let timestamp = self.blockchain().get_block_timestamp(); - let total_tickets = opt_total_tickets.unwrap_or(MAX_TICKETS); - let deadline = opt_deadline.unwrap_or(timestamp + THIRTY_DAYS_IN_SECONDS); - let max_entries_per_user = opt_max_entries_per_user.unwrap_or(MAX_TICKETS); - let prize_distribution = opt_prize_distribution - .unwrap_or_else(|| ManagedVec::from_single_item(PERCENTAGE_TOTAL as u8)); - - require!( - total_tickets > prize_distribution.len(), - "Number of winners should be smaller than the number of available tickets" - ); - require!( - self.status(&lottery_name) == Status::Inactive, - "Lottery is already active!" - ); - require!(token_identifier.is_valid(), "Invalid token name provided!"); - require!(ticket_price > 0, "Ticket price must be higher than 0!"); - require!( - total_tickets > 0, - "Must have more than 0 tickets available!" - ); - require!( - total_tickets <= MAX_TICKETS, - "Only 800 or less total tickets per lottery are allowed!" - ); - require!(deadline > timestamp, "Deadline can't be in the past!"); - require!( - deadline <= timestamp + THIRTY_DAYS_IN_SECONDS, - "Deadline can't be later than 30 days from now!" - ); - require!( - max_entries_per_user > 0, - "Must have more than 0 max entries per user!" - ); - require!( - self.sum_array(&prize_distribution) == PERCENTAGE_TOTAL, - "Prize distribution must add up to exactly 100(%)!" - ); - - match opt_burn_percentage { - OptionalValue::Some(burn_percentage) => { - require!(!token_identifier.is_egld(), "EGLD can't be burned!"); - - let roles = self - .blockchain() - .get_esdt_local_roles(&token_identifier.clone().unwrap_esdt()); - require!( - roles.has_role(&EsdtLocalRole::Burn), - "The contract can't burn the selected token!" - ); - - require!( - burn_percentage < PERCENTAGE_TOTAL, - "Invalid burn percentage!" - ); - self.burn_percentage_for_lottery(&lottery_name) - .set(burn_percentage); - }, - OptionalValue::None => {}, - } - - if let Some(whitelist) = opt_whitelist.as_option() { - let mut mapper = self.lottery_whitelist(&lottery_name); - for addr in &*whitelist { - let addr_id = self.addres_to_id_mapper().get_id_or_insert(&addr); - mapper.insert(addr_id); - } - } - - let info = LotteryInfo { - token_identifier, - ticket_price, - tickets_left: total_tickets, - deadline, - max_entries_per_user, - prize_distribution, - prize_pool: BigUint::zero(), - unawarded_amount: BigUint::zero(), - }; - - self.lottery_info(&lottery_name).set(&info); - } - - #[endpoint] - #[payable("*")] - fn buy_ticket(&self, lottery_name: ManagedBuffer) { - let (token_identifier, payment) = self.call_value().egld_or_single_fungible_esdt(); - - match self.status(&lottery_name) { - Status::Inactive => sc_panic!("Lottery is currently inactive."), - Status::Running => { - self.update_after_buy_ticket(&lottery_name, &token_identifier, &payment) - }, - Status::Ended => { - sc_panic!("Lottery entry period has ended! Awaiting winner announcement.") - }, - }; - } - - #[endpoint] - fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { - let sc_address = self.blockchain().get_sc_address(); - let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); - let caller = self.blockchain().get_caller(); - let caller_shard = self.blockchain().get_shard_of_address(&caller); - require!( - sc_address_shard != caller_shard, - "Caller needs to be on a remote shard" - ); - - match self.status(&lottery_name) { - Status::Inactive => sc_panic!("Lottery is inactive!"), - Status::Running => sc_panic!("Lottery is still running!"), - Status::Ended => self.handle_awarding(&lottery_name), - } - } - - fn handle_awarding(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { - if self.total_winning_tickets(lottery_name).is_empty() { - self.prepare_awarding(lottery_name); - } - self.distribute_prizes(lottery_name) - } - - #[view] - fn status(&self, lottery_name: &ManagedBuffer) -> Status { - if self.lottery_info(lottery_name).is_empty() { - return Status::Inactive; - } - - let info = self.lottery_info(lottery_name).get(); - let current_time = self.blockchain().get_block_timestamp(); - if current_time > info.deadline || info.tickets_left == 0 { - return Status::Ended; - } - - Status::Running - } - - fn update_after_buy_ticket( - &self, - lottery_name: &ManagedBuffer, - token_identifier: &EgldOrEsdtTokenIdentifier, - payment: &BigUint, - ) { - let info_mapper = self.lottery_info(lottery_name); - let mut info = info_mapper.get(); - let caller = self.blockchain().get_caller(); - let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); - let whitelist = self.lottery_whitelist(lottery_name); - - require!( - whitelist.is_empty() || whitelist.contains(&caller_id), - "You are not allowed to participate in this lottery!" - ); - require!( - token_identifier == &info.token_identifier && payment == &info.ticket_price, - "Wrong ticket fee!" - ); - - let entries_mapper = self.number_of_entries_for_user(lottery_name, &caller_id); - let mut entries = entries_mapper.get(); - require!( - entries < info.max_entries_per_user, - "Ticket limit exceeded for this lottery!" - ); - - self.ticket_holders(lottery_name).push(&caller_id); - - entries += 1; - info.tickets_left -= 1; - info.prize_pool += &info.ticket_price; - info.unawarded_amount += &info.ticket_price; - - entries_mapper.set(entries); - info_mapper.set(&info); - } - - fn prepare_awarding(&self, lottery_name: &ManagedBuffer) { - let mut info = self.lottery_info(lottery_name).get(); - let ticket_holders_mapper = self.ticket_holders(lottery_name); - let total_tickets = ticket_holders_mapper.len(); - - if total_tickets == 0 { - return; - } - - self.burn_prize_percentage(lottery_name, &mut info); - - // if there are less tickets than the distributed prize pool, - // the 1st place gets the leftover, maybe could split between the remaining - // but this is a rare case anyway and it's not worth the overhead - let total_winning_tickets = if total_tickets < info.prize_distribution.len() { - total_tickets - } else { - info.prize_distribution.len() - }; - - self.total_winning_tickets(lottery_name) - .set(total_winning_tickets); - self.index_last_winner(lottery_name).set(1); - - self.lottery_info(lottery_name).set(info); - } - - fn burn_prize_percentage( - &self, - lottery_name: &ManagedBuffer, - info: &mut LotteryInfo, - ) { - let burn_percentage = self.burn_percentage_for_lottery(lottery_name).get(); - if burn_percentage == 0 { - return; - } - - let burn_amount = self.calculate_percentage_of(&info.prize_pool, &burn_percentage); - - // Prevent crashing if the role was unset while the lottery was running - // The tokens will simply remain locked forever - let esdt_token_id = info.token_identifier.clone().unwrap_esdt(); - let roles = self.blockchain().get_esdt_local_roles(&esdt_token_id); - if roles.has_role(&EsdtLocalRole::Burn) { - self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); - } - - info.prize_pool -= &burn_amount; - info.unawarded_amount -= burn_amount; - } - - fn distribute_prizes(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { - let mut info = self.lottery_info(lottery_name).get(); - let ticket_holders_mapper = self.ticket_holders(lottery_name); - let total_tickets = ticket_holders_mapper.len(); - - let mut index_last_winner = self.index_last_winner(lottery_name).get(); - let total_winning_tickets = self.total_winning_tickets(lottery_name).get(); - require!( - index_last_winner <= total_winning_tickets, - "Awarding has ended" - ); - - let mut iterations = 0; - while index_last_winner <= total_winning_tickets && iterations < MAX_OPERATIONS { - self.award_winner( - lottery_name, - &index_last_winner, - total_tickets, - total_winning_tickets, - &mut info, - ); - index_last_winner += 1; - iterations += 1; - } - self.lottery_info(lottery_name).set(info); - self.index_last_winner(lottery_name).set(index_last_winner); - if index_last_winner > total_winning_tickets { - self.clear_storage(lottery_name); - return AwardingStatus::Finished; - } - AwardingStatus::Ongoing - } - - fn award_winner( - &self, - lottery_name: &ManagedBuffer, - index_last_winner: &usize, - total_tickets: usize, - total_winning_tickets: usize, - info: &mut LotteryInfo, - ) { - let rand_index = self.get_distinct_random(*index_last_winner, total_tickets); - let ticket_holders_mapper = self.ticket_holders(lottery_name); - - // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper - let winner_address = self.ticket_holders(lottery_name).get(rand_index); - let last_index_winner_address = self.ticket_holders(lottery_name).get(*index_last_winner); - - self.ticket_holders(lottery_name) - .set(rand_index, &last_index_winner_address); - self.ticket_holders(lottery_name) - .set(*index_last_winner, &winner_address); - - // distribute to the first place last. Laws of probability say that order doesn't matter. - // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times - // 1st place will get the spare money instead. - if *index_last_winner <= total_winning_tickets { - let prize = self.calculate_percentage_of( - &info.prize_pool, - &BigUint::from( - info.prize_distribution - .get(total_winning_tickets - *index_last_winner), - ), - ); - if prize > 0 { - self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); - - info.unawarded_amount -= prize; - } - } else { - // insert token in accumulated rewards first place - let first_place_winner = ticket_holders_mapper.get(*index_last_winner); - - self.assign_prize_to_winner( - info.token_identifier.clone(), - &info.unawarded_amount, - &first_place_winner, - ); - } - } - - fn assign_prize_to_winner( - &self, - token_id: EgldOrEsdtTokenIdentifier, - amount: &BigUint, - winner_id: &u64, - ) { - self.accumulated_rewards(&token_id, winner_id) - .update(|value| *value += amount); - self.user_accumulated_token_rewards(winner_id) - .insert(token_id); - } - - #[endpoint] - fn claim_rewards(&self, tokens: MultiValueEncoded) { - let caller = self.blockchain().get_caller(); - let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); - require!( - !self.user_accumulated_token_rewards(&caller_id).is_empty(), - "You have no rewards to claim" - ); - - let mut accumulated_egld_rewards = BigUint::zero(); - let mut accumulated_esdt_rewards = ManagedVec::::new(); - - // to save reviewers time, these 2 iterators have different generics, so it was not possible to make just 1 for loop - - if tokens.is_empty() { - // if wanted tokens were not specified claim all, and clear user_accumulated_token_rewards storage mapper - - let mut all_tokens: ManagedVec = - ManagedVec::new(); - - for token_id in self.user_accumulated_token_rewards(&caller_id).iter() { - require!( - !self.accumulated_rewards(&token_id, &caller_id).is_empty(), - "Token requested not available for claim" - ); - all_tokens.push(token_id); - } - - self.claim_rewards_user( - all_tokens, - &caller_id, - &mut accumulated_egld_rewards, - &mut accumulated_esdt_rewards, - ) - } else { - // otherwise claim just what was requested and remove those tokens from the user_accumulated_token_rewards storage mapper - - self.claim_rewards_user( - tokens.to_vec(), - &caller_id, - &mut accumulated_egld_rewards, - &mut accumulated_esdt_rewards, - ) - }; - if !accumulated_esdt_rewards.is_empty() { - self.tx() - .to(&caller) - .multi_esdt(accumulated_esdt_rewards) - .transfer(); - } - - if accumulated_egld_rewards > 0u64 { - self.tx() - .to(&caller) - .egld(accumulated_egld_rewards) - .transfer(); - } - } - - fn claim_rewards_user( - &self, - tokens: ManagedVec, - caller_id: &u64, - accumulated_egld_rewards: &mut BigUint, - accumulated_esdt_rewards: &mut ManagedVec, - ) { - for token_id in tokens.iter().rev() { - let _ = &self - .user_accumulated_token_rewards(caller_id) - .swap_remove(&token_id); - - self.prepare_token_for_claim( - token_id, - caller_id, - accumulated_egld_rewards, - accumulated_esdt_rewards, - ); - } - } - - fn prepare_token_for_claim( - &self, - token_id: EgldOrEsdtTokenIdentifier, - caller_id: &u64, - accumulated_egld_rewards: &mut BigUint, - accumulated_esdt_rewards: &mut ManagedVec, - ) { - let value = self.accumulated_rewards(&token_id, caller_id).take(); - if token_id.is_egld() { - *accumulated_egld_rewards += value; - } else { - accumulated_esdt_rewards.push(EsdtTokenPayment::new(token_id.unwrap_esdt(), 0, value)); - } - } - - fn clear_storage(&self, lottery_name: &ManagedBuffer) { - let mut ticket_holders_mapper = self.ticket_holders(lottery_name); - let current_ticket_number = ticket_holders_mapper.len(); - - for i in 1..=current_ticket_number { - let addr = ticket_holders_mapper.get(i); - self.number_of_entries_for_user(lottery_name, &addr).clear(); - } - - ticket_holders_mapper.clear(); - self.lottery_info(lottery_name).clear(); - self.lottery_whitelist(lottery_name).clear(); - self.total_winning_tickets(lottery_name).clear(); - self.index_last_winner(lottery_name).clear(); - self.burn_percentage_for_lottery(lottery_name).clear(); - } - - fn sum_array(&self, array: &ManagedVec) -> u32 { - let mut sum = 0; - - for item in array { - sum += item as u32; - } - - sum - } - - /// does not check if max - min >= amount, that is the caller's job - fn get_distinct_random(&self, min: usize, max: usize) -> usize { - let mut rand = RandomnessSource::new(); - rand.next_usize_in_range(min, max) - } - - fn calculate_percentage_of(&self, value: &BigUint, percentage: &BigUint) -> BigUint { - value * percentage / PERCENTAGE_TOTAL - } - - // storage - - #[view(getLotteryInfo)] - #[storage_mapper("lotteryInfo")] - fn lottery_info( - &self, - lottery_name: &ManagedBuffer, - ) -> SingleValueMapper>; - - #[view(getLotteryWhitelist)] - #[storage_mapper("lotteryWhitelist")] - fn lottery_whitelist(&self, lottery_name: &ManagedBuffer) -> UnorderedSetMapper; - - #[storage_mapper("ticketHolder")] - fn ticket_holders(&self, lottery_name: &ManagedBuffer) -> VecMapper; - - #[storage_mapper("accumulatedRewards")] - fn accumulated_rewards( - &self, - token_id: &EgldOrEsdtTokenIdentifier, - user_id: &u64, - ) -> SingleValueMapper; - - #[storage_mapper("totalWinning_tickets")] - fn total_winning_tickets(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; - - #[storage_mapper("indexLastWinner")] - fn index_last_winner(&self, lottery_name: &ManagedBuffer) -> SingleValueMapper; - - #[storage_mapper("accumulatedRewards")] - fn user_accumulated_token_rewards( - &self, - user_id: &u64, - ) -> UnorderedSetMapper; - - #[storage_mapper("numberOfEntriesForUser")] - fn number_of_entries_for_user( - &self, - lottery_name: &ManagedBuffer, - user_id: &u64, - ) -> SingleValueMapper; - - #[storage_mapper("addressToIdMapper")] - fn addres_to_id_mapper(&self) -> AddressToIdMapper; - - #[storage_mapper("burnPercentageForLottery")] - fn burn_percentage_for_lottery( - &self, - lottery_name: &ManagedBuffer, - ) -> SingleValueMapper; } diff --git a/contracts/examples/lottery-esdt/src/specific/award.rs b/contracts/examples/lottery-esdt/src/specific/award.rs new file mode 100644 index 0000000000..f95771a446 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/specific/award.rs @@ -0,0 +1,194 @@ +use crate::{ + constants::MAX_OPERATIONS, lottery_info::LotteryInfo, storage, utils, views, AwardingStatus, + Status, +}; +use multiversx_sc::imports::*; + +#[multiversx_sc::module] +pub trait AwardingModule: views::ViewsModule + storage::StorageModule + utils::UtilsModule { + #[endpoint] + fn determine_winner(&self, lottery_name: ManagedBuffer) -> AwardingStatus { + let sc_address = self.blockchain().get_sc_address(); + let sc_address_shard = self.blockchain().get_shard_of_address(&sc_address); + let caller = self.blockchain().get_caller(); + let caller_shard = self.blockchain().get_shard_of_address(&caller); + require!( + sc_address_shard != caller_shard, + "Caller needs to be on a remote shard" + ); + + match self.status(&lottery_name) { + Status::Inactive => sc_panic!("Lottery is inactive!"), + Status::Running => sc_panic!("Lottery is still running!"), + Status::Ended => self.handle_awarding(&lottery_name), + } + } + + fn handle_awarding(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { + if self.total_winning_tickets(lottery_name).is_empty() { + self.prepare_awarding(lottery_name); + } + self.distribute_prizes(lottery_name) + } + + fn prepare_awarding(&self, lottery_name: &ManagedBuffer) { + let mut info = self.lottery_info(lottery_name).get(); + let ticket_holders_mapper = self.ticket_holders(lottery_name); + let total_tickets = ticket_holders_mapper.len(); + + if total_tickets == 0 { + return; + } + + self.burn_prize_percentage(lottery_name, &mut info); + + // if there are less tickets than the distributed prize pool, + // the 1st place gets the leftover, maybe could split between the remaining + // but this is a rare case anyway and it's not worth the overhead + let total_winning_tickets = if total_tickets < info.prize_distribution.len() { + total_tickets + } else { + info.prize_distribution.len() + }; + + self.total_winning_tickets(lottery_name) + .set(total_winning_tickets); + self.index_last_winner(lottery_name).set(1); + + self.lottery_info(lottery_name).set(info); + } + + fn burn_prize_percentage( + &self, + lottery_name: &ManagedBuffer, + info: &mut LotteryInfo, + ) { + let burn_percentage = self.burn_percentage_for_lottery(lottery_name).get(); + if burn_percentage == 0 { + return; + } + + let burn_amount = self.calculate_percentage_of(&info.prize_pool, &burn_percentage); + + // Prevent crashing if the role was unset while the lottery was running + // The tokens will simply remain locked forever + let esdt_token_id = info.token_identifier.clone().unwrap_esdt(); + let roles = self.blockchain().get_esdt_local_roles(&esdt_token_id); + if roles.has_role(&EsdtLocalRole::Burn) { + self.send().esdt_local_burn(&esdt_token_id, 0, &burn_amount); + } + + info.prize_pool -= &burn_amount; + info.unawarded_amount -= burn_amount; + } + + fn distribute_prizes(&self, lottery_name: &ManagedBuffer) -> AwardingStatus { + let mut info = self.lottery_info(lottery_name).get(); + let ticket_holders_mapper = self.ticket_holders(lottery_name); + let total_tickets = ticket_holders_mapper.len(); + + let mut index_last_winner = self.index_last_winner(lottery_name).get(); + let total_winning_tickets = self.total_winning_tickets(lottery_name).get(); + require!( + index_last_winner <= total_winning_tickets, + "Awarding has ended" + ); + + let mut iterations = 0; + while index_last_winner <= total_winning_tickets && iterations < MAX_OPERATIONS { + self.award_winner( + lottery_name, + &index_last_winner, + total_tickets, + total_winning_tickets, + &mut info, + ); + index_last_winner += 1; + iterations += 1; + } + self.lottery_info(lottery_name).set(info); + self.index_last_winner(lottery_name).set(index_last_winner); + if index_last_winner > total_winning_tickets { + self.clear_storage(lottery_name); + return AwardingStatus::Finished; + } + AwardingStatus::Ongoing + } + + fn clear_storage(&self, lottery_name: &ManagedBuffer) { + let mut ticket_holders_mapper = self.ticket_holders(lottery_name); + let current_ticket_number = ticket_holders_mapper.len(); + + for i in 1..=current_ticket_number { + let addr = ticket_holders_mapper.get(i); + self.number_of_entries_for_user(lottery_name, &addr).clear(); + } + + ticket_holders_mapper.clear(); + self.lottery_info(lottery_name).clear(); + self.lottery_whitelist(lottery_name).clear(); + self.total_winning_tickets(lottery_name).clear(); + self.index_last_winner(lottery_name).clear(); + self.burn_percentage_for_lottery(lottery_name).clear(); + } + + fn award_winner( + &self, + lottery_name: &ManagedBuffer, + index_last_winner: &usize, + total_tickets: usize, + total_winning_tickets: usize, + info: &mut LotteryInfo, + ) { + let rand_index = self.get_distinct_random(*index_last_winner, total_tickets); + let ticket_holders_mapper = self.ticket_holders(lottery_name); + + // swap indexes of the winner addresses - we are basically bringing the winners in the first indexes of the mapper + let winner_address = self.ticket_holders(lottery_name).get(rand_index); + let last_index_winner_address = self.ticket_holders(lottery_name).get(*index_last_winner); + + self.ticket_holders(lottery_name) + .set(rand_index, &last_index_winner_address); + self.ticket_holders(lottery_name) + .set(*index_last_winner, &winner_address); + + // distribute to the first place last. Laws of probability say that order doesn't matter. + // this is done to mitigate the effects of BigUint division leading to "spare" prize money being left out at times + // 1st place will get the spare money instead. + if *index_last_winner <= total_winning_tickets { + let prize = self.calculate_percentage_of( + &info.prize_pool, + &BigUint::from( + info.prize_distribution + .get(total_winning_tickets - *index_last_winner), + ), + ); + if prize > 0 { + self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); + + info.unawarded_amount -= prize; + } + } else { + // insert token in accumulated rewards first place + let first_place_winner = ticket_holders_mapper.get(*index_last_winner); + + self.assign_prize_to_winner( + info.token_identifier.clone(), + &info.unawarded_amount, + &first_place_winner, + ); + } + } + + fn assign_prize_to_winner( + &self, + token_id: EgldOrEsdtTokenIdentifier, + amount: &BigUint, + winner_id: &u64, + ) { + self.accumulated_rewards(&token_id, winner_id) + .update(|value| *value += amount); + self.user_accumulated_token_rewards(winner_id) + .insert(token_id); + } +} diff --git a/contracts/examples/lottery-esdt/src/awarding_status.rs b/contracts/examples/lottery-esdt/src/specific/awarding_status.rs similarity index 100% rename from contracts/examples/lottery-esdt/src/awarding_status.rs rename to contracts/examples/lottery-esdt/src/specific/awarding_status.rs diff --git a/contracts/examples/lottery-esdt/src/specific/buy.rs b/contracts/examples/lottery-esdt/src/specific/buy.rs new file mode 100644 index 0000000000..5321b8e25c --- /dev/null +++ b/contracts/examples/lottery-esdt/src/specific/buy.rs @@ -0,0 +1,63 @@ +use multiversx_sc::imports::*; + +use crate::basics::{storage, views}; + +use super::status::Status; + +#[multiversx_sc::module] +pub trait BuyTicketModule: storage::StorageModule + views::ViewsModule { + #[endpoint] + #[payable("*")] + fn buy_ticket(&self, lottery_name: ManagedBuffer) { + let (token_identifier, payment) = self.call_value().egld_or_single_fungible_esdt(); + + match self.status(&lottery_name) { + Status::Inactive => sc_panic!("Lottery is currently inactive."), + Status::Running => { + self.update_after_buy_ticket(&lottery_name, &token_identifier, &payment) + }, + Status::Ended => { + sc_panic!("Lottery entry period has ended! Awaiting winner announcement.") + }, + }; + } + + fn update_after_buy_ticket( + &self, + lottery_name: &ManagedBuffer, + token_identifier: &EgldOrEsdtTokenIdentifier, + payment: &BigUint, + ) { + let info_mapper = self.lottery_info(lottery_name); + let mut info = info_mapper.get(); + let caller = self.blockchain().get_caller(); + let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); + let whitelist = self.lottery_whitelist(lottery_name); + + require!( + whitelist.is_empty() || whitelist.contains(&caller_id), + "You are not allowed to participate in this lottery!" + ); + require!( + token_identifier == &info.token_identifier && payment == &info.ticket_price, + "Wrong ticket fee!" + ); + + let entries_mapper = self.number_of_entries_for_user(lottery_name, &caller_id); + let mut entries = entries_mapper.get(); + require!( + entries < info.max_entries_per_user, + "Ticket limit exceeded for this lottery!" + ); + + self.ticket_holders(lottery_name).push(&caller_id); + + entries += 1; + info.tickets_left -= 1; + info.prize_pool += &info.ticket_price; + info.unawarded_amount += &info.ticket_price; + + entries_mapper.set(entries); + info_mapper.set(&info); + } +} diff --git a/contracts/examples/lottery-esdt/src/specific/claim.rs b/contracts/examples/lottery-esdt/src/specific/claim.rs new file mode 100644 index 0000000000..9034e37182 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/specific/claim.rs @@ -0,0 +1,101 @@ +use multiversx_sc::imports::*; + +use crate::basics::storage; + +#[multiversx_sc::module] +pub trait ClaimModule: storage::StorageModule { + #[endpoint] + fn claim_rewards(&self, tokens: MultiValueEncoded) { + let caller = self.blockchain().get_caller(); + let caller_id = self.addres_to_id_mapper().get_id_or_insert(&caller); + require!( + !self.user_accumulated_token_rewards(&caller_id).is_empty(), + "You have no rewards to claim" + ); + + let mut accumulated_egld_rewards = BigUint::zero(); + let mut accumulated_esdt_rewards = ManagedVec::::new(); + + // to save reviewers time, these 2 iterators have different generics, so it was not possible to make just 1 for loop + + if tokens.is_empty() { + // if wanted tokens were not specified claim all, and clear user_accumulated_token_rewards storage mapper + + let mut all_tokens: ManagedVec = + ManagedVec::new(); + + for token_id in self.user_accumulated_token_rewards(&caller_id).iter() { + require!( + !self.accumulated_rewards(&token_id, &caller_id).is_empty(), + "Token requested not available for claim" + ); + all_tokens.push(token_id); + } + + self.claim_rewards_user( + all_tokens, + &caller_id, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ) + } else { + // otherwise claim just what was requested and remove those tokens from the user_accumulated_token_rewards storage mapper + + self.claim_rewards_user( + tokens.to_vec(), + &caller_id, + &mut accumulated_egld_rewards, + &mut accumulated_esdt_rewards, + ) + }; + if !accumulated_esdt_rewards.is_empty() { + self.tx() + .to(&caller) + .multi_esdt(accumulated_esdt_rewards) + .transfer(); + } + + if accumulated_egld_rewards > 0u64 { + self.tx() + .to(&caller) + .egld(accumulated_egld_rewards) + .transfer(); + } + } + + fn claim_rewards_user( + &self, + tokens: ManagedVec, + caller_id: &u64, + accumulated_egld_rewards: &mut BigUint, + accumulated_esdt_rewards: &mut ManagedVec, + ) { + for token_id in tokens.iter().rev() { + let _ = &self + .user_accumulated_token_rewards(caller_id) + .swap_remove(&token_id); + + self.prepare_token_for_claim( + token_id, + caller_id, + accumulated_egld_rewards, + accumulated_esdt_rewards, + ); + } + } + + fn prepare_token_for_claim( + &self, + token_id: EgldOrEsdtTokenIdentifier, + caller_id: &u64, + accumulated_egld_rewards: &mut BigUint, + accumulated_esdt_rewards: &mut ManagedVec, + ) { + let value = self.accumulated_rewards(&token_id, caller_id).take(); + if token_id.is_egld() { + *accumulated_egld_rewards += value; + } else { + accumulated_esdt_rewards.push(EsdtTokenPayment::new(token_id.unwrap_esdt(), 0, value)); + } + } +} diff --git a/contracts/examples/lottery-esdt/src/lottery_info.rs b/contracts/examples/lottery-esdt/src/specific/lottery_info.rs similarity index 100% rename from contracts/examples/lottery-esdt/src/lottery_info.rs rename to contracts/examples/lottery-esdt/src/specific/lottery_info.rs diff --git a/contracts/examples/lottery-esdt/src/specific/mod.rs b/contracts/examples/lottery-esdt/src/specific/mod.rs new file mode 100644 index 0000000000..1405caa87b --- /dev/null +++ b/contracts/examples/lottery-esdt/src/specific/mod.rs @@ -0,0 +1,7 @@ +pub mod award; +pub mod awarding_status; +pub mod buy; +pub mod claim; +pub mod lottery_info; +pub mod setup; +pub mod status; diff --git a/contracts/examples/lottery-esdt/src/specific/setup.rs b/contracts/examples/lottery-esdt/src/specific/setup.rs new file mode 100644 index 0000000000..55b1483c89 --- /dev/null +++ b/contracts/examples/lottery-esdt/src/specific/setup.rs @@ -0,0 +1,138 @@ +use multiversx_sc::imports::*; + +use crate::{ + basics::{ + constants::{MAX_TICKETS, PERCENTAGE_TOTAL, THIRTY_DAYS_IN_SECONDS}, + storage, utils, views, + }, + specific::{lottery_info::LotteryInfo, status::Status}, +}; + +#[multiversx_sc::module] +pub trait SetupModule: storage::StorageModule + views::ViewsModule + utils::UtilsModule { + #[allow_multiple_var_args] + #[endpoint(createLotteryPool)] + fn create_lottery_pool( + &self, + lottery_name: ManagedBuffer, + token_identifier: EgldOrEsdtTokenIdentifier, + ticket_price: BigUint, + opt_total_tickets: Option, + opt_deadline: Option, + opt_max_entries_per_user: Option, + opt_prize_distribution: ManagedOption>, + opt_whitelist: ManagedOption>, + opt_burn_percentage: OptionalValue, + ) { + self.start_lottery( + lottery_name, + token_identifier, + ticket_price, + opt_total_tickets, + opt_deadline, + opt_max_entries_per_user, + opt_prize_distribution, + opt_whitelist, + opt_burn_percentage, + ); + } + + #[allow_multiple_var_args] + #[allow(clippy::too_many_arguments)] + fn start_lottery( + &self, + lottery_name: ManagedBuffer, + token_identifier: EgldOrEsdtTokenIdentifier, + ticket_price: BigUint, + opt_total_tickets: Option, + opt_deadline: Option, + opt_max_entries_per_user: Option, + opt_prize_distribution: ManagedOption>, + opt_whitelist: ManagedOption>, + opt_burn_percentage: OptionalValue, + ) { + require!(!lottery_name.is_empty(), "Name can't be empty!"); + + let timestamp = self.blockchain().get_block_timestamp(); + let total_tickets = opt_total_tickets.unwrap_or(MAX_TICKETS); + let deadline = opt_deadline.unwrap_or(timestamp + THIRTY_DAYS_IN_SECONDS); + let max_entries_per_user = opt_max_entries_per_user.unwrap_or(MAX_TICKETS); + let prize_distribution = opt_prize_distribution + .unwrap_or_else(|| ManagedVec::from_single_item(PERCENTAGE_TOTAL as u8)); + + require!( + total_tickets > prize_distribution.len(), + "Number of winners should be smaller than the number of available tickets" + ); + require!( + self.status(&lottery_name) == Status::Inactive, + "Lottery is already active!" + ); + require!(token_identifier.is_valid(), "Invalid token name provided!"); + require!(ticket_price > 0, "Ticket price must be higher than 0!"); + require!( + total_tickets > 0, + "Must have more than 0 tickets available!" + ); + require!( + total_tickets <= MAX_TICKETS, + "Only 800 or less total tickets per lottery are allowed!" + ); + require!(deadline > timestamp, "Deadline can't be in the past!"); + require!( + deadline <= timestamp + THIRTY_DAYS_IN_SECONDS, + "Deadline can't be later than 30 days from now!" + ); + require!( + max_entries_per_user > 0, + "Must have more than 0 max entries per user!" + ); + require!( + self.sum_array(&prize_distribution) == PERCENTAGE_TOTAL, + "Prize distribution must add up to exactly 100(%)!" + ); + + match opt_burn_percentage { + OptionalValue::Some(burn_percentage) => { + require!(!token_identifier.is_egld(), "EGLD can't be burned!"); + + let roles = self + .blockchain() + .get_esdt_local_roles(&token_identifier.clone().unwrap_esdt()); + require!( + roles.has_role(&EsdtLocalRole::Burn), + "The contract can't burn the selected token!" + ); + + require!( + burn_percentage < PERCENTAGE_TOTAL, + "Invalid burn percentage!" + ); + self.burn_percentage_for_lottery(&lottery_name) + .set(burn_percentage); + }, + OptionalValue::None => {}, + } + + if let Some(whitelist) = opt_whitelist.as_option() { + let mut mapper = self.lottery_whitelist(&lottery_name); + for addr in &*whitelist { + let addr_id = self.addres_to_id_mapper().get_id_or_insert(&addr); + mapper.insert(addr_id); + } + } + + let info = LotteryInfo { + token_identifier, + ticket_price, + tickets_left: total_tickets, + deadline, + max_entries_per_user, + prize_distribution, + prize_pool: BigUint::zero(), + unawarded_amount: BigUint::zero(), + }; + + self.lottery_info(&lottery_name).set(&info); + } +} diff --git a/contracts/examples/lottery-esdt/src/status.rs b/contracts/examples/lottery-esdt/src/specific/status.rs similarity index 100% rename from contracts/examples/lottery-esdt/src/status.rs rename to contracts/examples/lottery-esdt/src/specific/status.rs diff --git a/contracts/examples/lottery-esdt/wasm/src/lib.rs b/contracts/examples/lottery-esdt/wasm/src/lib.rs index b5f5528b54..468182c673 100644 --- a/contracts/examples/lottery-esdt/wasm/src/lib.rs +++ b/contracts/examples/lottery-esdt/wasm/src/lib.rs @@ -18,13 +18,13 @@ multiversx_sc_wasm_adapter::endpoints! { lottery_esdt ( init => init - createLotteryPool => create_lottery_pool - buy_ticket => buy_ticket determine_winner => determine_winner status => status - claim_rewards => claim_rewards getLotteryInfo => lottery_info getLotteryWhitelist => lottery_whitelist + claim_rewards => claim_rewards + buy_ticket => buy_ticket + createLotteryPool => create_lottery_pool ) } From a8b4ca7711a20690e36d5a69c7defe822599f904 Mon Sep 17 00:00:00 2001 From: Alin Cruceat Date: Mon, 16 Dec 2024 13:17:11 +0200 Subject: [PATCH 12/12] more refactoring --- .../lottery-esdt/src/specific/award.rs | 10 +++-- .../lottery-esdt/src/specific/claim.rs | 41 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/contracts/examples/lottery-esdt/src/specific/award.rs b/contracts/examples/lottery-esdt/src/specific/award.rs index f95771a446..061d2e0859 100644 --- a/contracts/examples/lottery-esdt/src/specific/award.rs +++ b/contracts/examples/lottery-esdt/src/specific/award.rs @@ -163,11 +163,13 @@ pub trait AwardingModule: views::ViewsModule + storage::StorageModule + utils::U .get(total_winning_tickets - *index_last_winner), ), ); - if prize > 0 { - self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); - - info.unawarded_amount -= prize; + if prize == 0 { + return; } + + self.assign_prize_to_winner(info.token_identifier.clone(), &prize, &winner_address); + + info.unawarded_amount -= prize; } else { // insert token in accumulated rewards first place let first_place_winner = ticket_holders_mapper.get(*index_last_winner); diff --git a/contracts/examples/lottery-esdt/src/specific/claim.rs b/contracts/examples/lottery-esdt/src/specific/claim.rs index 9034e37182..48804fae20 100644 --- a/contracts/examples/lottery-esdt/src/specific/claim.rs +++ b/contracts/examples/lottery-esdt/src/specific/claim.rs @@ -20,24 +20,11 @@ pub trait ClaimModule: storage::StorageModule { if tokens.is_empty() { // if wanted tokens were not specified claim all, and clear user_accumulated_token_rewards storage mapper - - let mut all_tokens: ManagedVec = - ManagedVec::new(); - - for token_id in self.user_accumulated_token_rewards(&caller_id).iter() { - require!( - !self.accumulated_rewards(&token_id, &caller_id).is_empty(), - "Token requested not available for claim" - ); - all_tokens.push(token_id); - } - - self.claim_rewards_user( - all_tokens, + self.handle_claim_with_unspecified_tokens( &caller_id, &mut accumulated_egld_rewards, &mut accumulated_esdt_rewards, - ) + ); } else { // otherwise claim just what was requested and remove those tokens from the user_accumulated_token_rewards storage mapper @@ -63,6 +50,30 @@ pub trait ClaimModule: storage::StorageModule { } } + fn handle_claim_with_unspecified_tokens( + &self, + caller_id: &u64, + accumulated_egld_rewards: &mut BigUint, + accumulated_esdt_rewards: &mut ManagedVec, + ) { + let mut all_tokens: ManagedVec = ManagedVec::new(); + + for token_id in self.user_accumulated_token_rewards(caller_id).iter() { + require!( + !self.accumulated_rewards(&token_id, caller_id).is_empty(), + "Token requested not available for claim" + ); + all_tokens.push(token_id); + } + + self.claim_rewards_user( + all_tokens, + caller_id, + accumulated_egld_rewards, + accumulated_esdt_rewards, + ) + } + fn claim_rewards_user( &self, tokens: ManagedVec,