Skip to content

Commit 0faa783

Browse files
authored
fix: minimize CCTP bytecode size (#7265)
1 parent 3fc7383 commit 0faa783

File tree

2 files changed

+28
-89
lines changed

2 files changed

+28
-89
lines changed

solidity/contracts/token/TokenBridgeCctpBase.sol

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {TypeCasts} from "../libs/TypeCasts.sol";
1717
import {MovableCollateralRouter, MovableCollateralRouterStorage} from "./libs/MovableCollateralRouter.sol";
1818
import {TokenRouter} from "./libs/TokenRouter.sol";
1919
import {AbstractPostDispatchHook} from "../hooks/libs/AbstractPostDispatchHook.sol";
20-
import {AbstractMessageIdAuthorizedIsm} from "../isms/hook/AbstractMessageIdAuthorizedIsm.sol";
2120
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
2221
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
2322

@@ -41,22 +40,11 @@ struct Domain {
4140
uint32 circle;
4241
}
4342

44-
// need intermediate contract to insert slots between TokenBridgeCctpBase and AbstractMessageIdAuthorizedIsm
45-
abstract contract TokenBridgeCctpIntermediateStorage is
43+
// see ./CCTP.md for sequence diagrams of the destination chain control flow
44+
abstract contract TokenBridgeCctpBase is
4645
TokenBridgeCctpBaseStorage,
4746
AbstractCcipReadIsm,
4847
AbstractPostDispatchHook
49-
{
50-
/// @notice Hyperlane domain => Domain struct.
51-
/// We use a struct to avoid ambiguity with domain 0 being unknown.
52-
mapping(uint32 hypDomain => Domain circleDomain)
53-
internal _hyperlaneDomainMap;
54-
}
55-
56-
// see ./CCTP.md for sequence diagrams of the destination chain control flow
57-
abstract contract TokenBridgeCctpBase is
58-
TokenBridgeCctpIntermediateStorage,
59-
AbstractMessageIdAuthorizedIsm
6048
{
6149
using Message for bytes;
6250
using TypeCasts for bytes32;
@@ -72,11 +60,20 @@ abstract contract TokenBridgeCctpBase is
7260
// @notice CCTP token messenger contract
7361
ITokenMessenger public immutable tokenMessenger;
7462

63+
/// @notice Hyperlane domain => Domain struct.
64+
/// We use a struct to avoid ambiguity with domain 0 being unknown.
65+
mapping(uint32 hypDomain => Domain circleDomain)
66+
internal _hyperlaneDomainMap;
67+
7568
/// @notice Circle domain => Domain struct.
7669
// We use a struct to avoid ambiguity with domain 0 being unknown.
7770
mapping(uint32 circleDomain => Domain hyperlaneDomain)
7871
internal _circleDomainMap;
7972

73+
/// @notice Maps messageId to whether or not the message has been verified
74+
/// by the CCTP message transmitter
75+
mapping(bytes32 messageId => bool) public isVerified;
76+
8077
/**
8178
* @notice Emitted when the Hyperlane domain to Circle domain mapping is updated.
8279
* @param hyperlaneDomain The Hyperlane domain.
@@ -252,13 +249,9 @@ abstract contract TokenBridgeCctpBase is
252249
function verify(
253250
bytes calldata _metadata,
254251
bytes calldata _hyperlaneMessage
255-
)
256-
external
257-
override(AbstractMessageIdAuthorizedIsm, IInterchainSecurityModule)
258-
returns (bool)
259-
{
252+
) external returns (bool) {
260253
// check if hyperlane message has already been verified by CCTP
261-
if (isVerified(_hyperlaneMessage)) {
254+
if (isVerified[_hyperlaneMessage.id()]) {
262255
return true;
263256
}
264257

@@ -299,22 +292,23 @@ abstract contract TokenBridgeCctpBase is
299292
bytes32 circleSender,
300293
bytes32 messageId
301294
) internal returns (bool) {
295+
require(
296+
msg.sender == address(messageTransmitter),
297+
"Not message transmitter"
298+
);
299+
302300
// ensure that the message was sent from the hook on the origin chain
303301
uint32 origin = circleDomainToHyperlaneDomain(circleSource);
304302
require(
305303
_mustHaveRemoteRouter(origin) == circleSender,
306304
"Unauthorized circle sender"
307305
);
308306

309-
preVerifyMessage(messageId, 0);
307+
isVerified[messageId] = true;
310308

311309
return true;
312310
}
313311

314-
function _isAuthorized() internal view override returns (bool) {
315-
return msg.sender == address(messageTransmitter);
316-
}
317-
318312
function _offchainLookupCalldata(
319313
bytes calldata _message
320314
) internal pure override returns (bytes memory) {

solidity/test/token/TokenBridgeCctp.t.sol

Lines changed: 9 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ contract TokenBridgeCctpV1Test is Test {
928928

929929
assertTrue(result);
930930
assertTrue(
931-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
931+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
932932
);
933933
}
934934

@@ -950,9 +950,7 @@ contract TokenBridgeCctpV1Test is Test {
950950
) public virtual {
951951
// Try to call from a non-message-transmitter address
952952
vm.prank(evil);
953-
vm.expectRevert(
954-
bytes("AbstractMessageIdAuthorizedIsm: sender is not the hook")
955-
);
953+
vm.expectRevert(bytes("Not message transmitter"));
956954
TokenBridgeCctpV1(address(tbDestination)).handleReceiveMessage(
957955
cctpOrigin,
958956
address(tbOrigin).addressToBytes32(),
@@ -993,29 +991,6 @@ contract TokenBridgeCctpV1Test is Test {
993991
);
994992
}
995993

996-
function test_handleReceiveMessage_revertsWhen_messageAlreadyDelivered(
997-
bytes32 messageId
998-
) public virtual {
999-
// First delivery succeeds
1000-
vm.prank(address(messageTransmitterDestination));
1001-
TokenBridgeCctpV1(address(tbDestination)).handleReceiveMessage(
1002-
cctpOrigin,
1003-
address(tbOrigin).addressToBytes32(),
1004-
abi.encode(messageId)
1005-
);
1006-
1007-
// Second delivery of the same message should revert
1008-
vm.prank(address(messageTransmitterDestination));
1009-
vm.expectRevert(
1010-
bytes("AbstractMessageIdAuthorizedIsm: message already verified")
1011-
);
1012-
TokenBridgeCctpV1(address(tbDestination)).handleReceiveMessage(
1013-
cctpOrigin,
1014-
address(tbOrigin).addressToBytes32(),
1015-
abi.encode(messageId)
1016-
);
1017-
}
1018-
1019994
function test_verify_returnsTrue_afterDirectDelivery(
1020995
bytes calldata message
1021996
) public virtual {
@@ -1033,7 +1008,7 @@ contract TokenBridgeCctpV1Test is Test {
10331008

10341009
// Verify the message is marked as verified
10351010
assertTrue(
1036-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
1011+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
10371012
);
10381013

10391014
// Now call verify with empty metadata - should return true without attestation
@@ -1645,7 +1620,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
16451620

16461621
assertTrue(result);
16471622
assertTrue(
1648-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
1623+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
16491624
);
16501625
}
16511626

@@ -1668,9 +1643,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
16681643
uint32 finalityThreshold
16691644
) public {
16701645
vm.prank(evil);
1671-
vm.expectRevert(
1672-
bytes("AbstractMessageIdAuthorizedIsm: sender is not the hook")
1673-
);
1646+
vm.expectRevert(bytes("Not message transmitter"));
16741647
TokenBridgeCctpV2(address(tbDestination)).handleReceiveFinalizedMessage(
16751648
cctpOrigin,
16761649
address(tbOrigin).addressToBytes32(),
@@ -1733,7 +1706,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
17331706

17341707
assertTrue(result);
17351708
assertTrue(
1736-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
1709+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
17371710
);
17381711
}
17391712

@@ -1757,9 +1730,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
17571730
uint32 finalityThreshold
17581731
) public {
17591732
vm.prank(evil);
1760-
vm.expectRevert(
1761-
bytes("AbstractMessageIdAuthorizedIsm: sender is not the hook")
1762-
);
1733+
vm.expectRevert(bytes("Not message transmitter"));
17631734
TokenBridgeCctpV2(address(tbDestination))
17641735
.handleReceiveUnfinalizedMessage(
17651736
cctpOrigin,
@@ -1806,32 +1777,6 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
18061777
);
18071778
}
18081779

1809-
function test_handleReceiveMessage_revertsWhen_messageAlreadyDelivered(
1810-
bytes32 messageId
1811-
) public override {
1812-
uint32 finalityThreshold = 2000;
1813-
// First delivery succeeds
1814-
vm.prank(address(messageTransmitterDestination));
1815-
TokenBridgeCctpV2(address(tbDestination)).handleReceiveFinalizedMessage(
1816-
cctpOrigin,
1817-
address(tbOrigin).addressToBytes32(),
1818-
finalityThreshold,
1819-
abi.encode(messageId)
1820-
);
1821-
1822-
// Second delivery of the same message should revert
1823-
vm.prank(address(messageTransmitterDestination));
1824-
vm.expectRevert(
1825-
bytes("AbstractMessageIdAuthorizedIsm: message already verified")
1826-
);
1827-
TokenBridgeCctpV2(address(tbDestination)).handleReceiveFinalizedMessage(
1828-
cctpOrigin,
1829-
address(tbOrigin).addressToBytes32(),
1830-
finalityThreshold,
1831-
abi.encode(messageId)
1832-
);
1833-
}
1834-
18351780
function test_verify_returnsTrue_afterDirectDelivery(
18361781
bytes calldata message
18371782
) public override {
@@ -1852,7 +1797,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
18521797

18531798
// Verify the message is marked as verified
18541799
assertTrue(
1855-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
1800+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
18561801
);
18571802

18581803
// Now call verify with empty metadata - should return true without attestation
@@ -1880,7 +1825,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
18801825

18811826
// Verify the message is marked as verified
18821827
assertTrue(
1883-
TokenBridgeCctpBase(address(tbDestination)).isVerified(message)
1828+
TokenBridgeCctpBase(address(tbDestination)).isVerified(messageId)
18841829
);
18851830

18861831
// Now call verify with empty metadata - should return true without attestation

0 commit comments

Comments
 (0)