Skip to content

Commit 7a41068

Browse files
authored
fix: cctp v2 burn amount (#6748)
1 parent 80f0199 commit 7a41068

File tree

6 files changed

+128
-54
lines changed

6 files changed

+128
-54
lines changed

.changeset/angry-pandas-swim.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperlane-xyz/core": patch
3+
---
4+
5+
Fix CCTP v2 transferRemote amount

solidity/contracts/token/TokenBridgeCctpBase.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ abstract contract TokenBridgeCctpBase is
3838
using TypeCasts for bytes32;
3939
using SafeERC20 for IERC20;
4040

41+
uint256 private constant _SCALE = 1;
42+
4143
IERC20 public immutable wrappedToken;
4244

4345
// @notice CCTP message transmitter contract
@@ -64,11 +66,10 @@ abstract contract TokenBridgeCctpBase is
6466

6567
constructor(
6668
address _erc20,
67-
uint256 _scale,
6869
address _mailbox,
6970
IMessageTransmitter _messageTransmitter,
7071
ITokenMessenger _tokenMessenger
71-
) FungibleTokenRouter(_scale, _mailbox) {
72+
) FungibleTokenRouter(_SCALE, _mailbox) {
7273
require(
7374
_messageTransmitter.version() == _getCCTPVersion(),
7475
"Invalid messageTransmitter CCTP version"

solidity/contracts/token/TokenBridgeCctpV1.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,12 @@ contract TokenBridgeCctpV1 is TokenBridgeCctpBase, IMessageHandler {
2626

2727
constructor(
2828
address _erc20,
29-
uint256 _scale,
3029
address _mailbox,
3130
IMessageTransmitter _messageTransmitter,
3231
ITokenMessengerV1 _tokenMessenger
3332
)
3433
TokenBridgeCctpBase(
3534
_erc20,
36-
_scale,
3735
_mailbox,
3836
_messageTransmitter,
3937
_tokenMessenger

solidity/contracts/token/TokenBridgeCctpV2.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
2929

3030
constructor(
3131
address _erc20,
32-
uint256 _scale,
3332
address _mailbox,
3433
IMessageTransmitterV2 _messageTransmitter,
3534
ITokenMessengerV2 _tokenMessenger,
@@ -38,7 +37,6 @@ contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
3837
)
3938
TokenBridgeCctpBase(
4039
_erc20,
41-
_scale,
4240
_mailbox,
4341
_messageTransmitter,
4442
_tokenMessenger
@@ -173,13 +171,15 @@ contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
173171
override
174172
returns (uint256 dispatchValue, bytes memory message)
175173
{
176-
uint256 fastFee = _feeAmount(destination, recipient, amount);
177-
_transferFromSender(amount + fastFee);
174+
uint256 burnAmount = amount +
175+
_feeAmount(destination, recipient, amount);
176+
177+
_transferFromSender(burnAmount);
178178

179179
uint32 circleDomain = hyperlaneDomainToCircleDomain(destination);
180180

181181
ITokenMessengerV2(address(tokenMessenger)).depositForBurn(
182-
amount,
182+
burnAmount,
183183
circleDomain,
184184
recipient,
185185
address(wrappedToken),
@@ -189,7 +189,7 @@ contract TokenBridgeCctpV2 is TokenBridgeCctpBase, IMessageHandlerV2 {
189189
);
190190

191191
dispatchValue = msg.value;
192-
message = TokenMessage.format(recipient, _outboundAmount(amount));
192+
message = TokenMessage.format(recipient, burnAmount);
193193
_validateTokenMessageLength(message);
194194
}
195195
}

solidity/test/token/TokenBridgeCctp.t.sol

Lines changed: 114 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ contract TokenBridgeCctpV1Test is Test {
4040
uint32 internal constant CCTP_VERSION_1 = 0;
4141
uint32 internal constant CCTP_VERSION_2 = 1;
4242

43-
uint256 internal constant scale = 1;
4443
uint32 internal constant origin = 1;
4544
uint32 internal constant destination = 2;
4645
uint32 internal constant cctpOrigin = 0;
@@ -110,7 +109,6 @@ contract TokenBridgeCctpV1Test is Test {
110109

111110
TokenBridgeCctpV1 originImplementation = new TokenBridgeCctpV1(
112111
address(tokenOrigin),
113-
scale,
114112
address(mailboxOrigin),
115113
messageTransmitterOrigin,
116114
tokenMessengerOrigin
@@ -131,7 +129,6 @@ contract TokenBridgeCctpV1Test is Test {
131129

132130
TokenBridgeCctpV1 destinationImplementation = new TokenBridgeCctpV1(
133131
address(tokenDestination),
134-
scale,
135132
address(mailboxDestination),
136133
messageTransmitterDestination,
137134
tokenMessengerDestination
@@ -338,7 +335,6 @@ contract TokenBridgeCctpV1Test is Test {
338335
function _upgrade(TokenBridgeCctpBase bridge) internal virtual {
339336
TokenBridgeCctpV1 newImplementation = new TokenBridgeCctpV1(
340337
address(bridge.wrappedToken()),
341-
bridge.scale(),
342338
address(bridge.mailbox()),
343339
bridge.messageTransmitter(),
344340
ITokenMessengerV1(address(bridge.tokenMessenger()))
@@ -508,7 +504,6 @@ contract TokenBridgeCctpV1Test is Test {
508504
vm.expectRevert(bytes("Invalid TokenMessenger CCTP version"));
509505
TokenBridgeCctpV1 v1 = new TokenBridgeCctpV1(
510506
address(tokenOrigin),
511-
scale,
512507
address(mailboxOrigin),
513508
messageTransmitterOrigin,
514509
tokenMessengerOrigin
@@ -518,7 +513,6 @@ contract TokenBridgeCctpV1Test is Test {
518513
vm.expectRevert(bytes("Invalid messageTransmitter CCTP version"));
519514
v1 = new TokenBridgeCctpV1(
520515
address(tokenOrigin),
521-
scale,
522516
address(mailboxOrigin),
523517
messageTransmitterOrigin,
524518
tokenMessengerOrigin
@@ -856,7 +850,6 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
856850

857851
TokenBridgeCctpV2 originImplementation = new TokenBridgeCctpV2(
858852
address(tokenOrigin),
859-
scale,
860853
address(mailboxOrigin),
861854
messageTransmitterOrigin,
862855
tokenMessengerOrigin,
@@ -879,7 +872,6 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
879872

880873
TokenBridgeCctpV2 destinationImplementation = new TokenBridgeCctpV2(
881874
address(tokenDestination),
882-
scale,
883875
address(mailboxDestination),
884876
messageTransmitterDestination,
885877
tokenMessengerDestination,
@@ -898,53 +890,69 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
898890
_setupTokenBridgesCctp(tbOrigin, tbDestination);
899891
}
900892

893+
function _setNonce(bytes memory cctpMessage, bytes32 nonce) internal view {
894+
// length + NONCE_INDEX
895+
uint256 nonceOffset = 32 + 12;
896+
assembly {
897+
mstore(add(cctpMessage, nonceOffset), nonce)
898+
}
899+
}
900+
901901
function _encodeCctpBurnMessage(
902902
uint64 nonce,
903903
uint32 sourceDomain,
904904
bytes32 recipient,
905905
uint256 amount,
906906
address sender
907-
) internal view override returns (bytes memory) {
907+
) internal view override returns (bytes memory cctpMessage) {
908908
bytes memory burnMessage = BurnMessageV2._formatMessageForRelay(
909909
version,
910910
address(tokenOrigin).addressToBytes32(),
911911
recipient,
912-
amount,
912+
amount + (amount * maxFee) / 10_000,
913913
sender.addressToBytes32(),
914914
maxFee,
915915
bytes("")
916916
);
917-
return
918-
CctpMessageV2._formatMessageForRelay(
919-
version,
920-
sourceDomain,
921-
cctpDestination,
922-
address(tokenMessengerOrigin).addressToBytes32(),
923-
address(tokenMessengerDestination).addressToBytes32(),
924-
bytes32(0),
925-
minFinalityThreshold,
926-
burnMessage
927-
);
917+
cctpMessage = CctpMessageV2._formatMessageForRelay(
918+
version,
919+
sourceDomain,
920+
cctpDestination,
921+
address(tokenMessengerOrigin).addressToBytes32(),
922+
address(tokenMessengerDestination).addressToBytes32(),
923+
bytes32(0),
924+
minFinalityThreshold,
925+
burnMessage
926+
);
927+
// pseudo random
928+
bytes32 nonceBytes = keccak256(
929+
abi.encode(nonce, sender, recipient, amount)
930+
);
931+
_setNonce(cctpMessage, nonceBytes);
928932
}
929933

930934
function _encodeCctpHookMessage(
931935
bytes32 sender,
932936
bytes32 recipient,
933937
bytes memory message
934-
) internal view override returns (bytes memory) {
935-
return
936-
CctpMessageV2._formatMessageForRelay(
937-
version,
938-
cctpOrigin,
939-
cctpDestination,
940-
sender,
941-
recipient,
942-
bytes32(0),
943-
minFinalityThreshold,
944-
message
945-
);
938+
) internal view override returns (bytes memory cctpMessage) {
939+
cctpMessage = CctpMessageV2._formatMessageForRelay(
940+
version,
941+
cctpOrigin,
942+
cctpDestination,
943+
sender,
944+
recipient,
945+
bytes32(0),
946+
minFinalityThreshold,
947+
message
948+
);
949+
// pseudo random nonce
950+
bytes32 nonce = keccak256(abi.encode(sender, recipient, message));
951+
_setNonce(cctpMessage, nonce);
946952
}
947953

954+
address constant usdc = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
955+
948956
function _deploy() internal returns (TokenBridgeCctpV2) {
949957
ITokenMessengerV2 tokenMessenger = ITokenMessengerV2(
950958
address(0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d)
@@ -955,8 +963,7 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
955963
);
956964

957965
TokenBridgeCctpV2 implementation = new TokenBridgeCctpV2(
958-
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913,
959-
1,
966+
usdc,
960967
0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D,
961968
messageTransmitter,
962969
tokenMessenger,
@@ -1027,7 +1034,8 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
10271034
TokenBridgeCctpV2 router = _deploy();
10281035

10291036
uint32 destination = 1; // ethereum
1030-
router.addDomain(destination, 0);
1037+
uint32 circleDestination = 0;
1038+
router.addDomain(destination, circleDestination);
10311039
router.enrollRemoteRouter(destination, ism);
10321040

10331041
Quote[] memory quotes = router.quoteTransferRemote(
@@ -1036,8 +1044,27 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
10361044
amount
10371045
);
10381046

1039-
deal(quotes[1].token, address(this), quotes[1].amount);
1040-
IERC20(quotes[1].token).approve(address(router), quotes[1].amount);
1047+
assertEq(quotes[1].token, usdc);
1048+
uint256 usdcQuote = quotes[1].amount;
1049+
1050+
deal(usdc, address(this), usdcQuote);
1051+
IERC20(usdc).approve(address(router), usdcQuote);
1052+
1053+
vm.expectEmit(true, true, true, true, address(router.tokenMessenger()));
1054+
emit ITokenMessengerV2.DepositForBurn(
1055+
usdc,
1056+
usdcQuote,
1057+
address(router),
1058+
recipient,
1059+
circleDestination,
1060+
bytes32(
1061+
0x00000000000000000000000028b5a0e9c621a5badaa536219b3a228c8168cf5d
1062+
), // tokenMessengerDestination
1063+
bytes32(0), // destinationCaller
1064+
maxFee,
1065+
minFinalityThreshold,
1066+
bytes("")
1067+
);
10411068

10421069
router.transferRemote{value: quotes[0].amount}(
10431070
destination,
@@ -1046,6 +1073,52 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
10461073
);
10471074
}
10481075

1076+
event MintAndWithdraw(
1077+
address indexed mintRecipient,
1078+
uint256 amount,
1079+
address indexed mintToken,
1080+
uint256 feeCollected
1081+
);
1082+
1083+
function testFork_verify_tokenMessage() public {
1084+
vm.createSelectFork(vm.rpcUrl("base"), 32_739_842);
1085+
1086+
TokenBridgeCctpV2 ism = _deploy();
1087+
1088+
bytes32 hook = deployer.addressToBytes32();
1089+
1090+
uint32 origin = 10; // optimism
1091+
uint32 circleOrigin = 2;
1092+
ism.addDomain(origin, circleOrigin);
1093+
ism.enrollRemoteRouter(origin, hook);
1094+
1095+
// https://optimistic.etherscan.io/tx/0x4a8c5aef605bd1a79d7e4ab7b1852d246a05859a168db2b4791563877f2f3325
1096+
uint256 amount = 2;
1097+
uint256 fee = 1;
1098+
bytes
1099+
memory cctpMessage = hex"0000000100000002000000069abb52aa4e37d2ee3e521f9bc92e97581a68dadcd826fd2abaa5150de95db90e00000000000000000000000028b5a0e9c621a5badaa536219b3a228c8168cf5d00000000000000000000000028b5a0e9c621a5badaa536219b3a228c8168cf5d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000003e8000003e8000000010000000000000000000000000b2c639c533813f4aa9d7837caf62653d097ff85000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000001f825d8";
1100+
1101+
// https://iris-api.circle.com/v2/messages/2?transactionHash=0x4a8c5aef605bd1a79d7e4ab7b1852d246a05859a168db2b4791563877f2f3325
1102+
bytes
1103+
memory attestation = hex"f75d61f667685827a63a857fcfae06fd9c42860c9a94175a2041a98941c874303aa44a973bf28b447ecc39e25d81a869584e9379d41f669dc526bf3b6810a0161c278bcd556ac5dd462095094af97a8773b33c00788a362c424d5569bdb4c2fb853ab4aefab3839bc8128e280f16fc09c6cfb11361061e527f5804fa6c6b130dc91b";
1104+
1105+
bytes memory metadata = abi.encode(cctpMessage, attestation);
1106+
1107+
bytes memory message = abi.encodePacked(
1108+
uint8(3),
1109+
uint32(0),
1110+
origin,
1111+
hook,
1112+
ism.localDomain(),
1113+
address(ism).addressToBytes32(),
1114+
abi.encode(hook, amount)
1115+
);
1116+
1117+
vm.expectEmit(true, true, true, true, address(ism.tokenMessenger()));
1118+
emit MintAndWithdraw(deployer, amount - fee, usdc, fee);
1119+
ism.verify(metadata, message);
1120+
}
1121+
10491122
function testFork_postDispatch(
10501123
bytes32 recipient,
10511124
bytes calldata body
@@ -1103,15 +1176,16 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
11031176
amount
11041177
);
11051178

1179+
uint256 tokenQuote = quote[1].amount;
11061180
vm.startPrank(user);
1107-
tokenOrigin.approve(address(tbOrigin), quote[1].amount);
1181+
tokenOrigin.approve(address(tbOrigin), tokenQuote);
11081182

11091183
vm.expectCall(
11101184
address(tokenMessengerOrigin),
11111185
abi.encodeCall(
11121186
ITokenMessengerV2.depositForBurn,
11131187
(
1114-
amount,
1188+
tokenQuote,
11151189
cctpDestination,
11161190
user.addressToBytes32(),
11171191
address(tokenOrigin),
@@ -1174,7 +1248,6 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
11741248
vm.expectRevert(bytes("Invalid TokenMessenger CCTP version"));
11751249
TokenBridgeCctpV2 v2 = new TokenBridgeCctpV2(
11761250
address(tokenOrigin),
1177-
scale,
11781251
address(mailboxOrigin),
11791252
messageTransmitterOrigin,
11801253
tokenMessengerOrigin,
@@ -1186,7 +1259,6 @@ contract TokenBridgeCctpV2Test is TokenBridgeCctpV1Test {
11861259
vm.expectRevert(bytes("Invalid messageTransmitter CCTP version"));
11871260
v2 = new TokenBridgeCctpV2(
11881261
address(tokenOrigin),
1189-
scale,
11901262
address(mailboxOrigin),
11911263
messageTransmitterOrigin,
11921264
tokenMessengerOrigin,

typescript/sdk/src/token/deploy.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,13 @@ abstract class TokenDeployer<
152152
case 'V1':
153153
return [
154154
config.token,
155-
scale,
156155
config.mailbox,
157156
config.messageTransmitter,
158157
config.tokenMessenger,
159158
];
160159
case 'V2':
161160
return [
162161
config.token,
163-
scale,
164162
config.mailbox,
165163
config.messageTransmitter,
166164
config.tokenMessenger,

0 commit comments

Comments
 (0)