Skip to content

Commit

Permalink
Apply suggestions from code review
Browse files Browse the repository at this point in the history
wording, typos, clarification

Co-authored-by: Ovidiu Olteanu <88948634+ovidiuolteanu@users.noreply.github.com>
  • Loading branch information
andrei-marinica and ovidiuolteanu authored Sep 29, 2023
1 parent a8651d2 commit c77db56
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 23 deletions.
36 changes: 19 additions & 17 deletions docs/developers/developer-reference/sc-contract-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ The description of a smart contract's inputs is known as the [ABI](/developers/d

Smart contracts are regularly written in Rust, and the framework makes sure to always also generate an ABI alongside the contract binary. However, since we also want to write the contract call in Rust, we don't usually need the ABI (which is a JSON file), it is much more useful to have some code generated for us to help us call the contract in Rust directly.

We call this contract call helper a __proxy__. All it does is that it provides a typed interface to any Rust program, it takes the typed arguments, and it serializes them according to the [MultiversX serialization format](/developers/data/serialization-overview).
We call this contract call helper a __proxy__. All it does is that it provides a typed interface to any Rust program, it takes the typed arguments and it serializes them according to the [MultiversX serialization format](/developers/data/serialization-overview).

Let's take this very simple example:

Expand Down Expand Up @@ -77,7 +77,7 @@ If the contract has modules that have functionality that you may want to call, y

