From abb2c4e0b29de2ff4466fbc77030a3b2c651e798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Costin=20Caraba=C8=99?= Date: Wed, 21 Aug 2024 19:23:23 +0300 Subject: [PATCH 1/2] paymaster: Add interactor --- contracts/paymaster/interactor/.gitignore | 2 + contracts/paymaster/interactor/Cargo.toml | 27 ++++ .../interactor/src/interactor_main.rs | 146 ++++++++++++++++++ contracts/paymaster/interactor/src/proxy.rs | 85 ++++++++++ contracts/paymaster/sc-config.toml | 4 + contracts/paymaster/src/forward_call.rs | 2 +- 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 contracts/paymaster/interactor/.gitignore create mode 100644 contracts/paymaster/interactor/Cargo.toml create mode 100644 contracts/paymaster/interactor/src/interactor_main.rs create mode 100644 contracts/paymaster/interactor/src/proxy.rs diff --git a/contracts/paymaster/interactor/.gitignore b/contracts/paymaster/interactor/.gitignore new file mode 100644 index 00000000..5a64d09a --- /dev/null +++ b/contracts/paymaster/interactor/.gitignore @@ -0,0 +1,2 @@ +# Pem files are used for interactions, but shouldn't be committed +*.pem diff --git a/contracts/paymaster/interactor/Cargo.toml b/contracts/paymaster/interactor/Cargo.toml new file mode 100644 index 00000000..21915899 --- /dev/null +++ b/contracts/paymaster/interactor/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "rust-interact" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false + +[[bin]] +name = "rust-interact" +path = "src/interactor_main.rs" + +[dependencies.paymaster] +path = ".." + +[dependencies.multiversx-sc-snippets] +version = "0.52.3" + +[dependencies.multiversx-sc] +version = "0.52.3" + +[dependencies] +clap = { version = "4.4.7", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.6" + +# [workspace] + diff --git a/contracts/paymaster/interactor/src/interactor_main.rs b/contracts/paymaster/interactor/src/interactor_main.rs new file mode 100644 index 00000000..b4983528 --- /dev/null +++ b/contracts/paymaster/interactor/src/interactor_main.rs @@ -0,0 +1,146 @@ +#![allow(non_snake_case)] + +mod proxy; + +use multiversx_sc_snippets::imports::*; +use multiversx_sc_snippets::sdk; +use serde::{Deserialize, Serialize}; +use std::{ + io::{Read, Write}, + path::Path, +}; + + +const GATEWAY: &str = sdk::gateway::DEVNET_GATEWAY; +const STATE_FILE: &str = "state.toml"; + + +#[tokio::main] +async fn main() { + env_logger::init(); + + let mut args = std::env::args(); + let _ = args.next(); + let cmd = args.next().expect("at least one argument required"); + let mut interact = ContractInteract::new().await; + match cmd.as_str() { + "deploy" => interact.deploy().await, + "forwardExecution" => interact.forward_execution().await, + _ => panic!("unknown command: {}", &cmd), + } +} + + +#[derive(Debug, Default, Serialize, Deserialize)] +struct State { + contract_address: Option +} + +impl State { + // Deserializes state from file + pub fn load_state() -> Self { + if Path::new(STATE_FILE).exists() { + let mut file = std::fs::File::open(STATE_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() + } + } + + /// Sets the contract address + pub fn set_address(&mut self, address: Bech32Address) { + self.contract_address = Some(address); + } + + /// Returns the contract address + pub fn current_address(&self) -> &Bech32Address { + self.contract_address + .as_ref() + .expect("no known contract, deploy first") + } + } + + impl Drop for State { + // Serializes state to file + fn drop(&mut self) { + let mut file = std::fs::File::create(STATE_FILE).unwrap(); + file.write_all(toml::to_string(self).unwrap().as_bytes()) + .unwrap(); + } + } + +struct ContractInteract { + interactor: Interactor, + wallet_address: Address, + contract_code: BytesValue, + state: State +} + +impl ContractInteract { + async fn new() -> Self { + let mut interactor = Interactor::new(GATEWAY).await; + let wallet_address = interactor.register_wallet(test_wallets::alice()); + + let contract_code = BytesValue::interpret_from( + "mxsc:../output/paymaster.mxsc.json", + &InterpreterContext::default(), + ); + + ContractInteract { + interactor, + wallet_address, + contract_code, + state: State::load_state() + } + } + + async fn deploy(&mut self) { + let new_address = self + .interactor + .tx() + .from(&self.wallet_address) + .gas(30_000_000u64) + .typed(proxy::PaymasterContractProxy) + .init() + .code(&self.contract_code) + .returns(ReturnsNewAddress) + .prepare_async() + .run() + .await; + let new_address_bech32 = bech32::encode(&new_address); + self.state + .set_address(Bech32Address::from_bech32_string(new_address_bech32.clone())); + + println!("new address: {new_address_bech32}"); + } + + async fn forward_execution(&mut self) { + let token_id = String::new(); + let token_nonce = 0u64; + let token_amount = BigUint::::from(0u128); + + let relayer_addr = bech32::decode(""); + let dest = bech32::decode(""); + let endpoint_name = ManagedBuffer::new_from_bytes(&b""[..]); + let endpoint_args = MultiValueVec::from(vec![ManagedBuffer::new_from_bytes(&b""[..])]); + + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_address()) + .gas(30_000_000u64) + .typed(proxy::PaymasterContractProxy) + .forward_execution(relayer_addr, dest, endpoint_name, endpoint_args) + .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount)) + .returns(ReturnsResultUnmanaged) + .prepare_async() + .run() + .await; + + println!("Result: {response:?}"); + } + +} diff --git a/contracts/paymaster/interactor/src/proxy.rs b/contracts/paymaster/interactor/src/proxy.rs new file mode 100644 index 00000000..9bb7df68 --- /dev/null +++ b/contracts/paymaster/interactor/src/proxy.rs @@ -0,0 +1,85 @@ +// Code generated by the multiversx-sc proxy generator. DO NOT EDIT. + +//////////////////////////////////////////////////// +////////////////// AUTO-GENERATED ////////////////// +//////////////////////////////////////////////////// + +#![allow(dead_code)] +#![allow(clippy::all)] + +use multiversx_sc::proxy_imports::*; + +pub struct PaymasterContractProxy; + +impl TxProxyTrait for PaymasterContractProxy +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + type TxProxyMethods = PaymasterContractProxyMethods; + + fn proxy_methods(self, tx: Tx) -> Self::TxProxyMethods { + PaymasterContractProxyMethods { wrapped_tx: tx } + } +} + +pub struct PaymasterContractProxyMethods +where + Env: TxEnv, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + wrapped_tx: Tx, +} + +#[rustfmt::skip] +impl PaymasterContractProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + Gas: TxGas, +{ + pub fn init( + self, + ) -> TxTypedDeploy { + self.wrapped_tx + .payment(NotPayable) + .raw_deploy() + .original_result() + } +} + +#[rustfmt::skip] +impl PaymasterContractProxyMethods +where + Env: TxEnv, + Env::Api: VMApi, + From: TxFrom, + To: TxTo, + Gas: TxGas, +{ + pub fn forward_execution< + Arg0: ProxyArg>, + Arg1: ProxyArg>, + Arg2: ProxyArg>, + Arg3: ProxyArg>>, + >( + self, + relayer_addr: Arg0, + dest: Arg1, + endpoint_name: Arg2, + endpoint_args: Arg3, + ) -> TxTypedCall { + self.wrapped_tx + .raw_call("forwardExecution") + .argument(&relayer_addr) + .argument(&dest) + .argument(&endpoint_name) + .argument(&endpoint_args) + .original_result() + } +} diff --git a/contracts/paymaster/sc-config.toml b/contracts/paymaster/sc-config.toml index ced8e4d9..8ee14733 100644 --- a/contracts/paymaster/sc-config.toml +++ b/contracts/paymaster/sc-config.toml @@ -1,2 +1,6 @@ [[proxy]] path = "src/paymaster_proxy.rs" + +[[proxy]] +path = "interactor/src/proxy.rs" + diff --git a/contracts/paymaster/src/forward_call.rs b/contracts/paymaster/src/forward_call.rs index 9c340670..c0efa34d 100644 --- a/contracts/paymaster/src/forward_call.rs +++ b/contracts/paymaster/src/forward_call.rs @@ -40,7 +40,7 @@ pub trait ForwardCall { .payment(&back_transfers.esdt_payments) .transfer(); } - if back_transfers.total_egld_amount != BigUint::zero() { + if back_transfers.total_egld_amount > 0 { self.tx() .to(&original_caller) .egld(back_transfers.total_egld_amount) From 231e425e37ca6ed5bc54a8a5779454dac8a3fa7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Costin=20Caraba=C8=99?= Date: Thu, 22 Aug 2024 17:54:52 +0300 Subject: [PATCH 2/2] paymaster: forwardExecution interactor --- Cargo.lock | 12 ++ Cargo.toml | 1 + contracts/paymaster/interactor/config.toml | 7 + .../interactor/src/interactor_main.rs | 122 +++++++++++------- .../interactor/src/paymaster_config.rs | 23 ++++ contracts/paymaster/interactor/state.toml | 1 + 6 files changed, 119 insertions(+), 47 deletions(-) create mode 100644 contracts/paymaster/interactor/config.toml create mode 100644 contracts/paymaster/interactor/src/paymaster_config.rs create mode 100644 contracts/paymaster/interactor/state.toml diff --git a/Cargo.lock b/Cargo.lock index e65842c8..889b785d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2215,6 +2215,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rust-interact" +version = "0.0.0" +dependencies = [ + "clap", + "multiversx-sc", + "multiversx-sc-snippets", + "paymaster", + "serde", + "toml", +] + [[package]] name = "rustc-demangle" version = "0.1.24" diff --git a/Cargo.toml b/Cargo.toml index 8fac2496..5fb5dd18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ members = [ "contracts/pair-mock/meta", "contracts/paymaster", "contracts/paymaster/meta", + "contracts/paymaster/interactor", "contracts/ping-pong-egld", "contracts/ping-pong-egld/meta", "contracts/proxy-deployer", diff --git a/contracts/paymaster/interactor/config.toml b/contracts/paymaster/interactor/config.toml new file mode 100644 index 00000000..0e212a6c --- /dev/null +++ b/contracts/paymaster/interactor/config.toml @@ -0,0 +1,7 @@ +gateway = 'https://devnet-gateway.multiversx.com' + +relayer_addr = "erd19azy5vlq343un82cqz03744vmta9ya8l5xwj3ywz7r8m8pkttd0syhkffa" +egld_mex_pair_address = "erd1qqqqqqqqqqqqqpgqzw0d0tj25qme9e4ukverjjjqle6xamay0n4s5r0v9g" +egld_usdc_pair_address = "erd1qqqqqqqqqqqqqpgqtqfhy99su9xzjjrq59kpzpp25udtc9eq0n4sr90ax6" +wegld_address = "erd1qqqqqqqqqqqqqpgqpv09kfzry5y4sj05udcngesat07umyj70n4sa2c0rp" +router_address = "erd1qqqqqqqqqqqqqpgqa7hv0nahgsl8tz0psat46x0tchm0wuyc0n4s6q28ad" diff --git a/contracts/paymaster/interactor/src/interactor_main.rs b/contracts/paymaster/interactor/src/interactor_main.rs index b4983528..ab16d482 100644 --- a/contracts/paymaster/interactor/src/interactor_main.rs +++ b/contracts/paymaster/interactor/src/interactor_main.rs @@ -1,19 +1,31 @@ #![allow(non_snake_case)] +mod paymaster_config; mod proxy; use multiversx_sc_snippets::imports::*; use multiversx_sc_snippets::sdk; +use paymaster_config::Config; use serde::{Deserialize, Serialize}; use std::{ io::{Read, Write}, path::Path, }; - const GATEWAY: &str = sdk::gateway::DEVNET_GATEWAY; const STATE_FILE: &str = "state.toml"; +const ONE_UNIT: u64 = 1_000_000_000_000_000_000; +const ONE_MILLION: u64 = 1_000_000; + +pub static WEGLD_TOKEN_ID: &[u8] = b"WEGLD-a28c59"; +pub static MEX_TOKEN_ID: &[u8] = b"MEX-a659d0"; +pub static ONE_TOKEN_ID: &[u8] = b"ONE-83a7c0"; +pub static USDC_TOKEN_ID: &[u8] = b"USDC-350c4e"; +pub static UTK_TOKEN_ID: &[u8] = b"UTK-14d57d"; + +pub const SWAP_TOKENS_FIXED_INPUT_FUNC_NAME: &[u8] = b"swapTokensFixedInput"; +pub const SWAP_TOKENS_FIXED_OUTPUT_FUNC_NAME: &[u8] = b"swapTokensFixedOutput"; #[tokio::main] async fn main() { @@ -30,59 +42,59 @@ async fn main() { } } - #[derive(Debug, Default, Serialize, Deserialize)] struct State { - contract_address: Option + contract_address: Option, } impl State { - // Deserializes state from file - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - /// Sets the contract address - pub fn set_address(&mut self, address: Bech32Address) { - self.contract_address = Some(address); - } - - /// Returns the contract address - pub fn current_address(&self) -> &Bech32Address { - self.contract_address - .as_ref() - .expect("no known contract, deploy first") + // Deserializes state from file + pub fn load_state() -> Self { + if Path::new(STATE_FILE).exists() { + let mut file = std::fs::File::open(STATE_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() } } - - impl Drop for State { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } + + /// Sets the contract address + pub fn set_address(&mut self, address: Bech32Address) { + self.contract_address = Some(address); } + /// Returns the contract address + pub fn current_address(&self) -> &Bech32Address { + self.contract_address + .as_ref() + .expect("no known contract, deploy first") + } +} + +impl Drop for State { + // Serializes state to file + fn drop(&mut self) { + let mut file = std::fs::File::create(STATE_FILE).unwrap(); + file.write_all(toml::to_string(self).unwrap().as_bytes()) + .unwrap(); + } +} + struct ContractInteract { interactor: Interactor, wallet_address: Address, contract_code: BytesValue, - state: State + config: Config, + state: State, } impl ContractInteract { async fn new() -> Self { let mut interactor = Interactor::new(GATEWAY).await; let wallet_address = interactor.register_wallet(test_wallets::alice()); - + let contract_code = BytesValue::interpret_from( "mxsc:../output/paymaster.mxsc.json", &InterpreterContext::default(), @@ -92,7 +104,8 @@ impl ContractInteract { interactor, wallet_address, contract_code, - state: State::load_state() + config: Config::load_config(), + state: State::load_state(), } } @@ -110,21 +123,37 @@ impl ContractInteract { .run() .await; let new_address_bech32 = bech32::encode(&new_address); - self.state - .set_address(Bech32Address::from_bech32_string(new_address_bech32.clone())); + self.state.set_address(Bech32Address::from_bech32_string( + new_address_bech32.clone(), + )); println!("new address: {new_address_bech32}"); } async fn forward_execution(&mut self) { - let token_id = String::new(); let token_nonce = 0u64; - let token_amount = BigUint::::from(0u128); - - let relayer_addr = bech32::decode(""); - let dest = bech32::decode(""); - let endpoint_name = ManagedBuffer::new_from_bytes(&b""[..]); - let endpoint_args = MultiValueVec::from(vec![ManagedBuffer::new_from_bytes(&b""[..])]); + let token_amount = BigUint::::from(ONE_UNIT) * ONE_MILLION; + + let relayer_addr = &self.config.relayer_addr; + + let dest = &self.config.egld_mex_pair_address; + let endpoint_name = ManagedBuffer::new_from_bytes(SWAP_TOKENS_FIXED_INPUT_FUNC_NAME); + let endpoint_args = MultiValueVec::from(vec![ + ManagedBuffer::new_from_bytes(WEGLD_TOKEN_ID), + ManagedBuffer::new_from_bytes(b"1"), + ]); + + let mut payments = ManagedVec::>::new(); + payments.push(EsdtTokenPayment::new( + TokenIdentifier::from(WEGLD_TOKEN_ID), + token_nonce, + BigUint::::from(ONE_UNIT / 100), // 0.01 WEGLD + )); + payments.push(EsdtTokenPayment::new( + TokenIdentifier::from(MEX_TOKEN_ID), + token_nonce, + token_amount, // 1_000_000 MEX + )); let response = self .interactor @@ -134,7 +163,7 @@ impl ContractInteract { .gas(30_000_000u64) .typed(proxy::PaymasterContractProxy) .forward_execution(relayer_addr, dest, endpoint_name, endpoint_args) - .payment((TokenIdentifier::from(token_id.as_str()), token_nonce, token_amount)) + .payment(payments) .returns(ReturnsResultUnmanaged) .prepare_async() .run() @@ -142,5 +171,4 @@ impl ContractInteract { println!("Result: {response:?}"); } - } diff --git a/contracts/paymaster/interactor/src/paymaster_config.rs b/contracts/paymaster/interactor/src/paymaster_config.rs new file mode 100644 index 00000000..02371b7a --- /dev/null +++ b/contracts/paymaster/interactor/src/paymaster_config.rs @@ -0,0 +1,23 @@ +use multiversx_sc_snippets::imports::Bech32Address; +use serde::Deserialize; +use std::io::Read; + +/// Config file +const CONFIG_FILE: &str = "config.toml"; + +/// Multisig Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub relayer_addr: Bech32Address, + pub egld_mex_pair_address: Bech32Address, +} + +impl Config { + // Deserializes config from file + pub fn load_config() -> Self { + let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } +} diff --git a/contracts/paymaster/interactor/state.toml b/contracts/paymaster/interactor/state.toml new file mode 100644 index 00000000..a413fe02 --- /dev/null +++ b/contracts/paymaster/interactor/state.toml @@ -0,0 +1 @@ +contract_address = "erd1qqqqqqqqqqqqqpgqgr7585knhje7ns7w662pptwtjc03hn34d8sspl0nd4"