For a long time, many Move developers have operated under a simple assumption: reentrancy is primarily a Solidity problem, and Move largely avoids it by design.

That assumption was broadly accurate for earlier versions of Move. Static module dependencies, strict resource semantics, and VM-level protections made the classic Solidity attack pattern difficult to reproduce: an external call re-enters a function before its previous execution has finished, allowing the same state to be manipulated repeatedly.

With the introduction of Function Values in Aptos Move 2.2, however, that security model became more nuanced.

Function Values allow functions to be passed around and invoked dynamically. This makes a call path like the following possible:

Module A → Module B → Function Value callback → Module A

Module A can pass a callback to Module B, and B can execute that callback before A's original execution has completed. In other words, execution can re-enter Module A while the first invocation is still active.

At first glance, this looks very similar to traditional reentrancy. But there is an important difference.

Move Still Has Resource Locking

When a module is re-entered through a Function Value, the Move VM restricts access to resources defined by that module.

For example, if Module A is already active and the callback re-enters A, another attempt to access A's own resource may look like this:

borrow_global<Balance>(addr);

move_from<Balance>(addr);

If Balance is defined by the re-entered module, the VM's resource-locking rules can prevent the second access and abort the transaction.

This means Function Values do not simply reintroduce the classic Solidity pattern where an attacker repeatedly withdraws funds before a balance is updated.

The more interesting security boundary appears one layer deeper.

Resource Locking Does Not Protect Every Piece of State

Resource locking protects resources belonging to the module being re-entered. It does not automatically freeze every external state dependency involved in the transaction.

Consider a function in Module A with the following execution flow:

Check external state → Execute user-controlled callback → Continue using the earlier check

Now assume that the callback can modify a resource managed by another module, or otherwise change external state that Module A relies on.

A simplified flow might look like this:

1. Module A checks that the user's limit is 100.

2. Module A executes an external Function Value.

3. The callback modifies state related to that limit.

4. Execution returns to Module A.

5. Module A continues using the result from step 1.

The problem is no longer simply “the same balance was withdrawn twice.”

Instead, the issue becomes a combination of reentrancy, cross-module state manipulation, and TOCTOU — Time of Check to Time of Use.

Module A made a security decision based on one version of the state, but by the time that decision is acted upon, the underlying state may have changed.

Function Values Expand the Trust Boundary

This becomes particularly important when a contract accepts arbitrary Function Values, callbacks, or hooks supplied by external callers.

In those cases, an audit should not stop at asking:

Can this module's own resources be accessed twice?

It should also ask:

  • What external state can change while the callback is executing?
  • Do permission, balance, price, or quota checks performed before the callback remain valid afterward?
  • Is the Function Value controlled by the caller?
  • Which modules can that callback invoke?
  • Which resources can those modules modify?
  • Does the original function rely on any state that should be revalidated after control returns?

Where security-sensitive logic depends on mutable external state, critical conditions may need to be checked again at the point of use rather than assuming that a pre-callback validation remains valid.

The Security Model Has Changed, Not Disappeared

The right conclusion is not:

“Move now has the same reentrancy problem as Solidity.”

That would be misleading.

Move's VM and resource model still prevent many forms of classic reentrancy. What Function Values change is the boundary of what developers and auditors need to reason about.

Once Function Values, external resources, and pre-callback state checks appear in the same execution path, reentrancy-related state changes and TOCTOU conditions become part of the threat model.

For Move developers, the key question is no longer only whether a module can be re-entered.

It is also:

What can change while control is outside the module, and are the assumptions made before that external execution still valid when control returns?

That is the security boundary Function Values have introduced into modern Aptos Move.

At MoveBit, we review not only isolated functions, but also the state transitions and trust boundaries created by callbacks, Function Values, and cross-module interactions.

If you're building on Aptos or other Move-based ecosystems, these are exactly the kinds of edge cases that deserve attention before deployment.

MoveBit Research