Overview
The System Contract is responsible for managing subscriptions on the Reactive Network (RNK), acting as a callback proxy contract. Integrated into the genesis block, it operates with immutable code, handling essential network functions without a mutable state. Extending CallbackProxy
and AbstractSubscriptionService
, the system contract also manages payments, whitelisting and blacklisting of contracts.
The Callback Proxy operates independently on destination chains and as an integrated part within the system contract on RNK. It primarily authorizes callback senders, processes callbacks, and manages contract balances and debt with methods like withdraw()
, addCallbackSenders()
, callback()
, along with internal functions for deposit and charge management.
Callback Payments
Callback execution is tied to payment, ensuring contracts either have sufficient balance or pay immediately upon receiving a callback. Failure to pay results in the contract being blacklisted, blocking future callbacks and transactions.
Direct Transfers
To transfer funds directly to your callback contract, use the following command. This sends 0.1 ether to the callback contract on the destination chain:
cast send $CALLBACK_ADDR --rpc-url $DESTINATION_CHAIN_RPC --private-key $DESTINATION_CHAIN_PRIVATE_KEY --value 0.1ether
After that, run the coverDebts
method, which checks if the contract has debts and sufficient funds to pay them. If both conditions are met, the contract will automatically settle its debts using its own funds.
cast send --rpc-url $DESTINATION_CHAIN_RPC --private-key $DESTINATION_CHAIN_PRIVATE_KEY $CALLBACK_ADDR "coverDebt()"
Depositing Funds to Callback Proxy
Alternatively, you can use the depositTo
method to transfer funds to the callback contract via Callback Proxy
, with the transaction fee paid by the EOA address whose private key signs the transaction. The following request sends 0.1 ether to the callback contract on the destination chain.
cast send --rpc-url $DESTINATION_CHAIN_RPC --private-key $DESTINATION_CHAIN_PRIVATE_KEY $CALLBACK_PROXY_ADDR "depositTo(address)" $CALLBACK_ADDR --value 0.1ether
Implement the pay()
method or inherit from AbstractPayer
for on-the-spot payments. Callback Proxy
triggers pay()
when a callback puts the contract in debt. The standard implementation verifies the proxy as the caller, ensures sufficient funds are available, and then settles the debt.
Checking Balance and Debt
To check the balance of a contract on the Reactive Network, use the following command:
cast balance --rpc-url $REACTIVE_RPC $REACTIVE_CONTRACT_ADDR
To check the debt status of a specific contract on the destination chain, run this command:
cast call --trace --rpc-url $DESTINATION_CHAIN_RPC --private-key $DESTINATION_CHAIN_PRIVATE_KEY $CALLBACK_PROXY_ADDR "debts(address)" $CONTRACT_ADDR
You might catch the Callback target currently in debt
error. Remember that the funds to cover the debt might be held in the user's callback contract account instead of Callback Proxy
. When the callback contract transfers these funds to the proxy, it triggers the receive()
function, closing the debt.
Callback Pricing
The current pricing formula, subject to change, is simplified for testing. It is set at 1 wei per gas unit but will later incorporate dynamic block base fees. The callback price is calculated as follows:
Where:
- is the base gas price, considering
tx.gasprice
andblock.basefee
. - is a pricing coefficient for the destination network.
- is 1 if the callback reverted; otherwise, 0.
- is the gas used for the callback.
- is a fixed gas surcharge for the destination network.
This formula is intended for the testnet stage and may evolve.
Reactive Transactions will share the same payment mechanism as RNK's callback payments, with a common balance. Separate contracts may be used for reactive and callback functionalities. This feature is not yet implemented, as it is not critical for the hackathon phase.
Gas / Tokenomics
- Gas usage and gas limits are tracked for all system transactions.
- On the Reactive Network (RNK), users are charged based on the formula: Gas Used × Gas Price = Total REACT Charge. This amount is deducted from the user's REACT token balance.
- Currently, RVM transactions don't incur gas charges (subject to change).
- Certain transactions may involve a
value
field, such as contract deployment or fund transfers. In these cases, the specified value (in REACT) is deducted from the user's balance. - RVM tokenomics is still under development; for now, all transactions are free of gas charges.
Abstract Contracts & Interfaces
Abstract contracts reduce boilerplate by incorporating common functionalities. They may change before production deployment.
AbstractCallback
An abstract base for managing callback-related functions with restricted access to a designated RVM ID.
constructor()
– Sets the callback sender and initializes the RVM ID.rvmIdOnly
modifier – Ensures that only authorized RVM IDs can interact with specific functions.
AbstractPausableReactive
Manages pausable event subscriptions for reactive contracts, allowing the owner to pause and resume event monitoring.
pause()
– Unsubscribes from all active event subscriptions and pauses the contract.resume()
– Resubscribes to all paused event subscriptions and resumes contract functionality.
AbstractPayer
Manages payment operations for contracts.
pay()
– Facilitates payment to the sender if authorized.coverDebt()
– Pays off any outstanding debt to the vendor.
AbstractReactive
Handles the distinction between the Reactive Network, ReactVM, and System Contract contexts.
rnOnly
modifier – Ensures thevm
variable isfalse
; ifvm
istrue
, it reverts with 'Reactive Network only'.vmOnly
modifier – Restricts access to functions for ReactVM instances based on thevm
variable.sysConOnly
modifier – Validates thatmsg.sender
matches theservice
contract's address; if not, it reverts with `System contract only'.
AbstractSubscriptionService
Provides an event subscription system for reactive contracts and allows contracts to subscribe to events based on criteria such as chain ID, contract address, and topics.
ping()
– Determines whether the contract is in the RN or RVM context (subject to change).subscribe()
– Registers a contract to receive notifications for events matching the provided criteria.unsubscribe()
– Removes an active subscription based on matching criteria.
IPayable
Defines basic payment functionality for contracts, including debt checking and receiving payments.
receive()
– Enables the contract to receive payments directly to cover debts.debt()
– Allows reactive contracts to query their outstanding debt.
IPayer
Defines the payment functionality for contracts.
pay()
– Facilitates payment of a specified amount, with a requirement to verify the sender.
IReactive
Defines the structure for reactive contracts, which receive notifications for events matching subscription criteria.
react()
– Handles incoming event notifications based on chain ID, contract address, topics, and event data.receive()
– Allows the contract to receive payments.
ISubscriptionService
Allows reactive contracts to subscribe to and receive notifications for events that match specified criteria across chains.
ping()
– Determines whether the contract operates in the Reactive Network or ReactVM context.subscribe()
– Subscribes the contract to monitor events based on specified criteria such as chain ID, contract address, and event topics.unsubscribe()
– Removes the contract's active event subscriptions, which can be resource-intensive.
ISystemContract
Combines IPayable
and ISubscriptionService
, providing both payment handling and event subscription capabilities. Payment functionality inherited from IPayable
to manage debts and payments. Event subscription functionality inherited from ISubscriptionService
to manage subscriptions and notifications for reactive contracts.
Most Common Errors
Unauthorized Access: Modifier-related errors (e.g., onlyOwner
, rvmIdOnly
, sysConOnly
) prevent unauthorized users from accessing specific contract functions. Reverts with 'Unauthorized' or 'Authorized X only' if the caller does not meet required conditions.
Incorrect Address or Contract Binding: Misconfigured addresses like service
, vendor
, or rvm_id
can cause contract failures, as external interactions depend on these addresses being correct.
Uninitialized Variables: If addresses like rvm_id
or vendor
are uninitialized, contracts can restrict access or fail during external calls. Uninitialized variables can also cause authorization failures.
Transfer/Payment Failures: Errors like 'Insufficient funds' or 'Transfer failed' occur if the contract doesn’t have enough funds in the balance or if .call
to external contracts fails.
Already Paused or Not Paused State: Functions like pause
or resume
in pausable contracts may revert if the contract is already in the required state, either paused or unpaused, leading to redundant or unnecessary actions.
Unsuccessful External Calls: Calls to external contracts like subscribe
, unsubscribe
, or pay
can silently fail due to reentrancy, gas issues, or misconfigured target contracts.
Expensive Gas Operations: Operations involving loops, recursion, or multiple subscriptions (e.g., in unsubscribe
or findSubscribersRecursively
) may result in high gas costs or out-of-gas errors.
Duplicate or Invalid Subscriptions: Contracts that handle subscriptions may fail to check for duplicates or invalid entries (e.g., all REACTIVE_IGNORE
), leading to inefficiencies or unexpected behavior.
Debt and Payment Issues: Insufficient payment or improper debt calculation can block further contract operations like resuming subscriptions, processing payments, or avoiding callback failures.
Modifier Conflicts or Context Errors: Modifiers like rnOnly
, vmOnly
, or sysConOnly
can cause errors if called in the wrong context, particularly in systems involving reactive virtual machines or system contracts.