Skip to content

Commit 3479e6a

Browse files
ams9198grantmike
authored andcommitted
STABLE-6895 (Part 1): Add initial BaseTokenMessenger implementation and tests (circlefin#10)
As discussed offline with @walkerq, breaking apart STABLE-6895 into 2 parts: 1) Extract base token messenger behavior and tests, copied from v1, into a separate type (`BaseTokenMessenger`). This base type handles adding / removing remote token messengers, the local minter, and encodes the local message transmitter / version. `TokenMessengerV2` derives from this type and layers on the v2-specific, messaging-layer differences. I think this is more flexible for a potential v3 and is a natural separation of concerns, but definitely open to further discussion! 2) (Future) integrate hooks, the first V2-specific functionality, into `TokenMessengerV2`. Some misc callouts: - Intention is leave the v1 contract code unmodified (optional: in the future we can refactor `TokenMessenger.t.sol` to use these shared baseTokenMessenger tests to avoid duplication). - Added some additional Ownable2Step test cases into `TestUtils.sol`. - Copied over the v1 unit tests from `TokenMessenger.t.sol`, but injected more fuzzable inputs into some of the test cases. Further callouts below in-line.
1 parent 377c9bd commit 3479e6a

File tree

5 files changed

+712
-9
lines changed

5 files changed

+712
-9
lines changed

