Docs

Docs

  • Develop
  • Validate
  • Integrate
  • Learn

›Developer reference

Welcome to Elrond

  • Welcome to Elrond

Technology

  • Architecture Overview
  • Glossary
  • Entities
  • Chronology
  • Secure Proof of Stake
  • Adaptive State Sharding
  • The Elrond WASM VM
  • Cross Shard Transactions

Wallet

  • Wallets - Overview
  • Web Wallet
  • Maiar Web Wallet Extension
  • Webhooks
  • Ledger

Tokens

  • Native Tokens
  • ESDT tokens
  • NFT tokens

Validators

  • Validators - Overview
  • System Requirements
  • Install a Mainnet Node

    • Scripts & User config
    • Installing a Validator Node
    • Optional Configurations
    • How to use the Docker Image

    Install a Testnet/Devnet Node

    • Scripts & User config
    • Installing a Validator Node
    • Manage a validator node
    • How to use the Docker Image

    Manage your keys

    • Validator Keys
    • Wallet Keys
    • Protecting your keys

    Staking, Unstaking, Unjailing

    • Staking, unstaking and unjailing
    • Staking
    • Unjailing
    • The Staking Smart Contract
  • The Delegation Manager
  • Convert An Existing Validator Into A Staking Pool
  • Merging A Validator Into An Existing Delegation Smart Contract
  • Rating
  • Elrond Node upgrades
  • Node redundancy
  • Import DB
  • Node CLI
  • Node Databases
  • Useful Links & Tools
  • FAQs

Developers

  • Developers - Overview
  • Tutorials

    • Build a dApp in 15 minutes
    • Build a Microservice for your dApp
    • The Crowdfunding Smart Contract (part 1)
    • The Crowdfunding Smart Contract (part 2)
    • The Counter Smart Contract
    • Custom Wallet Connect

    Signing Transactions

    • Signing Transactions
    • Tools for signing
    • Signing programmatically

    Gas and Fees

    • Overview
    • EGLD transfers (move balance transactions)
    • System Smart Contracts
    • User-defined Smart Contracts

    Developer reference

    • The Elrond Serialization Format
    • Smart contract annotations
    • Smart contract modules
    • Smart contract to smart contract calls
    • Smart Contract Developer Best Practices
    • Code Metadata
    • Smart Contract API Functions
    • Storage Mappers
    • Rust Testing Framework
    • Rust Testing Framework Functions Reference
    • Rust Smart Contract Debugging
    • Random Numbers in Smart Contracts

    Developers Best Practices

    • Basics
    • BigUint Operations
    • The dynamic allocation problem
    • Multi-values

    Mandos tests reference

    • Mandos Overview
    • Mandos Structure
    • Mandos Simple Values
    • Mandos Complex Values
    • Embedding Mandos code in Go
  • Constants
  • Built-In Functions
  • Account storage
  • Setup a Local Testnet
  • Set up a Local Testnet (advanced)
  • Creating Wallets

SDK and Tools

  • SDKs and Tools - Overview
  • REST API

    • REST API overview
    • api.elrond.com
    • Gateway overview
    • Addresses
    • Transactions
    • Network
    • Nodes
    • Blocks
    • Virtual Machine
    • Versions and Changelog
  • Proxy
  • Elasticsearch
  • erdpy

    • erdpy
    • Installing erdpy
    • Configuring erdpy
    • erdpy CLI
    • Deriving the Wallet PEM file
    • Sending bulk transactions
    • Writing and running erdpy scripts
    • Smart contract interactions

    erdjs

    • erdjs
    • Cookbook
    • Extending erdjs
    • Writing and testing interactions
    • Migration guides
    • Signing Providers for dApps
  • erdgo
  • erdcpp
  • erdjava
  • erdkotlin
  • erdwalletjs-cli

Integrators

  • Integrators - Overview
  • EGLD integration guide
  • ESDT tokens integration guide
  • Observing Squad
  • Accounts Management
  • Creating Transactions
  • Querying the Blockchain

Smart contract to smart contract calls

Introduction

This guide provides some examples on how to call a contract from another contract. More examples can be found in the contract composability feature tests.

There are three ways of doing these calls:

  • importing the callee contract's source code and using the auto-generated proxy (recommended)
  • writing the proxy manually
  • manually serializing the function name and arguments (not recommended)

Method #1: Importing the contract

If you have access to the callee contract's code, importing the auto-generated proxy is easy. Simply import the contract (and any other modules the contract itself may use):

[dependencies.contract-crate-name]
path = "relative-path-to-contract-crate"

If you want to use endpoints contained in an external module (i.e. in a different crate than the main contract) that the callee contract imports, you'll also have to add the module to the dependencies, the same way you added the main contract.

