Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paymaster SC #24

Merged
merged 13 commits into from
Oct 3, 2023
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,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",
Expand Down
1 change: 1 addition & 0 deletions contracts/adder/src/adder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub trait Adder {
}

/// Add desired amount to the storage variable.
#[payable("*")]
#[endpoint]
fn add(&self, value: BigUint) {
self.sum().update(|sum| *sum += value);
Expand Down
7 changes: 7 additions & 0 deletions contracts/paymaster/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
*/target/

# The mxpy output
/output*/
23 changes: 23 additions & 0 deletions contracts/paymaster/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "paymaster"
version = "0.0.0"
authors = [ "MultiversX <[email protected]>" ]
edition = "2018"
publish = false
readme = "README.md"


[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.43.4"

[dev-dependencies.adder]
path = "../adder"
40 changes: 40 additions & 0 deletions contracts/paymaster/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Paymaster SC

## Overview

**Definition of problem**: the user needs to have a gas token (eGLD in case of MultiversX) to be able to do anything on the blockchain.
This is a hard concept to explain to non-crypto users.
Also gives some headaches for crypto users as well for the first steps of onboarding.
Imagine those people who bridge their NFTs or those who bridge USDC to a new account.
The new account can’t do anything until it does not have some eGLD to pay for the transactions.

In order for users to onboard faster to xPortal we introduced the concept of relayed transactions (we have v1, v2 and soon v3 of it).
This means a relayer can wrap the user transaction inside a relayedTX and in this case the relayer pays for the gas, but the user’s transaction is getting executed.
Right now, the relayers we use are free of charge, thus they do this service for new users for free, but they actually need eGLD to do the transactions.
When we speak about a few hundred users, this is not an issue, but when you want to scale up relaying transactions to thousands/millions of users, it becomes unsustainable.

## Implementation

Paymaster is a SC that makes relayed transactions sustainable.
This means that a user who doesn't own EGLD (native token) can make transactions by paying a fee in another token.

The Paymaster's objective is twofold:
- take a fee and send it to the Relayers;
- execute what the user wants to be executed.


The contract has only one endpoint: `forward_execution` and can be called by anyone.
The user will use `MultiESDTNFTTransfer` support to send multiple payments:
- first payment is always the fee that will be sent to the Relayer;
- rest of the payments will be what users want to send.

One example of userTX is:
```
MultiESDTNFTTransfer@paymasterSCAddr@feeTokenID@nonce@value@listofOther(tokenID,nonce,value)@forwardExecution@relayerAddr@destination@endpoint@extraArguments

```

After sending the Relayer the fee, `forward_execution` endpoint will make an *asynchronous call* to the destination.
The destionation can be a user or a smart contract.

We register a callback to the *asynchronous call*. In case of failure the paymaster SC sends the tokens back to the user.
14 changes: 14 additions & 0 deletions contracts/paymaster/meta/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 3 additions & 0 deletions contracts/paymaster/meta/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
multiversx_sc_meta::cli_main::<paymaster::AbiProvider>();
}
3 changes: 3 additions & 0 deletions contracts/paymaster/multiversx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"language": "rust"
}
18 changes: 18 additions & 0 deletions contracts/paymaster/mxsc-template.toml
Original file line number Diff line number Diff line change
@@ -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",
]
39 changes: 39 additions & 0 deletions contracts/paymaster/scenarios/empty.scen.json
Original file line number Diff line number Diff line change
@@ -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": "*"
}
}
]
}
53 changes: 53 additions & 0 deletions contracts/paymaster/src/forward_call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
multiversx_sc::imports!();

pub type PaymentsVec<M> = ManagedVec<M, EsdtTokenPayment<M>>;

static ERR_CALLBACK_MSG: &[u8] = b"Error received in callback:";
pub const ESDT_TRANSFER_FUNC_NAME: &str = "ESDTTransfer";
#[multiversx_sc::module]
pub trait ForwardCall {
fn forward_call(
&self,
dest: ManagedAddress,
endpoint_name: ManagedBuffer,
endpoint_args: MultiValueEncoded<ManagedBuffer>,
payments: PaymentsVec<Self::Api>,
) {
let original_caller = self.blockchain().get_caller();

self.send()
.contract_call::<()>(dest, endpoint_name)
.with_raw_arguments(endpoint_args.to_arg_buffer())
.with_multi_token_transfer(payments)
.async_call()
.with_callback(self.callbacks().transfer_callback(original_caller))
.call_and_exit();
}

#[callback]
fn transfer_callback(
&self,
original_caller: ManagedAddress,
#[call_result] result: ManagedAsyncCallResult<MultiValueEncoded<ManagedBuffer>>,
) -> MultiValueEncoded<ManagedBuffer> {
let initial_payments = self.call_value().all_esdt_transfers();

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
}
}
}
}
39 changes: 39 additions & 0 deletions contracts/paymaster/src/paymaster.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#![no_std]

multiversx_sc::imports!();

pub mod forward_call;
const FEE_PAYMENT: usize = 0;

/// An empty contract. To be used as a template when starting a new contract from scratch.
#[multiversx_sc::contract]
pub trait PaymasterContract: forward_call::ForwardCall {
#[init]
fn init(&self) {}

#[endpoint(forwardExecution)]
#[payable("*")]
fn forward_execution(
&self,
relayer_addr: ManagedAddress,
dest: ManagedAddress,
endpoint_name: ManagedBuffer,
endpoint_args: MultiValueEncoded<ManagedBuffer>,
) {
let payments = self.call_value().all_esdt_transfers();
require!(!payments.is_empty(), "There is no fee for payment!");

let fee_payment = payments.get(FEE_PAYMENT);
self.send().direct_esdt(
&relayer_addr,
&fee_payment.token_identifier,
0,
&fee_payment.amount,
);
Comment on lines +26 to +32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you check that the fee is equal to some amount?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is checked at the relayer level.


let mut payments_without_fee = payments.clone_value();
payments_without_fee.remove(FEE_PAYMENT);

self.forward_call(dest, endpoint_name, endpoint_args, payments_without_fee);
}
}
Loading
Loading