Lesson 6: Implementing Basic Reactive Functions
Overview
In this lesson, we’ll go through the Reactive Smart Contract (RSC) specifically designed for the Uniswap V2 platform, aimed at executing stop orders based on predefined conditions. By the end of this lesson, you’ll know:
- That RSCs are pretty similar to Ethereum smart contracts and thus easy to understand.
- What each part of the stop-order reactive smart contract means.
- How this reactive smart contract is executed and what it does.
Contract
The contract UniswapDemoStopOrderReactive.sol
is set up to monitor liquidity pool events on Uniswap V2, namely tracking the
Sync
events to determine when the conditions for a stop order are met. When these conditions are triggered, it executes a
callback transaction on the Ethereum blockchain to perform the stop order.
Key Components
Event Declarations
Event Declarations: Events like Callback
, Subscribed
, AboveThreshold
, CallbackSent
, and Done
are used for logging
and tracking the contract's operations on the blockchain.
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
import '../../IReactive.sol';
import '../../SubscriptionService.sol';
struct Reserves {
uint112 reserve0;
uint112 reserve1;
}
// Reactive: 0x0c189A26E0AD06f8E12179280d9e8fB0EE1648C2
contract UniswapDemoStopOrderReactive {
event Callback(
uint256 indexed chain_id,
address indexed _contract,
uint64 indexed gas_limit,
bytes payload
);
event Subscribed(
address indexed service_address,
address indexed _contract,
uint256 indexed topic_0
);
event VM();
event AboveThreshold(
uint112 indexed reserve0,
uint112 indexed reserve1,
uint256 coefficient,
uint256 threshold
);
event CallbackSent();
event Done();
Contract Variables
UNISWAP_V2_SYNC_TOPIC_0
and STOP_ORDER_STOP_TOPIC_0
are constants representing the topics for Uniswap's Sync
events
and the contract's Stop
events, respectively. CALLBACK_GAS_LIMIT
is the gas limit set for the callback transaction.
Variables like triggered
, done
, pair
, stop_order
, client
, token0
, coefficient
, and threshold
store the state
and configuration of the stop order.
uint256 private constant UNISWAP_V2_SYNC_TOPIC_0 = 0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1;
uint256 private constant STOP_ORDER_STOP_TOPIC_0 = 0x9996f0dd09556ca972123b22cf9f75c3765bc699a1336a85286c7cb8b9889c6b;
uint64 private constant CALLBACK_GAS_LIMIT = 1000000;
/**
* Indicates whether this is the instance of the contract deployed to ReactVM.
*/
bool private vm;
// State specific to ReactVM instance of the contract.
bool private triggered;
bool private done;
address private pair;
address private stop_order;
address private client;
bool private token0;
uint256 private coefficient;
uint256 private threshold;
Contract Logic
Constructor
The constructor sets up the initial state of the contract and subscribes to the necessary events from the Uniswap V2 pair
_pair
and the stop-order contract _stop_order
using the SubscriptionService.sol
.
constructor(
address service_address,
address _pair,
address _stop_order,
address _client,
bool _token0,
uint256 _coefficient,
uint256 _threshold
) {
triggered = false;
done = false;
SubscriptionService service = SubscriptionService(service_address);
pair = _pair;
bytes memory payload = abi.encodeWithSignature("subscribe(address,uint256)", pair, UNISWAP_V2_SYNC_TOPIC_0);
(bool subscription_result,) = address(service).call(payload);
if (!subscription_result) {
vm = true;
emit VM();
} else {
emit Subscribed(service_address, pair, UNISWAP_V2_SYNC_TOPIC_0);
}
stop_order = _stop_order;
bytes memory payload_2 = abi.encodeWithSignature("subscribe(address,uint256)", stop_order, STOP_ORDER_STOP_TOPIC_0);
(bool subscription_result_2,) = address(service).call(payload_2);
if (!subscription_result_2) {
vm = true;
emit VM();
} else {
emit Subscribed(service_address, stop_order, STOP_ORDER_STOP_TOPIC_0);
}
client = _client;
token0 = _token0;
coefficient = _coefficient;
threshold = _threshold;
}
react() Function
The react()
function is the core of the contract's logic. It is triggered by events on the Ethereum smart contract it is
subscribed to. The function processes these events to check if the conditions for executing the stop order are met.
If the event is from the stop-order contract and matches the predefined topics and addresses, the contract concludes its operation
(done
= true
). If the event is a Sync
event from the Uniswap pair, the contract checks if the current reserves meet the stop-order
condition (below_threshold
function). If so, it triggers the callback transaction on Ethereum to execute the stop order.
// Methods specific to ReactVM instance of the contract.
function react(
uint256 chain_id,
address _contract,
uint256 topic_0,
uint256 topic_1,
uint256 topic_2,
uint256 /* topic_3 */,
bytes calldata data
) external {
// TODO: Support for multiple dynamic orders? Not viable until we have dynamic subscriptions.
// TODO: fix the assertions after debugging.
//require(vm, 'ReactVM only');
assert(!done);
if (_contract == stop_order) {
// TODO: Practically speaking, it's broken, because we also need to check the transfer direction.
// For the purposes of the demo, I'm just going to ignore that complication.
if (
triggered &&
topic_0 == STOP_ORDER_STOP_TOPIC_0 &&
topic_1 == uint256(uint160(pair)) &&
topic_2 == uint256(uint160(client))
) {
done = true;
emit Done();
}
} else {
Reserves memory sync = abi.decode(data, ( Reserves ));
if (below_threshold(sync) && !triggered) {
emit CallbackSent();
bytes memory payload = abi.encodeWithSignature(
"stop(address,address,bool,uint256,uint256)",
pair,
client,
token0,
coefficient,
threshold
);
triggered = true;
emit Callback(chain_id, stop_order, CALLBACK_GAS_LIMIT, payload);
}
}
}
below_threshold() Function
The below_threshold()
function determines whether the current token reserves in the Uniswap pool meet the threshold
conditions for executing the stop order.
function below_threshold(Reserves memory sync) internal view returns (bool) {
if (token0) {
return (sync.reserve1 * coefficient) / sync.reserve0 <= threshold;
} else {
return (sync.reserve0 * coefficient) / sync.reserve1 <= threshold;
}
}
Execution Flow
-
Initialization: Upon deployment, the contract subscribes to the necessary events from the Uniswap V2 pair and the stop order callback contract.
-
Event Monitoring: The contract listens for Sync events from the Uniswap pair to monitor the pool's reserve changes and
Stop
events from the stop-order contract to track the execution of orders. -
Stop Order Activation: When the
Sync
event indicates that the pool's price hits the threshold, the contract initiates the stop order through the callback function, executing a trade on Uniswap V2. -
Completion: After the stop order is executed, the contract captures the Stop event from the stop-order contract, marking the process as complete.
In essence, this Reactive Smart Contract leverages the event-driven capabilities of the ReactVM to autonomously monitor and respond to on-chain conditions, executing predefined trading strategies on Uniswap V2 efficiently and in a decentralized manner.
Conclusion
RSCs share a strong resemblance with Ethereum smart contracts, making them relatively straightforward to comprehend for those familiar with Ethereum's smart contract architecture. We've dissected every component of the stop-order reactive smart contract, understanding the role and purpose of each part, from event handling to state management. We've delved into the execution process of this reactive smart contract, detailing how it listens for and reacts to specific Uniswap V2 pool events to autonomously execute stop orders based on predefined conditions, illustrating the dynamic and responsive nature of RSCs in the decentralized finance ecosystem.
This lesson explains the concepts used in the P03 Uniswap Stop Order use case, feel free to try it yourself.