From 6e3071195fc2c20c19421dfe3b149d5249de47b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Costin=20Caraba=C8=99?= Date: Wed, 27 Sep 2023 16:07:57 +0300 Subject: [PATCH] Paymaster SC --- Cargo.toml | 2 + contracts/paymaster/.gitignore | 7 + contracts/paymaster/Cargo.toml | 18 ++ contracts/paymaster/meta/Cargo.toml | 14 ++ contracts/paymaster/meta/src/main.rs | 3 + contracts/paymaster/multiversx.json | 3 + contracts/paymaster/mxsc-template.toml | 18 ++ contracts/paymaster/scenarios/empty.scen.json | 39 ++++ contracts/paymaster/src/fees.rs | 79 +++++++ contracts/paymaster/src/forward_call.rs | 61 +++++ contracts/paymaster/src/paymaster.rs | 32 +++ .../paymaster/tests/empty_scenario_go_test.rs | 10 + .../paymaster/tests/empty_scenario_rs_test.rs | 14 ++ contracts/paymaster/wasm/Cargo.lock | 209 ++++++++++++++++++ contracts/paymaster/wasm/Cargo.toml | 27 +++ contracts/paymaster/wasm/src/lib.rs | 26 +++ 16 files changed, 562 insertions(+) create mode 100644 contracts/paymaster/.gitignore create mode 100644 contracts/paymaster/Cargo.toml create mode 100644 contracts/paymaster/meta/Cargo.toml create mode 100644 contracts/paymaster/meta/src/main.rs create mode 100644 contracts/paymaster/multiversx.json create mode 100644 contracts/paymaster/mxsc-template.toml create mode 100644 contracts/paymaster/scenarios/empty.scen.json create mode 100644 contracts/paymaster/src/fees.rs create mode 100644 contracts/paymaster/src/forward_call.rs create mode 100644 contracts/paymaster/src/paymaster.rs create mode 100644 contracts/paymaster/tests/empty_scenario_go_test.rs create mode 100644 contracts/paymaster/tests/empty_scenario_rs_test.rs create mode 100644 contracts/paymaster/wasm/Cargo.lock create mode 100644 contracts/paymaster/wasm/Cargo.toml create mode 100644 contracts/paymaster/wasm/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 7d807b5b..937ffe66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ members = [ "contracts/order-book/factory/meta", "contracts/order-book/pair", "contracts/order-book/pair/meta", + "contracts/paymaster", + "contracts/paymaster/meta", "contracts/ping-pong-egld", "contracts/ping-pong-egld/meta", "contracts/proxy-pause", diff --git a/contracts/paymaster/.gitignore b/contracts/paymaster/.gitignore new file mode 100644 index 00000000..2c76bc98 --- /dev/null +++ b/contracts/paymaster/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +*/target/ + +# The mxpy output +/output*/ diff --git a/contracts/paymaster/Cargo.toml b/contracts/paymaster/Cargo.toml new file mode 100644 index 00000000..5ec2cc83 --- /dev/null +++ b/contracts/paymaster/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "paymaster" +version = "0.0.0" +authors = [ "you",] +edition = "2018" +publish = false + +[lib] +path = "src/paymaster.rs" + +[dev-dependencies] +num-bigint = "0.4.2" + +[dependencies.multiversx-sc] +version = "0.43.4" + +[dev-dependencies.multiversx-sc-scenario] +version = "0.41.1" diff --git a/contracts/paymaster/meta/Cargo.toml b/contracts/paymaster/meta/Cargo.toml new file mode 100644 index 00000000..20157c88 --- /dev/null +++ b/contracts/paymaster/meta/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "paymaster-meta" +version = "0.0.0" +edition = "2018" +publish = false +authors = [ "you",] + +[dev-dependencies] + +[dependencies.paymaster] +path = ".." + +[dependencies.multiversx-sc-meta] +version = "0.43.4" diff --git a/contracts/paymaster/meta/src/main.rs b/contracts/paymaster/meta/src/main.rs new file mode 100644 index 00000000..275bd693 --- /dev/null +++ b/contracts/paymaster/meta/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + multiversx_sc_meta::cli_main::(); +} diff --git a/contracts/paymaster/multiversx.json b/contracts/paymaster/multiversx.json new file mode 100644 index 00000000..73655396 --- /dev/null +++ b/contracts/paymaster/multiversx.json @@ -0,0 +1,3 @@ +{ + "language": "rust" +} \ No newline at end of file diff --git a/contracts/paymaster/mxsc-template.toml b/contracts/paymaster/mxsc-template.toml new file mode 100644 index 00000000..441addd7 --- /dev/null +++ b/contracts/paymaster/mxsc-template.toml @@ -0,0 +1,18 @@ +name = "empty" +contract_trait = "EmptyContract" +src_file = "empty.rs" +rename_pairs = [ + [ + "blockchain.set_current_dir_from_workspace(\"contracts/examples/empty\");", + "// blockchain.set_current_dir_from_workspace(\"relative path to your workspace, if applicable\");", + ], +] +files_include = [ + "meta", + "scenarios", + "src", + "tests", + "wasm/Cargo.toml", + "Cargo.toml", + "multiversx.json", +] diff --git a/contracts/paymaster/scenarios/empty.scen.json b/contracts/paymaster/scenarios/empty.scen.json new file mode 100644 index 00000000..b44fde46 --- /dev/null +++ b/contracts/paymaster/scenarios/empty.scen.json @@ -0,0 +1,39 @@ +{ + "name": "paymaster", + "steps": [ + { + "step": "setState", + "accounts": { + "address:owner": { + "nonce": "1", + "balance": "0" + } + }, + "newAddresses": [ + { + "creatorAddress": "address:owner", + "creatorNonce": "1", + "newAddress": "sc:empty" + } + ] + }, + { + "step": "scDeploy", + "id": "deploy", + "tx": { + "from": "address:owner", + "contractCode": "file:../output/paymaster.wasm", + "arguments": [], + "gasLimit": "5,000,000", + "gasPrice": "0" + }, + "expect": { + "out": [], + "status": "", + "logs": [], + "gas": "*", + "refund": "*" + } + } + ] +} \ No newline at end of file diff --git a/contracts/paymaster/src/fees.rs b/contracts/paymaster/src/fees.rs new file mode 100644 index 00000000..7781aacc --- /dev/null +++ b/contracts/paymaster/src/fees.rs @@ -0,0 +1,79 @@ +use crate::forward_call::PaymentsVec; + +multiversx_sc::imports!(); + +pub mod pair_proxy { + #[multiversx_sc::proxy] + pub trait PairProxy { + #[view(getSafePriceByDefaultOffset)] + fn get_safe_price_by_default_offset( + &self, + pair_address: ManagedAddress, + input_payment: EsdtTokenPayment, + ) -> EsdtTokenPayment; + } +} + +#[multiversx_sc::module] +pub trait FeesModule { + #[only_owner] + #[endpoint(addAcceptedFeesTokens)] + fn add_accepted_fees_tokens( + &self, + accepted_tokens: MultiValueEncoded>, + ) { + for pair in accepted_tokens { + let (token_id, pair_address) = pair.into_tuple(); + require!(token_id.is_valid_esdt_identifier(), "Invalid token"); + + self.pair_address_for_token(&token_id).set(pair_address); + } + } + + fn get_price(&self, token_id: TokenIdentifier, amount: BigUint) -> BigUint { + let mapper = self.pair_address_for_token(&token_id); + require!( + !mapper.is_empty(), + "There is no pair addres for the token provided!" + ); + + let pair_address = mapper.get(); + let price_query_address = self.price_query_address().get(); + let price: EsdtTokenPayment = self + .pair_proxy(price_query_address) + .get_safe_price_by_default_offset( + pair_address, + EsdtTokenPayment::new(token_id, 0, amount), + ) + .execute_on_dest_context(); + + price.amount + } + + fn pay_fee_to_relayer(&self, relayer_addr: ManagedAddress, payments: PaymentsVec) { + let mut payments_iter = payments.iter(); + let fee_payment = match payments_iter.next() { + Some(fee) => fee, + None => sc_panic!("Fee payment is missing!"), + }; + + self.send().direct_esdt( + &relayer_addr, + &fee_payment.token_identifier, + 0, + &fee_payment.amount, + ); + } + + #[proxy] + fn pair_proxy(&self, sc_address: ManagedAddress) -> pair_proxy::Proxy; + + #[storage_mapper("pairAddressForToken")] + fn pair_address_for_token( + &self, + token_id: &TokenIdentifier, + ) -> SingleValueMapper; + + #[storage_mapper("priceQueryAddress")] + fn price_query_address(&self) -> SingleValueMapper; +} diff --git a/contracts/paymaster/src/forward_call.rs b/contracts/paymaster/src/forward_call.rs new file mode 100644 index 00000000..6f1f8a52 --- /dev/null +++ b/contracts/paymaster/src/forward_call.rs @@ -0,0 +1,61 @@ +multiversx_sc::imports!(); + +pub type PaymentsVec = ManagedVec>; + +static ERR_CALLBACK_MSG: &[u8] = b"Error received in callback:"; + +#[multiversx_sc::module] +pub trait ForwardCall { + fn forward_call( + &self, + dest: ManagedAddress, + endpoint_name: ManagedBuffer, + endpoint_args: MultiValueEncoded, + payments: PaymentsVec, + ) { + let original_caller = self.blockchain().get_caller(); + + if !self.blockchain().is_smart_contract(&dest) { + self.send().direct_multi(&dest, &payments); + } else { + let mut args_buffer = ManagedArgBuffer::new(); + for arg in endpoint_args { + args_buffer.push_arg(arg); + } + + ContractCallWithMultiEsdt::::new(dest, endpoint_name, payments.clone()) + .with_raw_arguments(args_buffer) + .async_call() + .with_callback( + self.callbacks() + .transfer_callback(original_caller, payments), + ) + .call_and_exit(); + } + } + #[callback] + fn transfer_callback( + &self, + original_caller: ManagedAddress, + initial_payments: ManagedVec>, + #[call_result] result: ManagedAsyncCallResult>, + ) -> MultiValueEncoded { + match result { + ManagedAsyncCallResult::Ok(return_values) => return_values, + ManagedAsyncCallResult::Err(err) => { + if !initial_payments.is_empty() { + self.send() + .direct_multi(&original_caller, &initial_payments); + } + + let mut err_result = MultiValueEncoded::new(); + err_result.push(ManagedBuffer::new_from_bytes(ERR_CALLBACK_MSG)); + err_result.push(err.err_msg.clone()); + + sc_print!("{}", err.err_msg); + + err_result + } + } + } +} diff --git a/contracts/paymaster/src/paymaster.rs b/contracts/paymaster/src/paymaster.rs new file mode 100644 index 00000000..b5f84b0d --- /dev/null +++ b/contracts/paymaster/src/paymaster.rs @@ -0,0 +1,32 @@ +#![no_std] + +multiversx_sc::imports!(); + +pub mod fees; +pub mod forward_call; + +/// An empty contract. To be used as a template when starting a new contract from scratch. +#[multiversx_sc::contract] +pub trait PaymasterContract: fees::FeesModule + forward_call::ForwardCall { + #[init] + fn init(&self, price_query_address: ManagedAddress) { + self.price_query_address().set(price_query_address); + } + + #[endpoint(forwardExecution)] + #[payable("*")] + fn forward_execution( + &self, + relayer_addr: ManagedAddress, + dest: ManagedAddress, + endpoint_name: ManagedBuffer, + endpoint_args: MultiValueEncoded, + ) { + let payments = self.call_value().all_esdt_transfers(); + + self.pay_fee_to_relayer(relayer_addr, payments.clone_value()); + let mut payments_without_fee = payments.clone_value(); + payments_without_fee.remove(0); + self.forward_call(dest, endpoint_name, endpoint_args, payments_without_fee); + } +} diff --git a/contracts/paymaster/tests/empty_scenario_go_test.rs b/contracts/paymaster/tests/empty_scenario_go_test.rs new file mode 100644 index 00000000..ef0a7f21 --- /dev/null +++ b/contracts/paymaster/tests/empty_scenario_go_test.rs @@ -0,0 +1,10 @@ +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + ScenarioWorld::vm_go() +} + +#[test] +fn empty_go() { + world().run("scenarios/empty.scen.json"); +} diff --git a/contracts/paymaster/tests/empty_scenario_rs_test.rs b/contracts/paymaster/tests/empty_scenario_rs_test.rs new file mode 100644 index 00000000..e7cd14ce --- /dev/null +++ b/contracts/paymaster/tests/empty_scenario_rs_test.rs @@ -0,0 +1,14 @@ +use multiversx_sc_scenario::*; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/examples/empty"); + + blockchain.register_contract("file:output/paymaster.wasm", paymaster::ContractBuilder); + blockchain +} + +#[test] +fn empty_rs() { + world().run("scenarios/empty.scen.json"); +} diff --git a/contracts/paymaster/wasm/Cargo.lock b/contracts/paymaster/wasm/Cargo.lock new file mode 100644 index 00000000..5a9b6622 --- /dev/null +++ b/contracts/paymaster/wasm/Cargo.lock @@ -0,0 +1,209 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "empty" +version = "0.0.0" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "empty-wasm" +version = "0.0.0" +dependencies = [ + "empty", + "multiversx-sc-wasm-adapter", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "multiversx-sc" +version = "0.43.4" +dependencies = [ + "bitflags", + "hashbrown", + "hex-literal", + "multiversx-sc-codec", + "multiversx-sc-derive", + "num-traits", +] + +[[package]] +name = "multiversx-sc-codec" +version = "0.18.1" +dependencies = [ + "arrayvec", + "multiversx-sc-codec-derive", +] + +[[package]] +name = "multiversx-sc-codec-derive" +version = "0.18.1" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multiversx-sc-derive" +version = "0.43.4" +dependencies = [ + "hex", + "proc-macro2", + "quote", + "radix_trie", + "syn", +] + +[[package]] +name = "multiversx-sc-wasm-adapter" +version = "0.43.4" +dependencies = [ + "multiversx-sc", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/contracts/paymaster/wasm/Cargo.toml b/contracts/paymaster/wasm/Cargo.toml new file mode 100644 index 00000000..065dc288 --- /dev/null +++ b/contracts/paymaster/wasm/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "paymaster-wasm" +version = "0.0.0" +edition = "2018" +publish = false +authors = [ "you",] + +[lib] +crate-type = [ "cdylib",] + +[workspace] +members = [ ".",] + +[dev-dependencies] + +[profile.release] +codegen-units = 1 +opt-level = "z" +lto = true +debug = false +panic = "abort" + +[dependencies.paymaster] +path = ".." + +[dependencies.multiversx-sc-wasm-adapter] +version = "0.43.4" diff --git a/contracts/paymaster/wasm/src/lib.rs b/contracts/paymaster/wasm/src/lib.rs new file mode 100644 index 00000000..8ea5e5e0 --- /dev/null +++ b/contracts/paymaster/wasm/src/lib.rs @@ -0,0 +1,26 @@ +// Code generated by the multiversx-sc multi-contract system. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +// Init: 1 +// Endpoints: 0 +// Async Callback (empty): 1 +// Total number of exported functions: 2 + +#![no_std] +#![allow(internal_features)] +#![feature(lang_items)] + +multiversx_sc_wasm_adapter::allocator!(); +multiversx_sc_wasm_adapter::panic_handler!(); + +multiversx_sc_wasm_adapter::endpoints! { + empty + ( + init => init + ) +} + +multiversx_sc_wasm_adapter::async_callback_empty! {}