🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Message Passing Concepts

Message passing is a system where a contract on one side sends a numbered message and a contract on the other side runs it once. Each message carries a nonce, which is a number used once to tell messages apart.

A first delivery stores its payload

The destination records the nonce as used and stores the payload. The payload is the message content in this small example.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract MessageInbox {
    address public trustedSender;
    uint256 public lastNonce;
    uint256 public lastPayload;
    mapping(uint256 => bool) public executed;
    constructor(address senderAddress) {
        trustedSender = senderAddress;
    }
    function deliver(uint256 nonce, uint256 payload) public {
        require(msg.sender == trustedSender, "unknown sender");
        require(!executed[nonce], "nonce already used");
        executed[nonce] = true;
        lastNonce = nonce;
        lastPayload = payload;
    }
}

A replayed nonce reverts

Delivering the same nonce a second time must fail, or the same action would run twice. The stored payload stays at the first value.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract MessageInbox {
    address public trustedSender;
    uint256 public lastNonce;
    uint256 public lastPayload;
    mapping(uint256 => bool) public executed;
    constructor(address senderAddress) {
        trustedSender = senderAddress;
    }
    function deliver(uint256 nonce, uint256 payload) public {
        require(msg.sender == trustedSender, "unknown sender");
        require(!executed[nonce], "nonce already used");
        executed[nonce] = true;
        lastNonce = nonce;
        lastPayload = payload;
    }
}

An unknown sender is rejected

A delivery from anyone except the trusted sender must fail. The nonce stays unused after the rejected call.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract MessageInbox {
    address public trustedSender;
    uint256 public lastNonce;
    uint256 public lastPayload;
    mapping(uint256 => bool) public executed;
    constructor(address senderAddress) {
        trustedSender = senderAddress;
    }
    function deliver(uint256 nonce, uint256 payload) public {
        require(msg.sender == trustedSender, "unknown sender");
        require(!executed[nonce], "nonce already used");
        executed[nonce] = true;
        lastNonce = nonce;
        lastPayload = payload;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract MessageSource {
    function sendMessage(MessageInbox inbox, uint256 nonce, uint256 payload) public {
        inbox.deliver(nonce, payload);
    }
}

contract MessageInbox {
    address public trustedSender;
    uint256 public lastNonce;
    uint256 public lastPayload;
    mapping(uint256 => bool) public executed;
    constructor(address senderAddress) {
        trustedSender = senderAddress;
    }
    function deliver(uint256 nonce, uint256 payload) public {
        require(msg.sender == trustedSender, "unknown sender");
        require(!executed[nonce], "nonce already used");
        executed[nonce] = true;
        lastNonce = nonce;
        lastPayload = payload;
    }
}

Key points

  • Message passing: Message passing is a system where a numbered message sent on one side is run once on the other side.
  • Nonce: A nonce is a number used once to tell one message from another.
  • Payload: A payload is the data a message carries for the other side to act on.
  • Trusted sender: A trusted sender is the only account allowed to deliver messages to the destination.
  • Replay: A replay is a second delivery of a message that was already run.
  • Restatement: A numbered message from the trusted sender runs exactly once on the destination.

In this example, the source contract is the trusted sender for the inbox, so it delivers message 7 with payload 123. A direct call to the inbox fails, and sending message 7 again through the source also fails.

Note

This lesson simulates the two sides with two contracts on the local chain only, since real message passing spans two chains.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

A message with nonce 7 was already delivered. What happens when it is delivered again?