Additionally, in your caller code, you have to add the following import:

use module_namespace::ProxyTrait as _;

If you use the rust-analyser VSCode extension, it might complain that it can't find this, but if you actually build the contract, the compiler can find it just fine.

Once you've imported the contract and any external modules it might use, you have to declare your proxy creator function:

#[proxy]
fn contract_proxy(&self, sc_address: ManagedAddress) -> contract_namespace::Proxy<Self::Api>;

This function creates an object that contains all the endpoints of the callee contract, and it handles the serialization automatically.

Let's say you have the following endpoint in the contract you wish to call:

#[endpoint(myEndpoint)]
fn my_endpoint(&self, arg: BigUint) -> BigUint {
    // implementation
}

To call this endpoint, you would do this in the caller contract:

let biguint_result = self.contract_proxy(callee_sc_address)
    .my_endpoint(my_biguint_arg)
    .execute_on_dest_context();

This performs a synchronous call to the callee_sc_address contract, with the my_biguint_arg used as input for arg: BigUint. Notice how you also don't have to specify the myEndpoint name either. It's handled automatically.

After performing this call, you can execute some more code in the caller contract, using biguint_result as you wish.

NOTE: Keep in mind that this only works for same-shard contracts. If the contracts are in different shards, you have to use async-calls or transfer-and-execute.

Types of Contract to Contract calls

There are two main types of contract-to-contract calls available at the moment:

  • synchronous, same-shard calls, through execute_on_dest_context (as demonstrated above)
  • asynchronous calls

Asynchornous calls