src/v2/BaseTokenMessenger.sol

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
import {Ownable2Step} from "../roles/Ownable2Step.sol";
21+
import {ITokenMinter} from "../interfaces/ITokenMinter.sol";
22+
import {Rescuable} from "../roles/Rescuable.sol";
23+
24+
/**
25+
* @title BaseTokenMessenger
26+
* @notice Base administrative functionality for TokenMessenger implementations,
27+
* including managing remote token messengers and the local token minter.
28+
*/
29+
abstract contract BaseTokenMessenger is Rescuable {
30+
// ============ Events ============
31+
/**
32+
* @notice Emitted when a remote TokenMessenger is added
33+
* @param domain remote domain
34+
* @param tokenMessenger TokenMessenger on remote domain
35+
*/
36+
event RemoteTokenMessengerAdded(uint32 domain, bytes32 tokenMessenger);
37+
38+
/**
39+
* @notice Emitted when a remote TokenMessenger is removed
40+
* @param domain remote domain
41+
* @param tokenMessenger TokenMessenger on remote domain
42+
*/
43+
event RemoteTokenMessengerRemoved(uint32 domain, bytes32 tokenMessenger);
44+
45+
/**
46+
* @notice Emitted when the local minter is added
47+
* @param localMinter address of local minter
48+
* @notice Emitted when the local minter is added
49+
*/
50+
event LocalMinterAdded(address localMinter);
51+
52+
/**
53+
* @notice Emitted when the local minter is removed
54+
* @param localMinter address of local minter
55+
* @notice Emitted when the local minter is removed
56+
*/
57+
event LocalMinterRemoved(address localMinter);
58+
59+
// ============ State Variables ============
60+
// Local Message Transmitter responsible for sending and receiving messages to/from remote domains
61+
address public immutable localMessageTransmitter;
62+
63+
// Version of message body format
64+
uint32 public immutable messageBodyVersion;
65+
66+
// Minter responsible for minting and burning tokens on the local domain
67+
ITokenMinter public localMinter;
68+
69+
// Valid TokenMessengers on remote domains
70+
mapping(uint32 => bytes32) public remoteTokenMessengers;
71+
72+
// ============ Modifiers ============
73+
/**
74+
* @notice Only accept messages from a registered TokenMessenger contract on given remote domain
75+
* @param domain The remote domain
76+
* @param tokenMessenger The address of the TokenMessenger contract for the given remote domain
77+
*/
78+
modifier onlyRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger) {
79+
require(
80+
_isRemoteTokenMessenger(domain, tokenMessenger),
81+
"Remote TokenMessenger unsupported"
82+
);
83+
_;
84+
}
85+
86+
/**
87+
* @notice Only accept messages from the registered message transmitter on local domain
88+
*/
89+
modifier onlyLocalMessageTransmitter() {
90+
// Caller must be the registered message transmitter for this domain
91+
require(_isLocalMessageTransmitter(), "Invalid message transmitter");
92+
_;
93+
}
94+
95+
// ============ Constructor ============
96+
/**
97+
* @param _messageTransmitter Message transmitter address
98+
* @param _messageBodyVersion Message body version
99+
*/
100+
constructor(address _messageTransmitter, uint32 _messageBodyVersion) {
101+
require(
102+
_messageTransmitter != address(0),
103+
"MessageTransmitter not set"
104+
);
105+
localMessageTransmitter = _messageTransmitter;
106+
messageBodyVersion = _messageBodyVersion;
107+
}
108+
109+
// ============ External Functions ============
110+
/**
111+
* @notice Add the TokenMessenger for a remote domain.
112+
* @dev Reverts if there is already a TokenMessenger set for domain.
113+
* @param domain Domain of remote TokenMessenger.
114+
* @param tokenMessenger Address of remote TokenMessenger as bytes32.
115+
*/
116+
function addRemoteTokenMessenger(
117+
uint32 domain,
118+
bytes32 tokenMessenger
119+
) external onlyOwner {
120+
require(tokenMessenger != bytes32(0), "bytes32(0) not allowed");
121+
122+
require(
123+
remoteTokenMessengers[domain] == bytes32(0),
124+
"TokenMessenger already set"
125+
);
126+
127+
remoteTokenMessengers[domain] = tokenMessenger;
128+
emit RemoteTokenMessengerAdded(domain, tokenMessenger);
129+
}
130+
131+
/**
132+
* @notice Remove the TokenMessenger for a remote domain.
133+
* @dev Reverts if there is no TokenMessenger set for `domain`.
134+
* @param domain Domain of remote TokenMessenger
135+
*/
136+
function removeRemoteTokenMessenger(uint32 domain) external onlyOwner {
137+
// No TokenMessenger set for given remote domain.
138+
require(
139+
remoteTokenMessengers[domain] != bytes32(0),
140+
"No TokenMessenger set"
141+
);
142+
143+
bytes32 _removedTokenMessenger = remoteTokenMessengers[domain];
144+
delete remoteTokenMessengers[domain];
145+
emit RemoteTokenMessengerRemoved(domain, _removedTokenMessenger);
146+
}
147+
148+
/**
149+
* @notice Add minter for the local domain.
150+
* @dev Reverts if a minter is already set for the local domain.
151+
* @param newLocalMinter The address of the minter on the local domain.
152+
*/
153+
function addLocalMinter(address newLocalMinter) external onlyOwner {
154+
require(newLocalMinter != address(0), "Zero address not allowed");
155+
156+
require(
157+
address(localMinter) == address(0),
158+
"Local minter is already set."
159+
);
160+
161+
localMinter = ITokenMinter(newLocalMinter);
162+
163+
emit LocalMinterAdded(newLocalMinter);
164+
}
165+
166+
/**
167+
* @notice Remove the minter for the local domain.
168+
* @dev Reverts if the minter of the local domain is not set.
169+
*/
170+
function removeLocalMinter() external onlyOwner {
171+
address _localMinterAddress = address(localMinter);
172+
require(_localMinterAddress != address(0), "No local minter is set.");
173+
174+
delete localMinter;
175+
emit LocalMinterRemoved(_localMinterAddress);
176+
}
177+
178+
// ============ Internal Utils ============
179+
/**
180+
* @notice return the remote TokenMessenger for the given `_domain` if one exists, else revert.
181+
* @param _domain The domain for which to get the remote TokenMessenger
182+
* @return _tokenMessenger The address of the TokenMessenger on `_domain` as bytes32
183+
*/
184+
function _getRemoteTokenMessenger(
185+
uint32 _domain
186+
) internal view returns (bytes32) {
187+
bytes32 _tokenMessenger = remoteTokenMessengers[_domain];
188+
require(_tokenMessenger != bytes32(0), "No TokenMessenger for domain");
189+
return _tokenMessenger;
190+
}
191+
192+
/**
193+
* @notice return the local minter address if it is set, else revert.
194+
* @return local minter as ITokenMinter.
195+
*/
196+
function _getLocalMinter() internal view returns (ITokenMinter) {
197+
require(address(localMinter) != address(0), "Local minter is not set");
198+
return localMinter;
199+
}
200+
201+
/**
202+
* @notice Return true if the given remote domain and TokenMessenger is registered
203+
* on this TokenMessenger.
204+
* @param _domain The remote domain of the message.
205+
* @param _tokenMessenger The address of the TokenMessenger on remote domain.
206+
* @return true if a remote TokenMessenger is registered for `_domain` and `_tokenMessenger`,
207+
* on this TokenMessenger.
208+
*/
209+
function _isRemoteTokenMessenger(
210+
uint32 _domain,
211+
bytes32 _tokenMessenger
212+
) internal view returns (bool) {
213+
return
214+
_tokenMessenger != bytes32(0) &&
215+
remoteTokenMessengers[_domain] == _tokenMessenger;
216+
}
217+
218+
/**
219+
* @notice Returns true if the message sender is the local registered MessageTransmitter
220+
* @return true if message sender is the registered local message transmitter
221+
*/
222+
function _isLocalMessageTransmitter() internal view returns (bool) {
223+
return
224+
address(localMessageTransmitter) != address(0) &&
225+
msg.sender == address(localMessageTransmitter);
226+
}
227+
}

