Skip to main content

Reactive Smart Contracts

Reactive Smart Contracts run on a standard Ethereum Virtual Machine (EVM) and can be written in any language compatible with EVM. However, the Application Binary Interfaces (ABIs) are customized for Solidity. Their unique capabilities stem from reactive nodes and a specialized pre-deployed system contract.

Special Considerations

Reactive contracts are deployed simultaneously on the main reactive network and the private ReactVM subnet. The copy deployed on the main network is accessible by Externally Owned Accounts (EOAs) and can interact with the system contract to manage subscriptions. The copy deployed on ReactVM processes incoming events from origin chain contracts but cannot be interacted with by EOAs.

The two contract copies DO NOT share state and cannot interact directly. Since both copies use the same bytecode, it is recommended to identify the deployment target in the constructor and guard your methods accordingly. Determine whether the contract is being deployed to ReactVM by interacting with the system contract. Calls to the system contract in ReactVM will revert since this contract is not present there. Refer to the reactive demos for examples.

Reactive contracts running in the ReactVM context have limited capabilities for interaction outside their VM. They can only:

  • Passively receive log records from the reactive network.
  • Initiate calls to destination chain contracts.

Subscription Basics

Static subscriptions for reactive contracts are configured by calling the subscribe() method of the Reactive Network's system contract during deployment. This must happen in the constructor(), and the reactive contract must adeptly handle reverts. The latter requirement arises because reactive contracts are deployed both to the Reactive Network as such and to their deployer's private ReactVM, where the system contract is not present. The following code will accomplish this:

constructor() {
SubscriptionService service = SubscriptionService(service_address);
bytes memory payload = abi.encodeWithSignature("subscribe(address,uint256)", CONTRACT_ADDRESS, TOPIC_0);
(bool subscription_result,) = address(service).call(payload);
if (!subscription_result) {
emit VM();
}
}

The design of dynamic subscriptions and unsubscriptions is currently in flux and will be detailed later. It is likely to be achieved by reactive contracts emitting log records indicating changes in their subscriptions.

The subscription system, at its core, allows the event provider (the Reactive Network) to associate any number of uint256 fields with a given event. Subscribers can request events matching any subset of these fields. At the proof-of-concept stage, the Reactive Network provides only the originating contract's address and topic 0 as filtering criteria. The full implementation is planned to allow filtering by chain ID, contract address, and all topics. This may be expanded or changed in the future.

To explain capabilities by example, YOU CAN:

  • Subscribe to all log records emitted by a specific contract. For example, to subscribe to all events from contract 0x7E0987E5b3a30e3f2828572Bb659A548460a3003, call subscribe(0x7E0987E5b3a30e3f2828572Bb659A548460a3003, 0) in the constructor.

  • Subscribe to all log records with a specific topic 0. For example, to subscribe to all Uniswap V2 Sync events, call subscribe(0, 0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1) in the constructor.

  • Subscribe to log records emitted by a specific contract with a specific topic 0 by calling subscribe() with the desired contract address and topic 0.

  • Specify multiple independent subscriptions by calling subscribe() multiple times in constructor. Your reactive contract will receive events matching any of its subscriptions.

On the other hand, YOU CAN'T:

  • Match event parameters using less than, greater than, range, or bitwise operations. Only strict equality is supported.

  • Use disjunction or sets of criteria in a single subscription. You can, however, call subscribe() multiple times to achieve similar results, but this approach is somewhat vulnerable to combinatorial explosion.

Processing events

To process incoming events, a Reactive Smart Contract must implement the IReactive interface, requiring the implementation of a single method with the following signature:

function react(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 topic_3,
bytes calldata data
) external;

Reactive Network feeds events matching the reactive contract's subscriptions by initiating calls to this method.

Reactive Smart Contracts may use all EVM capabilities. The limitation is that they are executed in the context of a private ReactVM associated with a specific deployer address, preventing interaction with contracts deployed by others.

Destination Callbacks

The key capability of a Reactive Smart Contract is the ability to create new transactions in Layer 1 (L1) networks. This is achieved by emitting log records of a predetermined format:

event Callback(
uint256 indexed chain_id,
address indexed _contract,
uint64 indexed gas_limit,
bytes payload
);

Upon observing such a record in the traces, the Reactive Network submits a new transaction with the desired payload to the L1 network indicated by chain ID (if supported).

The Uniswap Demo uses this capability to initiate token sales through a destination chain contract:

bytes memory payload = abi.encodeWithSignature(
"stop(address,address,bool,uint256,uint256)",
pair,
client,
token0,
coefficient,
threshold
);
emit Callback(chain_id, stop_order, CALLBACK_GAS_LIMIT, payload);