diff --git a/contracts/examples/digital-cash/scenarios/claim-egld.scen.json b/contracts/examples/digital-cash/scenarios/claim-egld.scen.json index 085287a127..c9c70411c9 100644 --- a/contracts/examples/digital-cash/scenarios/claim-egld.scen.json +++ b/contracts/examples/digital-cash/scenarios/claim-egld.scen.json @@ -138,7 +138,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -149,11 +153,21 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10", - "str:collectedFees": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:collectedFees|nested:str:EGLD": "10" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/claim-esdt.scen.json b/contracts/examples/digital-cash/scenarios/claim-esdt.scen.json index d3010b1ed1..8c0931423b 100644 --- a/contracts/examples/digital-cash/scenarios/claim-esdt.scen.json +++ b/contracts/examples/digital-cash/scenarios/claim-esdt.scen.json @@ -137,7 +137,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -148,11 +152,21 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10", - "str:collectedFees": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:collectedFees|nested:str:EGLD": "10" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/claim-fees.scen.json b/contracts/examples/digital-cash/scenarios/claim-fees.scen.json index 0b9c11b9d5..78611be938 100644 --- a/contracts/examples/digital-cash/scenarios/claim-fees.scen.json +++ b/contracts/examples/digital-cash/scenarios/claim-fees.scen.json @@ -73,7 +73,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -84,10 +88,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/claim-multi-esdt.scen.json b/contracts/examples/digital-cash/scenarios/claim-multi-esdt.scen.json index 85df0810de..301d037ab7 100644 --- a/contracts/examples/digital-cash/scenarios/claim-multi-esdt.scen.json +++ b/contracts/examples/digital-cash/scenarios/claim-multi-esdt.scen.json @@ -135,7 +135,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -146,11 +150,21 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10", - "str:collectedFees": "30" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:collectedFees|nested:str:EGLD": "30" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/forward.scen.json b/contracts/examples/digital-cash/scenarios/forward.scen.json index ad9f41413d..44721e7f2f 100644 --- a/contracts/examples/digital-cash/scenarios/forward.scen.json +++ b/contracts/examples/digital-cash/scenarios/forward.scen.json @@ -11,6 +11,7 @@ "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", + "egldValue": "0", "function": "forward", "arguments": [ "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", @@ -54,10 +55,11 @@ }, { "step": "scCall", - "id": "forward-ok", + "id": "forward-without-fees-ok", "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", + "egldValue": "0", "function": "forward", "arguments": [ "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", @@ -81,7 +83,7 @@ "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", - "egldValue": "1,000", + "egldValue": "500", "function": "depositFees", "arguments": [ "0x8dc17613990e9b7476401a36d112d1a4d31190dec21e7e9a3c933872a27613ee" @@ -100,11 +102,37 @@ }, { "step": "scCall", - "id": "forward-ok", + "id": "forward-with-fees-fail", + "tx": { + "from": "address:acc2", + "to": "sc:the_digital_cash_contract", + "function": "forward", + "egldValue": "500", + "arguments": [ + "0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d", + "0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d", + "0x1ac4f6d4d45836d97ffeda83a66aaea7631a3bb3d4063421ccb2b9de9485bdb4c9bd6e44e003f6a9c9eb74379467238204ff579471d203b1878c3f1530592a02" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:invalid depositor address", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "forward-with-fees-ok", "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", "function": "forward", + "egldValue": "500", "arguments": [ "0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d", "0x8dc17613990e9b7476401a36d112d1a4d31190dec21e7e9a3c933872a27613ee", @@ -142,7 +170,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -153,7 +185,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x8dc17613990e9b7476401a36d112d1a4d31190dec21e7e9a3c933872a27613ee": { @@ -164,11 +200,21 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10", - "str:collectedFees": "40" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:collectedFees|nested:str:EGLD": "40" }, "code": "file:../output/digital-cash.wasm" }, @@ -178,7 +224,7 @@ "storage": {} }, "address:acc2": { - "nonce": "8", + "nonce": "9", "balance": "997,000", "esdt": { "str:CASHTOKEN-123456": "50" diff --git a/contracts/examples/digital-cash/scenarios/fund-egld-and-esdt.scen.json b/contracts/examples/digital-cash/scenarios/fund-egld-and-esdt.scen.json index 7263a472e0..b30b5a2b97 100644 --- a/contracts/examples/digital-cash/scenarios/fund-egld-and-esdt.scen.json +++ b/contracts/examples/digital-cash/scenarios/fund-egld-and-esdt.scen.json @@ -91,10 +91,20 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, @@ -197,7 +207,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -208,10 +222,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, @@ -389,7 +413,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -400,7 +428,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -411,10 +443,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-egld.scen.json b/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-egld.scen.json new file mode 100644 index 0000000000..83e5d386e5 --- /dev/null +++ b/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-egld.scen.json @@ -0,0 +1,126 @@ +{ + "name": "pay-fee-adn-fund-egld", + "steps": [ + { + "step": "externalSteps", + "path": "whitelist-blacklist-fee-tokens.scen.json" + }, + { + "step": "scCall", + "id": "pay-fee-and-fund-egld-fail", + "tx": { + "from": "address:acc3", + "to": "sc:the_digital_cash_contract", + "egldValue": "10", + "function": "payFeeAndFundEGLD", + "arguments": [ + "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", + "u64:100" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:payment not covering fees", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "pay-fee-and-fund-egld-success", + "tx": { + "from": "address:acc3", + "to": "sc:the_digital_cash_contract", + "egldValue": "1,000", + "function": "payFeeAndFundEGLD", + "arguments": [ + "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", + "u64:100" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:the_digital_cash_contract": { + "nonce": "0", + "balance": "1,000", + "storage": { + "str:deposit|0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60": { + "0-depositor_address": "address:acc3", + "1-esdt_funds": "u32:0", + "2-egld_funds": "biguint:990", + "3-valability": "u64:100", + "4-expiration_round": "u64:16", + "5-fees": { + "0-num_token_to_transfer": "u32:1", + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:10" + } + } + }, + "str:fee|nested:str:EGLD": "10", + "str:fee|nested:str:CASHTOKEN-778899": "3", + "str:whitelistedFeeTokens|str:.len": "2", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:whitelistedFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.len": "3", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:allTimeFeeTokens|str:.item|u32:3": "str:ESDT-778899", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.index|nested:str:ESDT-778899": "3" + }, + "code": "file:../output/digital-cash.wasm" + }, + "address:acc1": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, + "address:acc2": { + "nonce": "0", + "balance": "1,000,000", + "esdt": { + "str:CASHTOKEN-123456": "100" + }, + "storage": {} + }, + "address:acc3": { + "nonce": "2", + "balance": "999000", + "esdt": { + "str:CASHTOKEN-112233": "100", + "str:CASHTOKEN-445566": "100", + "str:CASHTOKEN-778899": "100" + }, + "storage": {} + }, + "address:digital_cash_owner_address": { + "nonce": "6", + "balance": "0", + "storage": {} + } + } + } + ] +} diff --git a/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-esdt.scen.json b/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-esdt.scen.json new file mode 100644 index 0000000000..6848eb9cd1 --- /dev/null +++ b/contracts/examples/digital-cash/scenarios/pay-fee-and-fund-esdt.scen.json @@ -0,0 +1,148 @@ +{ + "name": "pay-fee-and-fund-esdt", + "steps": [ + { + "step": "externalSteps", + "path": "whitelist-blacklist-fee-tokens.scen.json" + }, + { + "step": "scCall", + "id": "pay-fee-and-fund-esdt-fail", + "tx": { + "from": "address:acc3", + "to": "sc:the_digital_cash_contract", + "function": "payFeeAndFundESDT", + "esdtValue": [ + { + "tokenIdentifier": "str:CASHTOKEN-445566", + "value": "50" + }, + { + "tokenIdentifier": "str:CASHTOKEN-112233", + "value": "50" + } + ], + "arguments": [ + "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", + "u64:100" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:invalid fee toke provided", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "pay-fee-and-fund-esdt-success", + "tx": { + "from": "address:acc3", + "to": "sc:the_digital_cash_contract", + "function": "payFeeAndFundESDT", + "esdtValue": [ + { + "tokenIdentifier": "str:CASHTOKEN-778899", + "value": "50" + }, + { + "tokenIdentifier": "str:CASHTOKEN-112233", + "value": "50" + } + ], + "arguments": [ + "0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60", + "u64:100" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:the_digital_cash_contract": { + "nonce": "0", + "balance": "0", + "esdt": { + "str:CASHTOKEN-112233": "50", + "str:CASHTOKEN-778899": "50" + }, + "storage": { + "str:deposit|0xdb474a3a065d3f0c0a62ae680ef6435e48eb482899d2ae30ff7a3a4b0ef19c60": { + "0-depositor_address": "address:acc3", + "1-esdt_funds": "u32:1|nested:str:CASHTOKEN-112233|u64:0|biguint:50", + "2-egld_funds": "biguint:0", + "3-valability": "u64:100", + "4-expiration_round": "u64:16", + "5-fees": { + "0-num_token_to_transfer": "u32:1", + "1-value": { + "0-tokenIdentifier": "nested:str:CASHTOKEN-778899", + "1-nonce": "u64:0", + "2-amount": "biguint:50" + } + } + }, + "str:fee|nested:str:EGLD": "10", + "str:fee|nested:str:CASHTOKEN-778899": "3", + "str:whitelistedFeeTokens|str:.len": "2", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:whitelistedFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.len": "3", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:allTimeFeeTokens|str:.item|u32:3": "str:ESDT-778899", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.index|nested:str:ESDT-778899": "3" + }, + "code": "file:../output/digital-cash.wasm" + }, + "address:acc1": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, + "address:acc2": { + "nonce": "0", + "balance": "1,000,000", + "esdt": { + "str:CASHTOKEN-123456": "100" + }, + "storage": {} + }, + "address:acc3": { + "nonce": "2", + "balance": "1,000,000", + "esdt": { + "str:CASHTOKEN-112233": "50", + "str:CASHTOKEN-445566": "100", + "str:CASHTOKEN-778899": "50" + }, + "storage": {} + }, + "address:digital_cash_owner_address": { + "nonce": "6", + "balance": "0", + "storage": {} + } + } + } + ] +} diff --git a/contracts/examples/digital-cash/scenarios/set-accounts.scen.json b/contracts/examples/digital-cash/scenarios/set-accounts.scen.json index 0c742a688d..f010d40595 100644 --- a/contracts/examples/digital-cash/scenarios/set-accounts.scen.json +++ b/contracts/examples/digital-cash/scenarios/set-accounts.scen.json @@ -44,7 +44,8 @@ "from": "address:digital_cash_owner_address", "contractCode": "file:../output/digital-cash.wasm", "arguments": [ - "10" + "10", + "str:EGLD" ], "gasLimit": "5,000,000", "gasPrice": "0" @@ -63,7 +64,13 @@ "nonce": "0", "balance": "0", "storage": { - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/whitelist-blacklist-fee-tokens.scen.json b/contracts/examples/digital-cash/scenarios/whitelist-blacklist-fee-tokens.scen.json new file mode 100644 index 0000000000..bd9f1afd1a --- /dev/null +++ b/contracts/examples/digital-cash/scenarios/whitelist-blacklist-fee-tokens.scen.json @@ -0,0 +1,176 @@ +{ + "name": "whitelist-blacklist-fee-token", + "steps": [ + { + "step": "externalSteps", + "path": "set-accounts.scen.json" + }, + { + "step": "scCall", + "id": "whitelist-fail", + "tx": { + "from": "address:digital_cash_owner_address", + "to": "sc:the_digital_cash_contract", + "function": "whitelistFeeToken", + "arguments": [ + "biguint:10", + "str:EGLD" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:Token already whitelisted", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "whitelist-success-1", + "tx": { + "from": "address:digital_cash_owner_address", + "to": "sc:the_digital_cash_contract", + "function": "whitelistFeeToken", + "arguments": [ + "3", + "str:CASHTOKEN-778899" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "whitelist-success-2", + "tx": { + "from": "address:digital_cash_owner_address", + "to": "sc:the_digital_cash_contract", + "function": "whitelistFeeToken", + "arguments": [ + "5", + "str:ESDT-778899" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "blacklist-fail", + "tx": { + "from": "address:digital_cash_owner_address", + "to": "sc:the_digital_cash_contract", + "function": "blacklistFeeToken", + "arguments": [ + "str:ESDT-000000" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "4", + "message": "str:Token is not whitelisted", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "scCall", + "id": "blacklist-success", + "tx": { + "from": "address:digital_cash_owner_address", + "to": "sc:the_digital_cash_contract", + "function": "blacklistFeeToken", + "arguments": [ + "str:ESDT-778899" + ], + "gasLimit": "500,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "0", + "message": "", + "logs": "*", + "gas": "*", + "refund": "*" + } + }, + { + "step": "checkState", + "accounts": { + "sc:the_digital_cash_contract": { + "nonce": "0", + "balance": "0", + "storage": { + "str:fee|nested:str:EGLD": "10", + "str:fee|nested:str:CASHTOKEN-778899": "3", + "str:whitelistedFeeTokens|str:.len": "2", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:whitelistedFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.len": "3", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.item|u32:2": "str:CASHTOKEN-778899", + "str:allTimeFeeTokens|str:.item|u32:3": "str:ESDT-778899", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.index|nested:str:CASHTOKEN-778899": "2", + "str:allTimeFeeTokens|str:.index|nested:str:ESDT-778899": "3" + }, + "code": "file:../output/digital-cash.wasm" + }, + "address:acc1": { + "nonce": "0", + "balance": "1,000,000", + "storage": {} + }, + "address:acc2": { + "nonce": "0", + "balance": "1,000,000", + "esdt": { + "str:CASHTOKEN-123456": "100" + }, + "storage": {} + }, + "address:acc3": { + "nonce": "0", + "balance": "1,000,000", + "esdt": { + "str:CASHTOKEN-112233": "100", + "str:CASHTOKEN-445566": "100", + "str:CASHTOKEN-778899": "100" + }, + "storage": {} + }, + "address:digital_cash_owner_address": { + "nonce": "6", + "balance": "0", + "storage": {} + } + } + } + ] +} diff --git a/contracts/examples/digital-cash/scenarios/withdraw-egld.scen.json b/contracts/examples/digital-cash/scenarios/withdraw-egld.scen.json index e89119fb44..5c4182a164 100644 --- a/contracts/examples/digital-cash/scenarios/withdraw-egld.scen.json +++ b/contracts/examples/digital-cash/scenarios/withdraw-egld.scen.json @@ -7,7 +7,7 @@ }, { "step": "scCall", - "id": "withdraw-esdt-1", + "id": "withdraw-egld-fail-1", "tx": { "from": "address:acc1", "to": "sc:the_digital_cash_contract", @@ -48,7 +48,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -59,7 +63,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -70,10 +78,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, @@ -109,7 +127,7 @@ }, { "step": "scCall", - "id": "withdraw-egld-2", + "id": "withdraw-egld-fail-2", "tx": { "from": "address:acc1", "to": "sc:the_digital_cash_contract", @@ -141,7 +159,7 @@ }, { "step": "scCall", - "id": "withdraw-egld-3", + "id": "withdraw-egld-success", "tx": { "from": "address:acc1", "to": "sc:the_digital_cash_contract", @@ -182,7 +200,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -193,10 +215,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/withdraw-esdt.scen.json b/contracts/examples/digital-cash/scenarios/withdraw-esdt.scen.json index 77a8fbe9c0..07d7c821e4 100644 --- a/contracts/examples/digital-cash/scenarios/withdraw-esdt.scen.json +++ b/contracts/examples/digital-cash/scenarios/withdraw-esdt.scen.json @@ -7,7 +7,7 @@ }, { "step": "scCall", - "id": "withdraw-esdt-1", + "id": "withdraw-esdt-fail-1", "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", @@ -48,7 +48,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -59,7 +63,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -70,10 +78,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, @@ -109,7 +127,7 @@ }, { "step": "scCall", - "id": "withdraw-esdt-2", + "id": "withdraw-esdt-fail-2", "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", @@ -141,7 +159,7 @@ }, { "step": "scCall", - "id": "withdraw-esdt-3", + "id": "withdraw-esdt-success", "tx": { "from": "address:acc2", "to": "sc:the_digital_cash_contract", @@ -181,7 +199,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -192,10 +214,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/scenarios/withdraw-multi-esdt.scen.json b/contracts/examples/digital-cash/scenarios/withdraw-multi-esdt.scen.json index 7f1a96e2ea..fae381c069 100644 --- a/contracts/examples/digital-cash/scenarios/withdraw-multi-esdt.scen.json +++ b/contracts/examples/digital-cash/scenarios/withdraw-multi-esdt.scen.json @@ -48,7 +48,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -59,7 +63,11 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x885532043a061e0c779e4064b85193f72cffd22c5bcc208c209128e60f21bf0d": { @@ -70,10 +78,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:3", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, @@ -179,7 +197,11 @@ "4-expiration_round": "u64:10", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, "str:deposit|0x487bd4010b50c24a02018345fe5171edf4182e6294325382c75ef4c4409f01bd": { @@ -190,10 +212,20 @@ "4-expiration_round": "u64:16", "5-fees": { "0-num_token_to_transfer": "u32:1", - "1-value": "biguint:1,000" + "1-value": { + "0-tokenIdentifier": "nested:str:EGLD", + "1-nonce": "u64:0", + "2-amount": "biguint:1,000" + } } }, - "str:fee": "10" + "str:fee|nested:str:EGLD": "10", + "str:whitelistedFeeTokens|str:.len": "1", + "str:whitelistedFeeTokens|str:.item|u32:1": "str:EGLD", + "str:whitelistedFeeTokens|str:.index|nested:str:EGLD": "1", + "str:allTimeFeeTokens|str:.len": "1", + "str:allTimeFeeTokens|str:.item|u32:1": "str:EGLD", + "str:allTimeFeeTokens|str:.index|nested:str:EGLD": "1" }, "code": "file:../output/digital-cash.wasm" }, diff --git a/contracts/examples/digital-cash/src/constants.rs b/contracts/examples/digital-cash/src/constants.rs new file mode 100644 index 0000000000..275e6bc702 --- /dev/null +++ b/contracts/examples/digital-cash/src/constants.rs @@ -0,0 +1,5 @@ +pub const NON_EXISTENT_KEY_ERR_MSG: &[u8] = b"non-existent key"; +pub const FEES_NOT_COVERED_ERR_MSG: &[u8] = b"fees not covered"; +pub const CANNOT_DEPOSIT_FUNDS_ERR_MSG: &[u8] = + b"cannot deposit funds without covering the fee cost first"; +pub const SECONDS_PER_ROUND: u64 = 6; diff --git a/contracts/examples/digital-cash/src/deposit_info.rs b/contracts/examples/digital-cash/src/deposit_info.rs index b156275aa7..2838d5de84 100644 --- a/contracts/examples/digital-cash/src/deposit_info.rs +++ b/contracts/examples/digital-cash/src/deposit_info.rs @@ -25,8 +25,8 @@ where } } -#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi, Default)] +#[derive(NestedEncode, NestedDecode, TopEncode, TopDecode, TypeAbi)] pub struct Fee { pub num_token_to_transfer: usize, - pub value: BigUint, + pub value: EgldOrEsdtTokenPayment, } diff --git a/contracts/examples/digital-cash/src/digital_cash.rs b/contracts/examples/digital-cash/src/digital_cash.rs index aee3e173d3..c1d6ac4af2 100644 --- a/contracts/examples/digital-cash/src/digital_cash.rs +++ b/contracts/examples/digital-cash/src/digital_cash.rs @@ -4,211 +4,69 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +mod constants; mod deposit_info; +mod helpers; +mod pay_fee_and_fund; +mod signature_operations; +mod storage; -use deposit_info::{DepositInfo, Fee}; - -pub const SECONDS_PER_ROUND: u64 = 6; -pub use multiversx_sc::api::{ED25519_KEY_BYTE_LEN, ED25519_SIGNATURE_BYTE_LEN}; - -static NON_EXISTENT_KEY_ERR_MSG: &[u8] = b"non-existent key"; -static FEES_NOT_COVERED_ERR_MSG: &[u8] = b"fees not covered"; -static CANNOT_DEPOSIT_FUNDS_ERR_MSG: &[u8] = - b"cannot deposit funds without covering the fee cost first"; +use constants::*; #[multiversx_sc::contract] -pub trait DigitalCash { +pub trait DigitalCash: + pay_fee_and_fund::PayFeeAndFund + + signature_operations::SignatureOperationsModule + + helpers::HelpersModule + + storage::StorageModule +{ #[init] - fn init(&self, fee: BigUint) { - self.fee().set(fee); - } - - // endpoints - - #[endpoint] - #[payable("*")] - fn fund(&self, address: ManagedAddress, valability: u64) { - let deposit_mapper = self.deposit(&address); - require!(!deposit_mapper.is_empty(), FEES_NOT_COVERED_ERR_MSG); - let depositor = deposit_mapper.get().depositor_address; - require!( - self.blockchain().get_caller() == depositor, - "invalid depositor" - ); - - let egld_payment = self.call_value().egld_value().clone_value(); - let esdt_payment = self.call_value().all_esdt_transfers().clone_value(); - let num_tokens = self.get_num_token_transfers(&egld_payment, &esdt_payment); - require!(num_tokens > 0, "amount must be greater than 0"); - - let fee = self.fee().get(); - deposit_mapper.update(|deposit| { - require!( - deposit.egld_funds == 0 && deposit.esdt_funds.is_empty(), - "key already used" - ); - require!( - fee * num_tokens as u64 <= deposit.fees.value, - CANNOT_DEPOSIT_FUNDS_ERR_MSG - ); - - deposit.fees.num_token_to_transfer += num_tokens; - deposit.valability = valability; - deposit.expiration_round = self.get_expiration_round(valability); - deposit.esdt_funds = esdt_payment; - deposit.egld_funds = egld_payment; - }); + fn init(&self, fee: BigUint, token: EgldOrEsdtTokenIdentifier) { + self.whitelist_fee_token(fee, token); } - #[endpoint] - fn withdraw(&self, address: ManagedAddress) { - let deposit_mapper = self.deposit(&address); - require!(!deposit_mapper.is_empty(), NON_EXISTENT_KEY_ERR_MSG); - - let block_round = self.blockchain().get_block_round(); - let deposit = deposit_mapper.take(); - require!( - deposit.expiration_round < block_round, - "withdrawal has not been available yet" - ); - - let egld_funds = deposit.egld_funds + deposit.fees.value; - if egld_funds > 0 { - self.send() - .direct_egld(&deposit.depositor_address, &egld_funds); - } - - if !deposit.esdt_funds.is_empty() { - self.send() - .direct_multi(&deposit.depositor_address, &deposit.esdt_funds); - } + #[endpoint(whitelistFeeToken)] + #[only_owner] + fn whitelist_fee_token(&self, fee: BigUint, token: EgldOrEsdtTokenIdentifier) { + require!(self.fee(&token).is_empty(), "Token already whitelisted"); + self.fee(&token).set(fee); + self.whitelisted_fee_tokens().insert(token.clone()); + self.all_time_fee_tokens().insert(token); } - #[endpoint] - fn claim( - &self, - address: ManagedAddress, - signature: ManagedByteArray, - ) { - let deposit_mapper = self.deposit(&address); - require!(!deposit_mapper.is_empty(), NON_EXISTENT_KEY_ERR_MSG); - - let caller_address = self.blockchain().get_caller(); - self.require_signature(&address, &caller_address, signature); - - let block_round = self.blockchain().get_block_round(); - let fee = self.fee().get(); - let mut deposit = deposit_mapper.take(); - require!(deposit.expiration_round >= block_round, "deposit expired"); - - let num_tokens_transfered = deposit.get_num_tokens(); - let fee_cost = fee * num_tokens_transfered as u64; - deposit.fees.value -= &fee_cost; - - self.collected_fees() - .update(|collected_fees| *collected_fees += fee_cost); - - if deposit.egld_funds > 0 { - self.send() - .direct_egld(&caller_address, &deposit.egld_funds); - } - if !deposit.esdt_funds.is_empty() { - self.send() - .direct_multi(&caller_address, &deposit.esdt_funds); - } - if deposit.fees.value > 0 { - self.send() - .direct_egld(&deposit.depositor_address, &deposit.fees.value); - } + #[endpoint(blacklistFeeToken)] + #[only_owner] + fn blacklist_fee_token(&self, token: EgldOrEsdtTokenIdentifier) { + require!(!self.fee(&token).is_empty(), "Token is not whitelisted"); + self.fee(&token).clear(); + self.whitelisted_fee_tokens().swap_remove(&token); } #[endpoint(claimFees)] #[only_owner] fn claim_fees(&self) { - let fees = self.collected_fees().take(); - if fees == 0 { - return; - } - + let fee_tokens_mapper = self.all_time_fee_tokens(); + let fee_tokens = fee_tokens_mapper.iter(); let caller_address = self.blockchain().get_caller(); - self.send().direct_egld(&caller_address, &fees); - } - - #[endpoint(depositFees)] - #[payable("EGLD")] - fn deposit_fees(&self, address: ManagedAddress) { - let payment = self.call_value().egld_value().clone_value(); - let caller_address = self.blockchain().get_caller(); - let deposit_mapper = self.deposit(&address); - if !deposit_mapper.is_empty() { - deposit_mapper.update(|deposit| deposit.fees.value += payment); - - return; + let mut collected_esdt_fees = ManagedVec::new(); + for token in fee_tokens { + let fee = self.collected_fees(&token).take(); + if fee == 0 { + continue; + } + if token == EgldOrEsdtTokenIdentifier::egld() { + self.send().direct_egld(&caller_address, &fee); + } else { + let collected_fee = EsdtTokenPayment::new(token.unwrap_esdt(), 0, fee); + collected_esdt_fees.push(collected_fee); + } } - - let new_deposit = DepositInfo { - depositor_address: caller_address, - esdt_funds: ManagedVec::new(), - egld_funds: BigUint::zero(), - valability: 0, - expiration_round: 0, - fees: Fee { - num_token_to_transfer: 0, - value: payment, - }, - }; - deposit_mapper.set(new_deposit); - } - - #[endpoint] - fn forward( - &self, - address: ManagedAddress, - forward_address: ManagedAddress, - signature: ManagedByteArray, - ) { - let deposit_mapper = self.deposit(&forward_address); - require!(!deposit_mapper.is_empty(), CANNOT_DEPOSIT_FUNDS_ERR_MSG); - - let caller_address = self.blockchain().get_caller(); - let fee = self.fee().get(); - self.require_signature(&address, &caller_address, signature); - - let mut forwarded_deposit = self.deposit(&address).take(); - let num_tokens = forwarded_deposit.get_num_tokens(); - deposit_mapper.update(|deposit| { - require!( - deposit.egld_funds == BigUint::zero() && deposit.esdt_funds.is_empty(), - "key already used" - ); - require!( - &fee * num_tokens as u64 <= deposit.fees.value, - "cannot forward funds without the owner covering the fee cost first" - ); - - deposit.fees.num_token_to_transfer += num_tokens; - deposit.valability = forwarded_deposit.valability; - deposit.expiration_round = self.get_expiration_round(forwarded_deposit.valability); - deposit.esdt_funds = forwarded_deposit.esdt_funds; - deposit.egld_funds = forwarded_deposit.egld_funds; - }); - - let forward_fee = &fee * num_tokens as u64; - forwarded_deposit.fees.value -= &forward_fee; - - self.collected_fees() - .update(|collected_fees| *collected_fees += forward_fee); - - if forwarded_deposit.fees.value > 0 { - self.send().direct_egld( - &forwarded_deposit.depositor_address, - &forwarded_deposit.fees.value, - ); + if !collected_esdt_fees.is_empty() { + self.send() + .direct_multi(&caller_address, &collected_esdt_fees); } } - // views - #[view(getAmount)] fn get_amount( &self, @@ -232,48 +90,4 @@ pub trait DigitalCash { BigUint::zero() } - - // private functions - - fn get_expiration_round(&self, valability: u64) -> u64 { - let valability_rounds = valability / SECONDS_PER_ROUND; - self.blockchain().get_block_round() + valability_rounds - } - - fn get_num_token_transfers( - &self, - egld_value: &BigUint, - esdt_transfers: &ManagedVec, - ) -> usize { - let mut amount = esdt_transfers.len(); - if egld_value > &0 { - amount += 1; - } - - amount - } - - fn require_signature( - &self, - address: &ManagedAddress, - caller_address: &ManagedAddress, - signature: ManagedByteArray, - ) { - let addr = address.as_managed_buffer(); - let message = caller_address.as_managed_buffer(); - self.crypto() - .verify_ed25519(addr, message, signature.as_managed_buffer()); - } - - // storage - - #[view] - #[storage_mapper("deposit")] - fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper>; - - #[storage_mapper("fee")] - fn fee(&self) -> SingleValueMapper; - - #[storage_mapper("collectedFees")] - fn collected_fees(&self) -> SingleValueMapper; } diff --git a/contracts/examples/digital-cash/src/helpers.rs b/contracts/examples/digital-cash/src/helpers.rs new file mode 100644 index 0000000000..c9bb1ea0aa --- /dev/null +++ b/contracts/examples/digital-cash/src/helpers.rs @@ -0,0 +1,119 @@ +multiversx_sc::imports!(); + +use crate::{ + constants::*, + deposit_info::{DepositInfo, Fee}, + storage, +}; +#[multiversx_sc::module] +pub trait HelpersModule: storage::StorageModule { + fn send_fee_to_address(&self, fee: &EgldOrEsdtTokenPayment, address: &ManagedAddress) { + if fee.token_identifier == EgldOrEsdtTokenIdentifier::egld() { + self.send().direct_egld(address, &fee.amount); + } else { + let esdt_fee = fee.clone().unwrap_esdt(); + self.send() + .direct_esdt(address, &esdt_fee.token_identifier, 0, &esdt_fee.amount); + } + } + + fn get_num_token_transfers( + &self, + egld_value: &BigUint, + esdt_transfers: &ManagedVec, + ) -> usize { + let mut amount = esdt_transfers.len(); + if egld_value > &0 { + amount += 1; + } + + amount + } + + fn get_expiration_round(&self, valability: u64) -> u64 { + let valability_rounds = valability / SECONDS_PER_ROUND; + self.blockchain().get_block_round() + valability_rounds + } + + fn get_fee_for_token(&self, token: &EgldOrEsdtTokenIdentifier) -> BigUint { + require!( + self.whitelisted_fee_tokens().contains(token), + "invalid fee toke provided" + ); + let fee_token = self.fee(token); + fee_token.get() + } + + fn make_fund( + &self, + egld_payment: BigUint, + esdt_payment: ManagedVec, + address: ManagedAddress, + valability: u64, + ) { + let deposit_mapper = self.deposit(&address); + + deposit_mapper.update(|deposit| { + require!( + deposit.egld_funds == 0 && deposit.esdt_funds.is_empty(), + "key already used" + ); + let num_tokens = self.get_num_token_transfers(&egld_payment, &esdt_payment); + deposit.fees.num_token_to_transfer += num_tokens; + deposit.valability = valability; + deposit.expiration_round = self.get_expiration_round(valability); + deposit.esdt_funds = esdt_payment; + deposit.egld_funds = egld_payment; + }); + } + + fn check_fees_cover_number_of_tokens( + &self, + num_tokens: usize, + fee: BigUint, + paid_fee: BigUint, + ) { + require!(num_tokens > 0, "amount must be greater than 0"); + require!( + fee * num_tokens as u64 <= paid_fee, + CANNOT_DEPOSIT_FUNDS_ERR_MSG + ); + } + + fn update_fees( + &self, + caller_address: ManagedAddress, + address: &ManagedAddress, + payment: EgldOrEsdtTokenPayment, + ) { + self.get_fee_for_token(&payment.token_identifier); + let deposit_mapper = self.deposit(address); + if !deposit_mapper.is_empty() { + deposit_mapper.update(|deposit| { + require!( + deposit.depositor_address == caller_address, + "invalid depositor address" + ); + require!( + deposit.fees.value.token_identifier == payment.token_identifier, + "can only have 1 type of token as fee" + ); + deposit.fees.value.amount += payment.amount; + }); + return; + } + + let new_deposit = DepositInfo { + depositor_address: caller_address, + esdt_funds: ManagedVec::new(), + egld_funds: BigUint::zero(), + valability: 0, + expiration_round: 0, + fees: Fee { + num_token_to_transfer: 0, + value: payment, + }, + }; + deposit_mapper.set(new_deposit); + } +} diff --git a/contracts/examples/digital-cash/src/pay_fee_and_fund.rs b/contracts/examples/digital-cash/src/pay_fee_and_fund.rs new file mode 100644 index 0000000000..246ffefb6b --- /dev/null +++ b/contracts/examples/digital-cash/src/pay_fee_and_fund.rs @@ -0,0 +1,63 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use crate::{constants::*, helpers, storage}; + +#[multiversx_sc::module] +pub trait PayFeeAndFund: storage::StorageModule + helpers::HelpersModule { + #[endpoint(payFeeAndFundESDT)] + #[payable("*")] + fn pay_fee_and_fund_esdt(&self, address: ManagedAddress, valability: u64) { + let mut payments = self.call_value().all_esdt_transfers().clone_value(); + let fee = EgldOrEsdtTokenPayment::from(payments.get(0)); + let caller_address = self.blockchain().get_caller(); + self.update_fees(caller_address, &address, fee); + + payments.remove(0); + + self.make_fund(0u64.into(), payments, address, valability) + } + #[endpoint(payFeeAndFundEGLD)] + #[payable("EGLD")] + fn pay_fee_and_fund_egld(&self, address: ManagedAddress, valability: u64) { + let mut fund = self.call_value().egld_value().clone_value(); + let fee_value = self.fee(&EgldOrEsdtTokenIdentifier::egld()).get(); + require!(fund > fee_value, "payment not covering fees"); + + fund -= fee_value.clone(); + let fee = EgldOrEsdtTokenPayment::new(EgldOrEsdtTokenIdentifier::egld(), 0, fee_value); + let caller_address = self.blockchain().get_caller(); + self.update_fees(caller_address, &address, fee); + + self.make_fund(fund, ManagedVec::new(), address, valability); + } + + #[endpoint] + #[payable("*")] + fn fund(&self, address: ManagedAddress, valability: u64) { + require!(!self.deposit(&address).is_empty(), FEES_NOT_COVERED_ERR_MSG); + let deposit_mapper = self.deposit(&address).get(); + let depositor = deposit_mapper.depositor_address; + require!( + self.blockchain().get_caller() == depositor, + "invalid depositor" + ); + let deposited_fee_token = deposit_mapper.fees.value; + let fee_amount = self.fee(&deposited_fee_token.token_identifier).get(); + let egld_payment = self.call_value().egld_value().clone_value(); + let esdt_payment = self.call_value().all_esdt_transfers().clone_value(); + + let num_tokens = self.get_num_token_transfers(&egld_payment, &esdt_payment); + self.check_fees_cover_number_of_tokens(num_tokens, fee_amount, deposited_fee_token.amount); + + self.make_fund(egld_payment, esdt_payment, address, valability); + } + + #[endpoint(depositFees)] + #[payable("EGLD")] + fn deposit_fees(&self, address: &ManagedAddress) { + let payment = self.call_value().egld_or_single_esdt(); + let caller_address = self.blockchain().get_caller(); + self.update_fees(caller_address, address, payment); + } +} diff --git a/contracts/examples/digital-cash/src/signature_operations.rs b/contracts/examples/digital-cash/src/signature_operations.rs new file mode 100644 index 0000000000..f02c1c7cb8 --- /dev/null +++ b/contracts/examples/digital-cash/src/signature_operations.rs @@ -0,0 +1,148 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use crate::{constants::*, helpers, storage}; + +pub use multiversx_sc::api::{ED25519_KEY_BYTE_LEN, ED25519_SIGNATURE_BYTE_LEN}; + +#[multiversx_sc::module] +pub trait SignatureOperationsModule: storage::StorageModule + helpers::HelpersModule { + #[endpoint] + fn withdraw(&self, address: ManagedAddress) { + let deposit_mapper = self.deposit(&address); + require!(!deposit_mapper.is_empty(), NON_EXISTENT_KEY_ERR_MSG); + + let deposit = deposit_mapper.take(); + let paid_fee_token = deposit.fees.value; + + let block_round = self.blockchain().get_block_round(); + require!( + deposit.expiration_round < block_round, + "withdrawal has not been available yet" + ); + + let mut egld_funds = deposit.egld_funds; + let mut esdt_funds = deposit.esdt_funds; + + if paid_fee_token.token_identifier == EgldOrEsdtTokenIdentifier::egld() { + egld_funds += paid_fee_token.amount; + } else { + let esdt_fee_token = paid_fee_token.unwrap_esdt(); + let esdt_fee = + EsdtTokenPayment::new(esdt_fee_token.token_identifier, 0, esdt_fee_token.amount); + esdt_funds.push(esdt_fee); + } + + if egld_funds > 0 { + self.send() + .direct_egld(&deposit.depositor_address, &egld_funds); + } + + if !esdt_funds.is_empty() { + self.send() + .direct_multi(&deposit.depositor_address, &esdt_funds); + } + } + + #[endpoint] + fn claim( + &self, + address: ManagedAddress, + signature: ManagedByteArray, + ) { + let deposit_mapper = self.deposit(&address); + require!(!deposit_mapper.is_empty(), NON_EXISTENT_KEY_ERR_MSG); + + let caller_address = self.blockchain().get_caller(); + self.require_signature(&address, &caller_address, signature); + + let block_round = self.blockchain().get_block_round(); + let deposit = deposit_mapper.take(); + let num_tokens_transfered = deposit.get_num_tokens(); + let mut deposited_fee = deposit.fees.value; + + let fee_token = deposited_fee.token_identifier.clone(); + let fee = self.fee(&fee_token).get(); + require!(deposit.expiration_round >= block_round, "deposit expired"); + + let fee_cost = fee * num_tokens_transfered as u64; + deposited_fee.amount -= &fee_cost; + + self.collected_fees(&fee_token) + .update(|collected_fees| *collected_fees += fee_cost); + + if deposit.egld_funds > 0 { + self.send() + .direct_egld(&caller_address, &deposit.egld_funds); + } + if !deposit.esdt_funds.is_empty() { + self.send() + .direct_multi(&caller_address, &deposit.esdt_funds); + } + if deposited_fee.amount > 0 { + self.send_fee_to_address(&deposited_fee, &deposit.depositor_address); + } + } + + #[endpoint] + #[payable("*")] + fn forward( + &self, + address: ManagedAddress, + forward_address: ManagedAddress, + signature: ManagedByteArray, + ) { + let paid_fee = self.call_value().egld_or_single_esdt(); + let caller_address = self.blockchain().get_caller(); + let fee_token = paid_fee.token_identifier.clone(); + self.require_signature(&address, &caller_address, signature); + self.update_fees(caller_address, &forward_address, paid_fee); + + let new_deposit = self.deposit(&forward_address); + let fee = self.fee(&fee_token).get(); + + let mut current_deposit = self.deposit(&address).take(); + let num_tokens = current_deposit.get_num_tokens(); + new_deposit.update(|fwd_deposit| { + require!( + fwd_deposit.egld_funds == BigUint::zero() && fwd_deposit.esdt_funds.is_empty(), + "key already used" + ); + require!( + &fee * num_tokens as u64 <= fwd_deposit.fees.value.amount, + "cannot deposit funds without covering the fee cost first" + ); + + fwd_deposit.fees.num_token_to_transfer += num_tokens; + fwd_deposit.valability = current_deposit.valability; + fwd_deposit.expiration_round = self.get_expiration_round(current_deposit.valability); + fwd_deposit.esdt_funds = current_deposit.esdt_funds; + fwd_deposit.egld_funds = current_deposit.egld_funds; + }); + + let forward_fee = &fee * num_tokens as u64; + current_deposit.fees.value.amount -= &forward_fee; + + self.collected_fees(&fee_token) + .update(|collected_fees| *collected_fees += forward_fee); + + if current_deposit.fees.value.amount > 0 { + self.send_fee_to_address( + ¤t_deposit.fees.value, + ¤t_deposit.depositor_address, + ); + } + } + + fn require_signature( + &self, + address: &ManagedAddress, + caller_address: &ManagedAddress, + signature: ManagedByteArray, + ) { + let addr = address.as_managed_buffer(); + let message = caller_address.as_managed_buffer(); + self.crypto() + .verify_ed25519(addr, message, signature.as_managed_buffer()); + } +} diff --git a/contracts/examples/digital-cash/src/storage.rs b/contracts/examples/digital-cash/src/storage.rs new file mode 100644 index 0000000000..eb0fbef93d --- /dev/null +++ b/contracts/examples/digital-cash/src/storage.rs @@ -0,0 +1,23 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use crate::deposit_info::*; + +#[multiversx_sc::module] +pub trait StorageModule { + #[view] + #[storage_mapper("deposit")] + fn deposit(&self, donor: &ManagedAddress) -> SingleValueMapper>; + + #[storage_mapper("fee")] + fn fee(&self, token: &EgldOrEsdtTokenIdentifier) -> SingleValueMapper; + + #[storage_mapper("collectedFees")] + fn collected_fees(&self, token: &EgldOrEsdtTokenIdentifier) -> SingleValueMapper; + + #[storage_mapper("whitelistedFeeTokens")] + fn whitelisted_fee_tokens(&self) -> UnorderedSetMapper; + + #[storage_mapper("allTimeFeeTokens")] + fn all_time_fee_tokens(&self) -> UnorderedSetMapper; +} diff --git a/contracts/examples/digital-cash/tests/digital_cash_scenario_go_test.rs b/contracts/examples/digital-cash/tests/digital_cash_scenario_go_test.rs index e2d4d11267..7d48e43564 100644 --- a/contracts/examples/digital-cash/tests/digital_cash_scenario_go_test.rs +++ b/contracts/examples/digital-cash/tests/digital_cash_scenario_go_test.rs @@ -39,6 +39,21 @@ fn set_accounts_go() { world().run("scenarios/set-accounts.scen.json"); } +#[test] +fn whitelist_blacklist_fee_token_go() { + world().run("scenarios/whitelist-blacklist-fee-tokens.scen.json"); +} + +#[test] +fn pay_fee_and_fund_esdt_go() { + world().run("scenarios/pay-fee-and-fund-esdt.scen.json"); +} + +#[test] +fn pay_fee_and_fund_egld_go() { + world().run("scenarios/pay-fee-and-fund-egld.scen.json"); +} + #[test] fn withdraw_egld_go() { world().run("scenarios/withdraw-egld.scen.json"); diff --git a/contracts/examples/digital-cash/tests/digital_cash_scenario_rs_test.rs b/contracts/examples/digital-cash/tests/digital_cash_scenario_rs_test.rs index bc20caac7d..61ab7ff699 100644 --- a/contracts/examples/digital-cash/tests/digital_cash_scenario_rs_test.rs +++ b/contracts/examples/digital-cash/tests/digital_cash_scenario_rs_test.rs @@ -46,6 +46,21 @@ fn set_accounts_rs() { world().run("scenarios/set-accounts.scen.json"); } +#[test] +fn whitelist_blacklist_fee_token_rs() { + world().run("scenarios/whitelist-blacklist-fee-tokens.scen.json"); +} + +#[test] +fn pay_fee_and_fund_esdt_rs() { + world().run("scenarios/pay-fee-and-fund-esdt.scen.json"); +} + +#[test] +fn pay_fee_and_fund_egld_rs() { + world().run("scenarios/pay-fee-and-fund-egld.scen.json"); +} + #[test] fn withdraw_egld_rs() { world().run("scenarios/withdraw-egld.scen.json"); diff --git a/contracts/examples/digital-cash/wasm/src/lib.rs b/contracts/examples/digital-cash/wasm/src/lib.rs index 854d3310f9..4225446734 100644 --- a/contracts/examples/digital-cash/wasm/src/lib.rs +++ b/contracts/examples/digital-cash/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 8 +// Endpoints: 12 // Async Callback (empty): 1 -// Total number of exported functions: 10 +// Total number of exported functions: 14 #![no_std] @@ -22,13 +22,17 @@ multiversx_sc_wasm_adapter::endpoints! { digital_cash ( init => init + whitelistFeeToken => whitelist_fee_token + blacklistFeeToken => blacklist_fee_token + claimFees => claim_fees + getAmount => get_amount + payFeeAndFundESDT => pay_fee_and_fund_esdt + payFeeAndFundEGLD => pay_fee_and_fund_egld fund => fund + depositFees => deposit_fees withdraw => withdraw claim => claim - claimFees => claim_fees - depositFees => deposit_fees forward => forward - getAmount => get_amount deposit => deposit ) }