src/v2/TokenMessengerV2.sol

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
import {BaseTokenMessenger} from "./BaseTokenMessenger.sol";
21+
22+
contract TokenMessengerV2 is BaseTokenMessenger {
23+
// ============ Events ============
24+
25+
// ============ State Variables ============
26+
27+
// ============ Modifiers ============
28+
29+
// ============ Constructor ============
30+
/**
31+
* @param _messageTransmitter Message transmitter address
32+
* @param _messageBodyVersion Message body version
33+
*/
34+
constructor(
35+
address _messageTransmitter,
36+
uint32 _messageBodyVersion
37+
) BaseTokenMessenger(_messageTransmitter, _messageBodyVersion) {}
38+
39+
// ============ External Functions ============
40+
41+
// ============ Internal Utils ============
42+
}

test/TestUtils.sol

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ contract TestUtils is Test {
8181
address arbitraryAddress = vm.addr(1903);
8282

8383
// 8 KiB
84-
uint32 maxMessageBodySize = 8 * 2**10;
84+
uint32 maxMessageBodySize = 8 * 2 ** 10;
8585
// zero signature
8686
bytes zeroSignature =
8787
"00000000000000000000000000000000000000000000000000000000000000000";
@@ -136,6 +136,11 @@ contract TestUtils is Test {
136136
vm.expectRevert("Ownable: caller is not the owner");
137137
}
138138

139+
function expectRevertWithWrongOwner(address wrongOwner) public {
140+
vm.prank(wrongOwner);
141+
vm.expectRevert("Ownable: caller is not the owner");
142+
}
143+
139144
function expectRevertWithWrongTokenController() public {
140145
vm.prank(arbitraryAddress);
141146
vm.expectRevert("Caller is not tokenController");
@@ -215,6 +220,39 @@ contract TestUtils is Test {
215220
assertEq(_pausableContract.pauser(), _newPauser);
216221
}
217222

223+
function transferOwnershipFailsIfNotOwner(
224+
address _ownableContractAddress,
225+
address _notOwner,
226+
address _newOwner
227+
) public {
228+
Ownable2Step _ownableContract = Ownable2Step(_ownableContractAddress);
229+
address _initialOwner = _ownableContract.owner();
230+
expectRevertWithWrongOwner(_notOwner);
231+
_ownableContract.transferOwnership(_newOwner);
232+
233+
// Sanity check
234+
assertEq(_initialOwner, _ownableContract.owner());
235+
}
236+
237+
function acceptOwnershipFailsIfNotPendingOwner(
238+
address _ownableContractAddress,
239+
address _newOwner,
240+
address _otherAccount
241+
) public {
242+
Ownable2Step _ownableContract = Ownable2Step(_ownableContractAddress);
243+
address _initialOwner = _ownableContract.owner();
244+
_ownableContract.transferOwnership(_newOwner);
245+
assertEq(_ownableContract.pendingOwner(), _newOwner);
246+
247+
vm.prank(_otherAccount);
248+
vm.expectRevert("Ownable2Step: caller is not the new owner");
249+
_ownableContract.acceptOwnership();
250+
251+
// Sanity check
252+
assertEq(_initialOwner, _ownableContract.owner());
253+
assertEq(_newOwner, _ownableContract.pendingOwner());
254+
}
255+
218256
function transferOwnershipAndAcceptOwnership(
219257
address _ownableContractAddress,
220258
address _newOwner
@@ -284,19 +322,18 @@ contract TestUtils is Test {
284322
assertEq(_ownableContract.owner(), _secondNewOwner);
285323
}
286324

287-
function _signMessageWithAttesterPK(bytes memory _message)
288-
internal
289-
returns (bytes memory)
290-
{
325+
function _signMessageWithAttesterPK(
326+
bytes memory _message
327+
) internal returns (bytes memory) {
291328
uint256[] memory attesterPrivateKeys = new uint256[](1);
292329
attesterPrivateKeys[0] = attesterPK;
293330
return _signMessage(_message, attesterPrivateKeys);
294331
}
295332

296-
function _signMessage(bytes memory _message, uint256[] memory _privKeys)
297-
internal
298-
returns (bytes memory)
299-
{
333+
function _signMessage(
334+
bytes memory _message,
335+
uint256[] memory _privKeys
336+
) internal returns (bytes memory) {
300337
bytes memory _signaturesConcatenated = "";
301338

302339
for (uint256 i = 0; i < _privKeys.length; i++) {

0 commit comments

Comments
 (0)