50675 sc insight re entrant eth refund can emit mismatched shares in deposit event

Submitted on Jul 27th 2025 at 12:11:56 UTC by @Paludo0x for Attackathon | Plume Network

  • Report ID: #50675

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/immunefi-team/attackathon-plume-network-nucleus-boring-vault/blob/main/src/base/Roles/TellerWithMultiAssetSupportPredicateProxy.sol

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Vulnerability Details

TellerWithMultiAssetSupportPredicateProxy::depositAndBridge() calculates the number of shares to log after calling teller.depositAndBridge().

During that external call the Teller may refund surplus ETH to the proxy; the proxy’s receive() immediately forwards that ETH to lastSender with a raw call. This opens a re-entrancy window before the share amount is computed for the Deposit event.

A malicious user could manipulate the rate inside that window, causing the event to report a share value different from the amount actually minted.

Impact Details

Severity is set to Low because there is no direct fund loss.

However, because this is an RWA protocol, many off-chain services may rely on emitted events. There's a secondary risk of mis-accounting, compliance violations, and potential business/legal exposure if RWA reporting relies solely on events.

1

Compute the shares value before the external call.

2

Have Teller:depositAndBridge() return the minted shares value, and use that returned value for the event emission.

Proof of Concept

Relevant proxy code and explanation (expand)

This is TellerWithMultiAssetSupportPredicateProxy::depositAndBridge() relevant code:

When calling teller.depositAndBridge{ value: msg.value }(...) the native tokens are sent to the teller, which will bridge the message and pass msg.value to the bridging function.

Excerpt from MultiChainLayerZeroTellerWithMultiAssetSupport:

LayerZero (and other bridges) can return excess native tokens to the sender. In this case the proxy's receive() forwards ETH immediately to lastSender:

```solidity receive() external payable { // If we have a lastSender and receive ETH, forward it if (lastSender != address(0) && msg.value > 0) { // Forward the ETH to the last sender (bool success,) = lastSender.call{ value: msg.value }(""); if (!success) revert TellerWithMultiAssetSupportPredicateProxy__ETHTransferFailed(); } } ```

If lastSender is a smart contract, its receive() can re-enter and manipulate state (e.g., change a rate provider or another component) before the proxy calculates shares and emits the Deposit event, causing the event to contain an incorrect shares value.

Notes

  • All links and code snippets are preserved as in the original report.

  • No additional assertions or claims are added beyond the original content.

Was this helpful?