-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathOrigin.sol
168 lines (151 loc) · 8.72 KB
/
Origin.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {BaseMessageLib} from "./libs/memory/BaseMessage.sol";
import {MAX_CONTENT_BYTES} from "./libs/Constants.sol";
import {
ContentLengthTooBig,
EthTransferFailed,
IncorrectDestinationDomain,
InsufficientEthBalance
} from "./libs/Errors.sol";
import {GasData, GasDataLib} from "./libs/stack/GasData.sol";
import {MemView, MemViewLib} from "./libs/memory/MemView.sol";
import {Header, MessageLib, MessageFlag} from "./libs/memory/Message.sol";
import {Request, RequestLib} from "./libs/stack/Request.sol";
import {Tips, TipsLib} from "./libs/stack/Tips.sol";
import {TypeCasts} from "./libs/TypeCasts.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {AgentSecured} from "./base/AgentSecured.sol";
import {OriginEvents} from "./events/OriginEvents.sol";
import {InterfaceGasOracle} from "./interfaces/InterfaceGasOracle.sol";
import {InterfaceOrigin} from "./interfaces/InterfaceOrigin.sol";
import {StateHub} from "./hubs/StateHub.sol";
/// @notice `Origin` contract is used for sending messages to remote chains. It is done
/// by inserting the message hashes into the Origin Merkle, which makes it possible to
/// prove that message was sent using the Merkle proof against the Origin Merkle Root. This essentially
/// compresses the list of messages into a single 32-byte value that needs to be stored on the destination chain.
/// `Origin` is responsible for the following:
/// - Formatting the sent message payloads, and inserting their hashes into the Origin Merkle Tree.
/// - Keeping track of its own historical states (see parent contract `StateHub`).
/// - Enforcing minimum tip values for sent base messages based on the provided execution requests.
/// - Distributing the collected tips upon request from a local `AgentManager` contract.
contract Origin is StateHub, OriginEvents, InterfaceOrigin {
using MemViewLib for bytes;
using MessageLib for bytes;
using TipsLib for bytes;
using TypeCasts for address;
address public immutable gasOracle;
modifier onlyRemoteDestination(uint32 destination) {
if (destination == localDomain) revert IncorrectDestinationDomain();
_;
}
// ═════════════════════════════════════════ CONSTRUCTOR & INITIALIZER ═════════════════════════════════════════════
// solhint-disable-next-line no-empty-blocks
constructor(uint32 synapseDomain_, address agentManager_, address inbox_, address gasOracle_)
AgentSecured("0.0.3", synapseDomain_, agentManager_, inbox_)
{
gasOracle = gasOracle_;
}
/// @notice Initializes Origin contract:
/// - msg.sender is set as contract owner
/// - State of "empty merkle tree" is saved
function initialize() external initializer {
// Initialize Ownable: msg.sender is set as "owner"
__Ownable2Step_init();
// Initialize "states": state of an "empty merkle tree" is saved
_initializeStates();
}
// ═══════════════════════════════════════════════ SEND MESSAGES ═══════════════════════════════════════════════════
/// @inheritdoc InterfaceOrigin
function sendBaseMessage(
uint32 destination,
bytes32 recipient,
uint32 optimisticPeriod,
uint256 paddedRequest,
bytes memory content
) external payable onlyRemoteDestination(destination) returns (uint32 messageNonce, bytes32 messageHash) {
// Check that content is not too large
if (content.length > MAX_CONTENT_BYTES) revert ContentLengthTooBig();
// This will revert if msg.value is lower than value of minimum tips
Tips tips = _getMinimumTips(destination, paddedRequest, content.length).matchValue(msg.value);
Request request = RequestLib.wrapPadded(paddedRequest);
// Format the BaseMessage body
bytes memory body = BaseMessageLib.formatBaseMessage({
sender_: msg.sender.addressToBytes32(),
recipient_: recipient,
tips_: tips,
request_: request,
content_: content
});
// Send the message
return _sendMessage(destination, optimisticPeriod, MessageFlag.Base, body);
}
/// @inheritdoc InterfaceOrigin
function sendManagerMessage(uint32 destination, uint32 optimisticPeriod, bytes memory payload)
external
onlyAgentManager
onlyRemoteDestination(destination)
returns (uint32 messageNonce, bytes32 messageHash)
{
// AgentManager (checked via modifier) is responsible for constructing the calldata payload correctly.
return _sendMessage(destination, optimisticPeriod, MessageFlag.Manager, payload);
}
/// @inheritdoc InterfaceOrigin
function withdrawTips(address recipient, uint256 amount) external onlyAgentManager {
if (address(this).balance < amount) revert InsufficientEthBalance();
(bool success,) = recipient.call{value: amount}("");
if (!success) revert EthTransferFailed();
emit TipWithdrawalCompleted(recipient, amount);
}
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
/// @inheritdoc InterfaceOrigin
function getMinimumTipsValue(uint32 destination, uint256 paddedRequest, uint256 contentLength)
external
view
returns (uint256 tipsValue)
{
return _getMinimumTips(destination, paddedRequest, contentLength).value();
}
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
/// @dev Sends the given message to the specified destination. Message hash is inserted
/// into the Origin Merkle Tree, which will enable message execution on destination chain.
function _sendMessage(uint32 destination, uint32 optimisticPeriod, MessageFlag flag, bytes memory body)
internal
returns (uint32 messageNonce, bytes32 messageHash)
{
// Format the message header
messageNonce = _nextNonce();
Header header = flag.encodeHeader({
origin_: localDomain,
nonce_: messageNonce,
destination_: destination,
optimisticPeriod_: optimisticPeriod
});
// Format the full message payload
bytes memory msgPayload = MessageLib.formatMessage(header, body);
// Insert new leaf into the Origin Merkle Tree and save the updated state
messageHash = msgPayload.castToMessage().leaf();
_insertAndSave(messageHash);
// Emit event with message information
emit Sent(messageHash, messageNonce, destination, msgPayload);
// Update the gas oracle data. We do this after the provided tips are checked so that
// the provided message is sent in the event that the gas oracle data is updated to a higher value.
InterfaceGasOracle(gasOracle).updateGasData(destination);
// TODO: consider doing this before the message is sent, while adjusting GasOracle.getMinimumTips() to
// use the pending gas oracle data.
}
/// @dev Returns the minimum tips for sending a message to the given destination with the given request and content.
function _getMinimumTips(uint32 destination, uint256 paddedRequest, uint256 contentLength)
internal
view
returns (Tips)
{
return
TipsLib.wrapPadded(InterfaceGasOracle(gasOracle).getMinimumTips(destination, paddedRequest, contentLength));
}
/// @dev Gets the current gas data from the gas oracle to be saved as part of the Origin State.
function _fetchGasData() internal view override returns (GasData) {
return GasDataLib.wrapGasData(InterfaceGasOracle(gasOracle).getGasData());
}
}