-
Notifications
You must be signed in to change notification settings - Fork 32
/
PingPongClient.sol
152 lines (125 loc) · 7.66 KB
/
PingPongClient.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {TypeCasts} from "../libs/TypeCasts.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {MessageRecipient} from "./MessageRecipient.sol";
contract PingPongClient is MessageRecipient {
using TypeCasts for address;
struct PingPongMessage {
uint256 pingId;
bool isPing;
uint16 counter;
}
// ══════════════════════════════════════════════════ STORAGE ══════════════════════════════════════════════════════
uint256 public random;
/// @notice Amount of "Ping" messages sent.
uint256 public pingsSent;
/// @notice Amount of "Ping" messages received.
/// Every received Ping message leads to sending a Pong message back to initial sender.
uint256 public pingsReceived;
/// @notice Amount of "Pong" messages received.
/// When all messages are delivered, should be equal to `pingsSent`
uint256 public pongsReceived;
// ══════════════════════════════════════════════════ EVENTS ═══════════════════════════════════════════════════════
/// @notice Emitted when a Ping message is sent.
/// Triggered externally, or by receveing a Pong message with instructions to do more pings.
event PingSent(uint256 pingId);
/// @notice Emitted when a Ping message is received.
/// Will always send a Pong message back.
event PingReceived(uint256 pingId);
/// @notice Emitted when a Pong message is sent.
/// Triggered whenever a Ping message is received.
event PongSent(uint256 pingId);
/// @notice Emitted when a Pong message is received.
/// Will initiate a new Ping, if the counter in the message is non-zero.
event PongReceived(uint256 pingId);
// ════════════════════════════════════════════════ CONSTRUCTOR ════════════════════════════════════════════════════
constructor(address origin_, address destination_) MessageRecipient(origin_, destination_) {
// Initiate "random" value
random = uint256(keccak256(abi.encode(block.number)));
}
// ═══════════════════════════════════════════════ MESSAGE LOGIC ═══════════════════════════════════════════════════
function doPings(uint16 pingCount, uint32 destination_, address recipient, uint16 counter) external {
for (uint256 i = 0; i < pingCount; ++i) {
_ping(destination_, recipient.addressToBytes32(), counter);
}
}
/// @notice Send a Ping message to destination chain.
/// Upon receiving a Ping, a Pong message will be sent back.
/// If `counter > 0`, this process will be repeated when the Pong message is received.
/// @param destination_ Chain to send Ping message to
/// @param recipient Recipient of Ping message
/// @param counter Additional amount of Ping-Pong rounds to conclude
function doPing(uint32 destination_, address recipient, uint16 counter) external {
_ping(destination_, recipient.addressToBytes32(), counter);
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
function nextOptimisticPeriod() public view returns (uint32 period) {
// Use random optimistic period up to one minute
return uint32(random % 1 minutes);
}
// ═════════════════════════════════════ INTERNAL LOGIC: RECEIVE MESSAGES ══════════════════════════════════════════
/// @inheritdoc MessageRecipient
function _receiveBaseMessageUnsafe(uint32 origin_, uint32, bytes32 sender, uint256, uint32, bytes memory content)
internal
override
{
PingPongMessage memory message = abi.decode(content, (PingPongMessage));
if (message.isPing) {
// Ping is received
++pingsReceived;
emit PingReceived(message.pingId);
// Send Pong back
_pong(origin_, sender, message);
} else {
// Pong is received
++pongsReceived;
emit PongReceived(message.pingId);
// Send extra ping, if initially requested
if (message.counter != 0) {
_ping(origin_, sender, message.counter - 1);
}
}
}
// ═══════════════════════════════════════ INTERNAL LOGIC: SEND MESSAGES ═══════════════════════════════════════════
/// @dev Returns a random optimistic period value from 0 to 59 seconds.
function _optimisticPeriod() internal returns (uint32 period) {
// Use random optimistic period up to one minute
period = nextOptimisticPeriod();
// Adjust "random" value
random = uint256(keccak256(abi.encode(random)));
}
/**
* @dev Send a "Ping" or "Pong" message.
* @param destination_ Domain of destination chain
* @param recipient Message recipient on destination chain
* @param message Ping-pong message
*/
function _sendMessage(uint32 destination_, bytes32 recipient, PingPongMessage memory message) internal {
// TODO: this probably shouldn't be hardcoded
MessageRequest memory request = MessageRequest({gasDrop: 0, gasLimit: 500_000, version: 0});
bytes memory content = abi.encode(message);
_sendBaseMessage({
destination_: destination_,
recipient: recipient,
optimisticPeriod: _optimisticPeriod(),
tipsValue: 0,
request: request,
content: content
});
}
/// @dev Initiate a new Ping-Pong round.
function _ping(uint32 destination_, bytes32 recipient, uint16 counter) internal {
uint256 pingId = pingsSent++;
_sendMessage(destination_, recipient, PingPongMessage({pingId: pingId, isPing: true, counter: counter}));
emit PingSent(pingId);
}
/// @dev Send a Pong message back.
function _pong(uint32 destination_, bytes32 recipient, PingPongMessage memory message) internal {
_sendMessage(
destination_, recipient, PingPongMessage({pingId: message.pingId, isPing: false, counter: message.counter})
);
emit PongSent(message.pingId);
}
}