If the modules are in different crates than the target contract (and if the target contract doesn't somehow re-export them), you'll also have to add the module to the dependencies, the same way you added the target contract.

These proxies are traits, just like the contracts themselves. The implementation is produces automatically, but nonetheless, this means that in order to call them, the proxy trait must be in scope. This is why you will see such imports everywhere that proxies are called:
These proxies are traits, just like the contracts themselves. The implementation is produced automatically, but nonetheless, this means that in order to call them, the proxy trait must be in scope. This is why you will see such imports everywhere these proxies are called:

```rust
use module_namespace::ProxyTrait as _;
Expand Down Expand Up @@ -118,7 +118,7 @@ We'll talk about `async_call` and `call_and_exit` later on.
:::caution
Importing a smart contract crate only works if both contracts use the exact framework version. Otherwise, the compiler will (rightfully) complain that the interfaces do not perfectly match.

In case the target contract is not under our control, it is often wiser to just write the proxy of interest by hand.
In case the target contract is not under our control, it is often wiser to just manually compose the proxy of interest.
:::


Expand All @@ -127,7 +127,7 @@ In case the target contract is not under our control, it is often wiser to just

### Manually specified proxies

If we don't want to have a dependency to the target contract crate, or there is no access to this crate altogether, it is always possible for us to create such a proxy by hand. This might also be desirable if the framework versions of the two contracts are different, or not under our control.
If we don't want to have a dependency to the target contract crate, or there is no access to this crate altogether, it is always possible for us to create such a proxy manually. This might also be desirable if the framework versions of the two contracts are different, or not under our control.

Below we have an example of such a proxy:

Expand Down Expand Up @@ -165,7 +165,7 @@ fn contract_proxy(&self, sc_address: ManagedAddress) -> callee_proxy::Proxy<Self

### No proxy

The point of the proxies is to help us build contract calls in a type-safe manner. But this is by no means compulsory. Sometimes we specifically want to build contract calls by hand and serialize the arguments ourselves.
The point of the proxies is to help us build contract calls in a type-safe manner. But this is by no means compulsory. Sometimes we specifically want to build contract calls manually and serialize the arguments ourselves.

The point is to create a `ContractCallNoPayment` object. We'll discuss how to add the payments later.

Expand Down Expand Up @@ -208,7 +208,7 @@ graph LR

## Contract calls: payments

Now that we specified the recipient address, the function, and the arguments, it is time to add more configurations: token transfers and gas.
Now that we specified the recipient address, the function and the arguments, it is time to add more configurations: token transfers and gas.

Let's assume we want to call a `#[payable]` endpoint, with this definition:

Expand Down Expand Up @@ -236,23 +236,23 @@ self.contract_proxy(callee_sc_address)

Note that this method returns a new type of object, `ContractCallWithEgld`, instead of `ContractCallNoPayment`. Having multiple contract call types has multiple advantages:
- We can restrict at compile time what methods are available in the builder. For instance, it is possible to add ESDT transfers to `ContractCallNoPayment`, but not to `ContractCallWithEgld`. We thus no longer need to enforce at runtime the restriction that EGLD and ESDT cannot coexist. This restriction is also more immediately obvious to developers.
- The contracts end up being smaller, because the compiler knows which kinds of transfers occur in the contract, and which do not. For instance, if a contract only ever transfers EGLD, there is not need for the code that prepares ESDT transfers in the contract. If the check had been doneonly at runtime, this optimisation would not have been possible.
- The contracts end up being smaller, because the compiler knows which kinds of transfers occur in the contract, and which do not. For instance, if a contract only ever transfers EGLD, there is not need for the code that prepares ESDT transfers in the contract. If the check had been done only at runtime, this optimisation would not have been possible.


[comment]: # (mx-context-auto)

### ESDT transfers

On the MultiversX blockchain, you can transfer multiple ESDT tokens at once. We creates a single ESDT transfer type, which works for both single- and multi-transfers. It is called `ContractCallWithMultiEsdt`.
On the MultiversX blockchain, you can transfer multiple ESDT tokens at once. We create a single ESDT transfer type which works for both single- and multi-transfers. It is called `ContractCallWithMultiEsdt`.

We can obtain such and object, by starting with a `ContractCallNoPayment`, and calling `with_esdt_transfer` once, or several times. The first such call will yield the `ContractCallWithMultiEsdt`, subsequent calls simply add more ESDT transfers.
We can obtain such and object by starting with a `ContractCallNoPayment` and calling `with_esdt_transfer` once, or several times. The first such call will yield the `ContractCallWithMultiEsdt`, while subsequent calls simply add more ESDT transfers.

:::info A note on arguments
There is more than one way to provide the arguments to `with_esdt_transfer`:
- as a tuple of the form `(token_identifier, nonce, amount)`;
- as a `EsdtTokenPayment` object.

They contain the same data, sometimes it is more convenient to use one, sometimes the other.
They contain the same data, but sometimes it is more convenient to use one, sometimes the other.
:::

Example:
Expand Down Expand Up @@ -335,7 +335,7 @@ Not all contract calls require explicit specification of the gas limit, leaving
- async calls will halt execution and consume all the remaining gas, so specifying the gas limit is not necessary for them;
- synchronous calls will by default simply use all the available gas as the upper limit, since unspent gas is returned to the caller anyway.

On the other hand,promises and transfer-execute calls do require gas to be explicitly specified.
On the other hand, promises and transfer-execute calls do require gas to be explicitly specified.


---
Expand Down Expand Up @@ -579,11 +579,11 @@ The differences are:

### Transfer-execute

Transfer-execute calls are similar to asynchronous calls, but they can have no callback, and thus the caller cannot react in any way to what happens with the callee.
Transfer-execute calls are similar to asynchronous calls, but they can have no callback, thus the caller cannot react in any way to what happens with the callee.

Just like promises, there can be multiple such calls launched from a transaction, but unlike promises, they are alreay available on mainnet.
Just like promises, there can be multiple such calls launched from a transaction, but unlike promises, these are already available on mainnet.

Transfer-execute calls do not need any further configuration (other than explicit an explicit gas limit), and therefore there is no specific object type associated with them. They can be launched immediately:
Transfer-execute calls do not need any further configuration (other than an explicit gas limit), therefore there is no specific object type associated with them. They can be launched immediately:

```rust
self.contract_proxy(callee_sc_address)
Expand All @@ -598,9 +598,11 @@ self.contract_proxy(callee_sc_address)

### Synchronous calls

Synchronous calls are executed inline: this means execution is interrupted while they are executed, and resumed afterwards. We also get the result of the execution right away and we can use it immediately in the transaction.
Synchronous calls are executed inline: this means execution is interrupted while they are executed and resumed afterwards. We also get the result of the execution right away and we can use it immediately in the transaction.

The catch is that synchronous calls can only be sent to contracts in the __same shard__ as the caller. They will fail otherwise.
:::caution
Synchronous calls can only be sent to contracts in the __same shard__ as the caller. They will fail otherwise.
:::

Synchronous calls also do not need any further configuration, the call is straightforward:

Expand Down Expand Up @@ -722,7 +724,7 @@ flowchart TB

The object encoding these calls is called `ContractDeploy`. Unlike the contract calls, there is a single such object.

Creating this object is done in a similar fashion: either via proxies, or by hand. Constructors in proxies naturally produce `ContractDeploy` objects:
Creating this object is done in a similar fashion: either via proxies, or manually. Constructors in proxies naturally produce `ContractDeploy` objects:

```rust
mod callee_proxy {
Expand Down
12 changes: 6 additions & 6 deletions docs/developers/developer-reference/sc-payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Additional restrictions on the incoming tokens can be imposed in the body of the
## Sending payments


We have seen how how contracts can accomodate receiving tokens. Sending them is, in principle, even more straightforward, as it only involves calling methods from the `self.send()` component.
We have seen how contracts can accomodate receiving tokens. Sending them is, in principle, even more straightforward, as it only involves calling methods from the `self.send()` component.


[comment]: # (mx-context-auto)
Expand All @@ -105,13 +105,13 @@ Contracts can send tokens directly, without calling any endpoint at the destinat

The following methods will perform a transfer-execute call. This is a type of asynchronous call that does not provide a callback, or any other feedback from the receiver. For direct transferring of funds, this call type is ideal.

- `self.send().direct_egld(to, amount)` sends EGLD directly to an address.
- `self.send().direct_egld(to, amount)` sends EGLD directly to an address. If the amount is zero, execution will fail.
- `self.send().direct_non_zero_egld(to, amount)` sends EGLD directly to an address, if the amount is non-zero. Does nothing otherwise.
- `self.send().direct(to, token, nonce, amount)` sends EGLD or a single ESDT token directly to an address. The `token` argument is of type `EgldOrEsdtTokenIdentifier`.
- `self.send().direct(to, token, nonce, amount)` sends EGLD or a single ESDT token directly to an address. The `token` argument is of type `EgldOrEsdtTokenIdentifier`. If the amount is zero, execution will fail.
- `self.send().direct_non_zero(to, token, nonce, amount)` sends EGLD or a single ESDT token directly to an address, if the amount is non-zero. Does nothing otherwise.
- `self.send().direct_esdt(to, token, nonce, amount)` sends a single ESDT token directly to an address.
- `self.send().direct_esdt(to, token, nonce, amount)` sends a single ESDT token directly to an address. If the amount is zero, execution will fail.
- `self.send().direct_non_zero_esdt_payment(to, payment)` sends a single ESDT token directly to an address, if the amount is non-zero. Does nothing otherwise.
- `self.send().direct_multi(to, payments)` sends one or more ESDT tokens to destination. The `payments` argument is a list of such payments.
- `self.send().direct_multi(to, payments)` sends one or more ESDT tokens to destination. The `payments` argument is a list of such payments. If at least one of the amounts is zero, execution will fail.

It is also possible to transfer tokens via an async call:
- `self.send().transfer_esdt_via_async_call(to, token, nonce, amount)`
Expand All @@ -127,7 +127,7 @@ Sending tokens to a contract endpoint is a contract call, and we have a [type-sa

Even if you do not know what the endpoint or the arguments will be at compile time, we still recommend creating a `ContractCall` first, and then decorating and sending it, as in [these examples](/developers/developer-reference/sc-contract-calls#no-proxy).

There are, howver, a few direct API methods. Just to mention them:
However, a few direct API methods exist. Just to mention them:
- `self.send().direct_esdt_with_gas_limit(to, token_identifier, nonce, amount, gas, endpoint_name, arguments)`
- `self.send().direct_non_zero_esdt_with_gas_limit(to, token_identifier, nonce, amount, gas, endpoint_name, arguments)`
- `self.send().direct_with_gas_limit(to, token, nonce, amount, gas, endpoint_name, arguments)`
Expand Down

0 comments on commit c77db56

Please sign in to comment.