Asynchronous calls can be launched either through transfer_execute (in the case you don't care about the result) or through async_call when you want to save the result from the callee contract or perform some additional computation. Keep in mind logic in callbacks should be kept at a minimum, as they usually receive very little gas to perform their duty.

To launch a transfer and execute call using the above described proxy, you can simply replace execute_on_dest_context method with the transfer_execute method. Keep in mind that you can't get the returned BigUint in this case.

If instead you want to launch an async call, you have to use the async_call method, and use the call_and_exit() method on the returned object.

Using the above example, your async call would look like this:

#[endpoint]
fn caller_endpoint(&self) {
    // other code here

    self.contract_proxy(callee_sc_address)
        .my_endpoint(my_biguint_arg)
        .async_call()
        .call_and_exit();
}

Callbacks

If you want to perform some logic based on the result of the async call, or just some cleanup after the call, you have to declare a callback function. For example, let's say we want to do something based if the result is even, and something else if the result is odd, and do some cleanup in case of error. Our callback function would look something like this:

#[callback]
fn my_endpoint_callback(
    &self,
    #[call_result] result: ManagedAsyncCallResult<BigUint>
) {
    match result {
        ManagedAsyncCallResult::Ok(value) => {
            if value % 2 == 0 {
                // do something
            } else {
                // do something else
            }
        },
        ManagedAsyncCallResult::Err(err) => {
            // log the error in storage
            self.err_storage().set(&err.err_msg);
        },
    }
}

To assign this callback to the aforementioned async call, we hook it like this:

#[endpoint]
fn caller_endpoint(&self) {
    // other code here

    self.contract_proxy(callee_sc_address)
        .my_endpoint(my_biguint_arg)
        .async_call()
        .with_callback(self.callbacks().my_endpoint_callback())
        .call_and_exit();
}

Even though, in theory, smart contract can only have ONE callback function, the Rust framework handles this for you by saving an ID for the callback function in storage when you fire the async call, and it knows how to retrieve the ID and call the correct function once the call returns.

Callback Arguments

Your callback may have additional arguments that are given to it at the time of launching the async call. These will be automatically saved before performing the initial async call, and they will be retrieved when the callback is called. Example:

#[callback]
fn my_endpoint_callback(
    &self,
    original_caller: ManagedAddress,
    #[call_result] result: ManagedAsyncCallResult<BigUint>
) {
    match result {
        ManagedAsyncCallResult::Ok(value) => {
            if value % 2 == 0 {
                // do something
            } else {
                // do something else
            }
        },
        ManagedAsyncCallResult::Err(err) => {
            // log the error in storage
            self.err_storage().set(&err.err_msg);
        },
    }
}

To assign this callback to the aforementioned async call, we hook it like this:

#[endpoint]
fn caller_endpoint(&self) {
    // other code here
    let caller = self.blockchain().get_caller();

    self.contract_proxy(callee_sc_address)
        .my_endpoint(my_biguint_arg)
        .async_call()
        .with_callback(self.callbacks().my_endpoint_callback(caller))
        .call_and_exit();
}

Notice how the callback now has an argument:

self.callbacks().my_endpoint_callback(caller)

You can then use original_caller in the callback like any other function argument.

Payments in proxy arguments

Let's say you want to call a #[payable] endpoint, with this definition:

#[payable("*")]
#[endpoint(myEndpoint)]
fn my_payable_endpoint(&self, arg: BigUint) -> BigUint {
    let payment = self.call_value().egld_or_single_esdt();
    // implementation
}

To pass the payment, you can use the with_egld_or_single_esdt_token_transfer method:

#[endpoint]
fn caller_endpoint(&self, token: EgldOrEsdtTokenIdentifier, nonce: u64, amount: BigUint) {
    // other code here

    self.contract_proxy(callee_sc_address)
        .my_endpoint(token, nonce, amount, my_biguint_arg)
        .with_egld_or_single_esdt_token_transfer(token, nonce, amount)
        .async_call()
        .call_and_exit();
}

with_egld_or_single_esdt_token_transfer allows adding EGLD payment of a single ESDT token as payment.

There are similar functions for other types of payments:

  • add_esdt_token_transfer - for single ESDT transfers
  • with_egld_transfer - for EGLD transfers
  • with_multi_token_transfer - for ESDT multi-transfers

Payments in callbacks

If you expect to receive a payment instead of paying the contract, keep in mind callback functions are #[payable] by default, so you don't need to add the annotation:

#[callback]
fn my_endpoint_callback(&self, #[call_result] result: ManagedAsyncCallResult<BigUint>) {
    let payment = self.call_value().egld_or_single_esdt();

    match result {
        ManagedAsyncCallResult::Ok(value) => {
            if value % 2 == 0 {
                // do something
            } else {
                // do something else
            }
        },
        ManagedAsyncCallResult::Err(err) => {
            // log the error in storage
            self.err_storage().set(&err.err_msg);
        },
    }
}

Keep in mind you do NOT need to specify the payments when hooking the callback:

#[endpoint]
fn caller_endpoint(&self) {
    // other code here

    self.contract_proxy(callee_sc_address)
        .my_endpoint(my_biguint_arg)
        .async_call()
        .with_callback(self.callbacks().my_endpoint_callback())
        .call_and_exit();
}

Gas limit for execution

with_gas_limit allows you to specify a gas limit for your call. By default, all gas left is passed, and any remaining is returned either for further execution (in case of sync calls) or for callback execution (for async calls).

Method #2: Manually writing the proxy

Sometimes you don't have access to the callee contract code, or it's simply inconvenient to import it (different framework versions, for instance). In this case, you're going to have to manually declare your proxy. Let's use the same example endpoint as in the first method:

mod callee_proxy {
    elrond_wasm::imports!();

    #[elrond_wasm::proxy]
    pub trait CalleeContract {
        #[payable("*")]
        #[endpoint(myEndpoint)]
        fn my_payable_endpoint(&self, arg: BigUint) -> BigUint;
    }
}

This is the only thing you'll have to do differently. The rest is the same, you declare a proxy builder, and the calls are all exactly the same as in method #1.

#[proxy]
fn contract_proxy(&self, sc_address: ManagedAddress) -> callee_proxy::Proxy<Self::Api>;

Method #3: Manual calls (NOT recommended)

If for some reason you don't want to use the contract proxies, you can create a ContractCall object by hand:

let mut contract_call = ContractCall::new(
    self.api,
    dest_sc_address,
    ManagedBuffer::new_from_bytes(endpoint_name),
);

Where dest_sc_address is the address of the callee contract, and endpoint_name would be b"myEndpoint".

From here, you would use contract_call.push_endpoint_arg(&your_arg) and the manual payment adder functions described in method #1 miscellaneous category.

You would then use the same execute_on_dest_context, transfer_execute and async_call methods as in the automatically built ContractCall objects from before.

Only use this method if you REALLY have to, as it's very easy to make mistakes if you work with low-level code like this.

Conclusion

We hope this covers all the questions about how to call another contract from your contract. Use method #1 whenever possible, and method #2 if needed. Avoid method #3 as much as possible.

← Smart contract modulesSmart Contract Developer Best Practices →
  • Introduction
  • Method #1: Importing the contract
  • Types of Contract to Contract calls
    • Asynchornous calls
    • Callbacks
    • Callback Arguments
    • Payments in proxy arguments
    • Payments in callbacks
    • Gas limit for execution
    • Method #2: Manually writing the proxy
    • Method #3: Manual calls (NOT recommended)
  • Conclusion
Made withby the Elrond team.
GithubChat
Main siteWalletExplorerBridgeDocsGrowthMaiarMaiar Exchange