“Move for Solidity Developers” is a series created for experienced Solidity developers who already have some knowledge of Move. It helps you shift from writing Solidity contracts to developing on Move-based blockchains, such as Aptos and Sui.
Let’s dig into the concept of cross-contract calls and examine the distinctions between Solidity and Move contracts in this area. We will assess the mechanisms and security of executing cross-contract calls in Move, helping developers better understand how to manage contract interactions within the Move environment.
Understanding Cross-Contract Call
Conceptual Overview
A Cross-Contract Call occurs when one smart contract (Contract A) intentionally executes a function or accesses data residing within a different, separate smart contract (Contract B) during its execution. This interaction happens on-chain via predefined function calls. For instance, in the following code, the Caller contract invokes the setValueAndReceiveEther function on calleeAddress to set a value and simultaneously send ETH.

Terminology Alignment
In the Move ecosystem (e.g., Aptos, Sui), the direct equivalent of a Solidity contract is a module. A module bundles related functions and data types. Therefore, a "cross-contract call" is known as a cross-module call in Move. This occurs when a function in one module invokes a public or public(package) function in another. For example, in Move, this might involve calling coin::transfer to send tokens.
Solidity Cross-Contract Calls
Solidity primarily enables interaction with other contracts using higher-level interface-based calls and low-level address operations:
InterfaceName(targetAddress).functionName(arguments): The standard way to interact with known, well-defined external contracts.address.call{value:amount,gas:gasAmount}(bytes memory payload): The most fundamental and flexible method. Requires manual encoding (abi.encodeWithSignature/Selector) of the function call and decoding of the return data.address.delegatecall{gas:gasAmount}(bytes memory payload): Primarily used for implementing upgradeable proxy patterns or "library" patterns where reusable logic needs to manipulate the state of the calling contract.address.staticcall{gas:gasAmount}(bytes memory payload): Reading state or calling pure/view functions on external contracts without risk of state modification.
Move Cross-Module Calls
How To Implement Cross-Module Call
Move fundamentally relies on direct module and function identification for cross-module interaction:
useImport: To import a module, you first configure your package'sTOMLfile:- Declare an Alias: In the
[addresses]section, assign a readable alias to the on-chain address. (Can be optional if already declared in the dependencies module’stomlfile). - Add the Dependency: List the dependencies module in the
[dependencies]section. - With the
Move.tomlfile set up, you can import the module with theusekeyword, for example,use example::moduleX.
- Declare an Alias: In the

- Direct Function Call: Once a module is imported (or if its path is fully qualified), its public functions are invoked directly using the syntax
ModuleName::function_name(arguments). - Example:coin::transfer<USD>(sender,receiver,amount);calls thetransferfunction in thecoinmodule, specifying the typeUSDTand passing arguments. - Passing References or Objects: Calls typically use
immutable (&)ormutable(&mut)references to pass parameters, allowing functions to read or modify resources based on the reference type. - No Implicit Caller Concept: Move does not have a global
msg.senderto identify the caller of a function. Instead, authorization can be handled explicitly by passing a&signer(in Aptos) or accessingctx.senderfrom the transaction context (in Sui).
Below are concrete examples of cross-module calls in Aptos Move and Sui Move. Each example implements a simple Calculator contract calling a Math contract to perform addition.
Aptos Move

- User call
calculator::calculate_sum(user,2,3) calculatorinvokesmath::add(2,3,user_addr)mathcontract usesborrow_global_mut(user_addr)to access user's resource and update thelast_resultfield

Sui Move

- User calls
calculator::calculate_sum(2,3,user_result) calculatorinvokesmath::add(2,3,user_result)mathcalculates the sum and stores it inuser_result

Ultimately, Move re-imagines how smart contracts interact by prioritizing compile-time safety and clarity over runtime flexibility. Unlike Solidity, where contracts communicate through address-based messages that can be dynamic and less predictable, Move requires direct, explicit links between code modules. This fundamental design choice shifts the developer's focus toward a more structured architecture where data permissions and access control are built into the code's foundation. As demonstrated by the different approaches in Aptos and Sui, this model treats interactions not as arbitrary messages but as well-defined function calls between trusted components, leading to a more predictable on-chain environment.
Hidden Traps in Inline/Macro Functions
Both Aptos and Sui Move have implemented inline-like functions, known as inline in Aptos and macro in Sui. During compilation, these functions are expanded at the caller's location. When module A calls an inline function from module B, the compiler directly inserts the function's body at the call site. This technique removes cross-module jumps in the generated bytecode, giving the impression that the execution is happening within the same module.
However, it's important to note that in both Aptos and Sui Move, inline serves only as a compiler optimization hint, copying the function body to the call site without altering the access restrictions on resources or objects.
In the Aptos example below, the sub function needs to access a private resource (UserResult) defined by the math module. However, calling this function from the calculator module will fail because it lacks permission to access the resource (UserResult), which is defined in the math.
Aptos Move

Logs:

In Sui Move, the macro function encounters a similar issue. The sub function in the example below is designed to update the value of the UserResult type, which is defined within the math module. Attempting to call the sub function from a different module will fail because the UserResult type is only accessible within the math module.
Sui Move

Logs:

Therefore, while inline functions can offer faster execution and potential gas savings, it is crucial for developers to understand their distinction from regular cross-module calls to prevent incorrect usage. Developers must remember that this feature is a compiler optimization and does not alter Move's fundamental access restrictions, which remain tied to the module where a type is defined.
Cross-Contract Call: Solidity vs Aptos Move vs Sui Move
Solidity's "cross-contract calls" and Move's "cross-module calls" both illustrate how different code units interact within their respective blockchain environments. However, these interactions are governed by distinct philosophies and mechanisms, highlighting key differences in their programming models. Below is a summary table comparing these approaches:
| Feature | Solidity Cross-Contract Call | Aptos Move Cross-Module Call | Sui Move Cross-Module Call |
|---|---|---|---|
| Storage Model | Contract-centric storage |
Account-centric storage |
Object-centric storage |
| Code Units | contract keyword |
module keyword |
module keyword |
| Invocation | Requires target address and interface/ABI |
Requires use statement to import modules related to function calls |
Requires use statement to import modules related to function calls |
| Permission Identifier | msg.sender (direct caller)/tx.origin(original initiator of the transaction) |
signer(function call verifier) |
tx_context::sender(original initiator of the transaction) |
| Coin Forwarding | Explicit {value: amount} syntax (payable functions) |
Function call with recipient and amount | Handled by passing Coin objects as arguments |
| Visibility | public, external, internal, private |
public, public(package), package, friend, public(friend), entry, private |
public, public(package), private, entry, friend(deprecated) |
With a solid understanding of cross-module calls, you are now equipped to implement more complex interactions within your Move applications. In the next article, we'll explore the protection mechanism of reentrancy calls in Move.

