Skip to content

Commit e19013d

Browse files
committed
improved, extended, and unified decoding, added CoreBridgeLib
1 parent 5c24ed7 commit e19013d

31 files changed

+4552
-1479
lines changed

docs/RawDispatcher.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Contracts using this base class have to override the associated virtual function
116116

117117
Of course, integrations have to actually make use of these custom dispatch functions to reap their benefits. To this end, contracts using the RawDispatcher pattern/base call should come with two additional "SDKs":
118118
1. For on-chain integrations: A Solidity integrator `library` that fills the role of what is otherwise provided by an `interface`. That is, a set of encoding and decoding functions that mirror the contract's ABI but implement the custom call format of the contract decoding its returned `bytes` under the hood.
119-
2. For off-chain integrations: A Typescript analog of the integrator library. The [layouting mechanism in the Wormhole Typescript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/base/src/utils/layout) offers an easy, declarative way to specify such custom encodings. It also provides [definitions of common types](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/layout-items) and various examples of its use can be found in [the protocols defined in the SDK itself](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols) (e.g. [TokenBridge Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts), [WormholeRelayer Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/relayer/relayerLayout.ts), [CCTP messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/circleBridge/circleBridgeLayout.ts)) or strewn throughout the various example repos e.g. [example-swap-layer](https://github.com/wormhole-foundation/example-swap-layer/blob/main/evm/ts-sdk/src/layout.ts) or [example-native-token-transfers](https://github.com/wormhole-foundation/example-native-token-transfers/tree/main/sdk/definitions/src/layouts). (A proper, standalone tutorial is sadly still outstanding at the time of writing.)
119+
2. For off-chain integrations: A Typescript analog of the integrator library. The [layouting package](https://www.npmjs.com/package/binary-layout) offers an easy, declarative way to specify such custom encodings. It is also used in [the Wormhole Typescript SDK](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/base/src/utils/layout.ts) to [define common types](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/layout-items) and various other layout examples can be found in [the protocols defined within the SDK itself](https://github.com/wormhole-foundation/wormhole-sdk-ts/tree/main/core/definitions/src/protocols) (e.g. [TokenBridge Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/tokenBridge/tokenBridgeLayout.ts), [WormholeRelayer Messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/relayer/relayerLayout.ts), [CCTP messages](https://github.com/wormhole-foundation/wormhole-sdk-ts/blob/main/core/definitions/src/protocols/circleBridge/circleBridgeLayout.ts)) or strewn throughout the various example repos e.g. [example-swap-layer](https://github.com/wormhole-foundation/example-swap-layer/blob/main/evm/ts-sdk/src/layout.ts) or [example-native-token-transfers](https://github.com/wormhole-foundation/example-native-token-transfers/tree/main/sdk/definitions/src/layouts).
120120

121121
### Limitations
122122

docs/Testing.md

-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ The `WormholeCctpSimulator` contract can be deployed to simulate a virtual `Worm
4242

4343
Forge's `deal` cheat code does not work for USDC. `UsdcDealer` is another override library that implements a `deal` function that allows minting of USDC.
4444

45-
### CctpMessages
46-
47-
Library to parse CCTP messages composed/emitted by Circle's `TokenMessenger` and `MessageTransmitter` contracts. Used in `CctpOverride` and `WormholeCctpSimulator`.
48-
4945
### ERC20Mock
5046

5147
Copy of SolMate's ERC20 Mock token that uses the overrideable `IERC20` interface of this SDK to guarantee compatibility.

src/Utils.sol

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: Apache 2
22
pragma solidity ^0.8.4;
33

4-
import { WORD_SIZE, SCRATCH_SPACE_PTR, FREE_MEMORY_PTR } from "./constants/Common.sol";
4+
import {WORD_SIZE, SCRATCH_SPACE_PTR, FREE_MEMORY_PTR} from "./constants/Common.sol";
55

66
error NotAnEvmAddress(bytes32);
77

@@ -19,6 +19,10 @@ function fromUniversalAddress(bytes32 universalAddr) pure returns (address addr)
1919
}
2020
}
2121

22+
function minSigsForQuorum(uint numGuardians) pure returns (uint) { unchecked {
23+
return numGuardians * 2 / 3 + 1;
24+
}}
25+
2226
/**
2327
* Reverts with a given buffer data.
2428
* Meant to be used to easily bubble up errors from low level calls when they fail.

src/legacy/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Legacy Directory
2+
3+
The legacy directory is a dumping ground for all files that are kept for backwards compatibility but that are not kept up to the standards of the rest of the SDK.
4+
5+
# WormholeCctp
6+
7+
The WormholeCctpTokenMessenger is a standalone implementation of [WormholeCircleIntegration](https://github.com/wormhole-foundation/wormhole-circle-integration/).
8+
9+
Its has two associated files:
10+
1. WormholeCctpMessages (message encoding/decoding)
11+
2. WormholeCctpSimulator (for forge testing)
12+
13+
WormholeCctp functionality was extracted during the [liquidity layer](https://github.com/wormhole-foundation/example-liquidity-layer/blob/main/evm/src/shared/WormholeCctpTokenMessenger.sol) development process when it was recognized that going through the circle integration contract was adding additional, unnecessary overhead (gas, deployment, registration).
14+
15+
It later got moved to the Solidity SDK and used for testing in the [swap layer](https://github.com/wormhole-foundation/example-swap-layer).
16+
17+
The implementation is now considered legacy because it's unlikely to see any future use and thus keeping it up to date is not worth the effort.
18+
19+
A proper overhaul, besides introducing common optimizations in the rest of the SDK, would also rework the message format. Currently, most of the information that's in the VAA is actually redundant and could simply be taken from the CctpBurnTokenMessage. The only 2 pieces of information that should actually go into the VAA to uniquely link it to its associated CCTP messages is the CCTP nonce and the sourceDomain. But changing the message format would break backwards compatibility, thus interfering with its only expected use case.

src/legacy/WormholeCctpMessages.sol

+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.19;
3+
4+
import {BytesParsing} from "wormhole-sdk/libraries/BytesParsing.sol";
5+
6+
// ╭───────────────────────────────────────────────────────────────────────╮
7+
// │ Library for encoding and decoding WormholeCctpTokenMessenger messages │
8+
// ╰───────────────────────────────────────────────────────────────────────╯
9+
10+
library WormholeCctpMessageLib {
11+
using BytesParsing for bytes;
12+
using {BytesParsing.checkLength} for uint;
13+
14+
uint8 internal constant PAYLOAD_ID_DEPOSIT = 1;
15+
16+
// uint private constant _DEPOSIT_META_SIZE =
17+
// 32 /*universalTokenAddress*/ +
18+
// 32 /*amount*/ +
19+
// 4 /*sourceCctpDomain*/ +
20+
// 4 /*targetCctpDomain*/ +
21+
// 8 /*cctpNonce*/ +
22+
// 32 /*burnSource*/ +
23+
// 32 /*mintRecipient*/;
24+
25+
error PayloadTooLarge(uint256);
26+
error InvalidPayloadId(uint8);
27+
28+
function encodeDeposit(
29+
bytes32 universalTokenAddress,
30+
uint256 amount,
31+
uint32 sourceCctpDomain,
32+
uint32 targetCctpDomain,
33+
uint64 cctpNonce,
34+
bytes32 burnSource,
35+
bytes32 mintRecipient,
36+
bytes memory payload
37+
) internal pure returns (bytes memory) {
38+
uint payloadLen = payload.length;
39+
if (payloadLen > type(uint16).max)
40+
revert PayloadTooLarge(payloadLen);
41+
42+
return abi.encodePacked(
43+
PAYLOAD_ID_DEPOSIT,
44+
universalTokenAddress,
45+
amount,
46+
sourceCctpDomain,
47+
targetCctpDomain,
48+
cctpNonce,
49+
burnSource,
50+
mintRecipient,
51+
uint16(payloadLen),
52+
payload
53+
);
54+
}
55+
56+
function decodeDepositCd(bytes calldata vaaPayload) internal pure returns (
57+
bytes32 token,
58+
uint256 amount,
59+
uint32 sourceCctpDomain,
60+
uint32 targetCctpDomain,
61+
uint64 cctpNonce,
62+
bytes32 burnSource,
63+
bytes32 mintRecipient,
64+
bytes calldata payload
65+
) {
66+
uint offset = 0;
67+
( token,
68+
amount,
69+
sourceCctpDomain,
70+
targetCctpDomain,
71+
cctpNonce,
72+
burnSource,
73+
mintRecipient,
74+
offset
75+
) = decodeDepositHeaderCdUnchecked(vaaPayload, offset);
76+
77+
(payload, offset) = decodeDepositPayloadCdUnchecked(vaaPayload, offset);
78+
vaaPayload.length.checkLength(offset);
79+
}
80+
81+
function decodeDepositHeaderCdUnchecked(
82+
bytes calldata encoded,
83+
uint offset
84+
) internal pure returns (
85+
bytes32 token,
86+
uint256 amount,
87+
uint32 sourceCctpDomain,
88+
uint32 targetCctpDomain,
89+
uint64 cctpNonce,
90+
bytes32 burnSource,
91+
bytes32 mintRecipient,
92+
uint payloadOffset
93+
) {
94+
uint8 payloadId;
95+
(payloadId, offset) = encoded.asUint8CdUnchecked(offset);
96+
checkPayloadId(payloadId, PAYLOAD_ID_DEPOSIT);
97+
(token, offset) = encoded.asBytes32CdUnchecked(offset);
98+
(amount, offset) = encoded.asUint256CdUnchecked(offset);
99+
(sourceCctpDomain, offset) = encoded.asUint32CdUnchecked(offset);
100+
(targetCctpDomain, offset) = encoded.asUint32CdUnchecked(offset);
101+
(cctpNonce, offset) = encoded.asUint64CdUnchecked(offset);
102+
(burnSource, offset) = encoded.asBytes32CdUnchecked(offset);
103+
(mintRecipient, offset) = encoded.asBytes32CdUnchecked(offset);
104+
payloadOffset = offset;
105+
}
106+
107+
function decodeDepositPayloadCdUnchecked(
108+
bytes calldata encoded,
109+
uint offset
110+
) internal pure returns (bytes calldata payload, uint newOffset) {
111+
(payload, newOffset) = encoded.sliceUint16PrefixedCdUnchecked(offset);
112+
}
113+
114+
function decodeDepositMem(bytes memory vaaPayload) internal pure returns (
115+
bytes32 token,
116+
uint256 amount,
117+
uint32 sourceCctpDomain,
118+
uint32 targetCctpDomain,
119+
uint64 cctpNonce,
120+
bytes32 burnSource,
121+
bytes32 mintRecipient,
122+
bytes memory payload
123+
) {
124+
uint offset = 0;
125+
( token,
126+
amount,
127+
sourceCctpDomain,
128+
targetCctpDomain,
129+
cctpNonce,
130+
burnSource,
131+
mintRecipient,
132+
offset
133+
) = decodeDepositHeaderMemUnchecked(vaaPayload, 0);
134+
135+
(payload, offset) = decodeDepositPayloadMemUnchecked(vaaPayload, offset);
136+
vaaPayload.length.checkLength(offset);
137+
}
138+
139+
function decodeDepositHeaderMemUnchecked(
140+
bytes memory encoded,
141+
uint offset
142+
) internal pure returns (
143+
bytes32 token,
144+
uint256 amount,
145+
uint32 sourceCctpDomain,
146+
uint32 targetCctpDomain,
147+
uint64 cctpNonce,
148+
bytes32 burnSource,
149+
bytes32 mintRecipient,
150+
uint payloadOffset
151+
) {
152+
uint8 payloadId;
153+
(payloadId, offset) = encoded.asUint8MemUnchecked(offset);
154+
checkPayloadId(payloadId, PAYLOAD_ID_DEPOSIT);
155+
(token, offset) = encoded.asBytes32MemUnchecked(offset);
156+
(amount, offset) = encoded.asUint256MemUnchecked(offset);
157+
(sourceCctpDomain, offset) = encoded.asUint32MemUnchecked(offset);
158+
(targetCctpDomain, offset) = encoded.asUint32MemUnchecked(offset);
159+
(cctpNonce, offset) = encoded.asUint64MemUnchecked(offset);
160+
(burnSource, offset) = encoded.asBytes32MemUnchecked(offset);
161+
(mintRecipient, offset) = encoded.asBytes32MemUnchecked(offset);
162+
payloadOffset = offset;
163+
}
164+
165+
function decodeDepositPayloadMemUnchecked(
166+
bytes memory encoded,
167+
uint offset
168+
) internal pure returns (bytes memory payload, uint newOffset) {
169+
(payload, newOffset) = encoded.sliceUint16PrefixedMemUnchecked(offset);
170+
}
171+
172+
function checkPayloadId(uint8 encoded, uint8 expected) internal pure {
173+
if (encoded != expected)
174+
revert InvalidPayloadId(encoded);
175+
}
176+
}

src/testing/WormholeCctpSimulator.sol src/legacy/WormholeCctpSimulator.sol

+23-19
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ pragma solidity ^0.8.19;
33

44
import {Vm} from "forge-std/Vm.sol";
55

6-
import "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol";
7-
8-
import "wormhole-sdk/interfaces/IWormhole.sol";
9-
import {WormholeCctpMessages} from "wormhole-sdk/libraries/WormholeCctpMessages.sol";
10-
import {toUniversalAddress} from "wormhole-sdk/Utils.sol";
11-
12-
import {VM_ADDRESS} from "wormhole-sdk/testing/Constants.sol";
13-
import "wormhole-sdk/testing/CctpOverride.sol";
14-
import "wormhole-sdk/testing/WormholeOverride.sol";
6+
import {IWormhole} from "wormhole-sdk/interfaces/IWormhole.sol";
7+
import {IMessageTransmitter} from "wormhole-sdk/interfaces/cctp/IMessageTransmitter.sol";
8+
import {ITokenMessenger} from "wormhole-sdk/interfaces/cctp/ITokenMessenger.sol";
9+
import {ITokenMinter} from "wormhole-sdk/interfaces/cctp/ITokenMinter.sol";
10+
11+
import {
12+
CctpMessageLib,
13+
CctpTokenBurnMessage
14+
} from "wormhole-sdk/libraries/CctpMessages.sol";
15+
import {WormholeCctpMessageLib} from "wormhole-sdk/legacy/WormholeCctpMessages.sol";
16+
import {toUniversalAddress} from "wormhole-sdk/Utils.sol";
17+
import {VM_ADDRESS} from "wormhole-sdk/testing/Constants.sol";
18+
import {CctpOverride} from "wormhole-sdk/testing/CctpOverride.sol";
19+
import {WormholeOverride} from "wormhole-sdk/testing/WormholeOverride.sol";
1520

1621
//faked foreign call chain:
1722
// foreignCaller -> foreignSender -> FOREIGN_TOKEN_MESSENGER -> foreign MessageTransmitter
1823
//example:
1924
// foreignCaller = swap layer
2025
// foreignSender = liquidity layer - implements WormholeCctpTokenMessenger
21-
// emits WormholeCctpMessages.Deposit VAA with a RedeemFill payload
26+
// emits WormholeCctpMessageLib.Deposit VAA with a RedeemFill payload
2227

2328
//local call chain using faked vaa and circle attestation:
2429
// test -> intermediate contract(s) -> mintRecipient -> MessageTransmitter -> TokenMessenger
@@ -36,9 +41,8 @@ bytes32 constant FOREIGN_USDC =
3641
//simulates a foreign WormholeCctpTokenMessenger
3742
contract WormholeCctpSimulator {
3843
using WormholeOverride for IWormhole;
39-
using CctpMessages for CctpTokenBurnMessage;
4044
using CctpOverride for IMessageTransmitter;
41-
using CctpMessages for bytes;
45+
using CctpMessageLib for bytes;
4246
using { toUniversalAddress } for address;
4347

4448
Vm constant vm = Vm(VM_ADDRESS);
@@ -138,14 +142,14 @@ contract WormholeCctpSimulator {
138142
encodedVaa = wormhole.craftVaa(
139143
foreignChain,
140144
foreignSender,
141-
WormholeCctpMessages.encodeDeposit(
142-
burnMsg.burnToken,
145+
WormholeCctpMessageLib.encodeDeposit(
146+
burnMsg.body.burnToken,
143147
amount,
144148
burnMsg.header.sourceDomain,
145149
burnMsg.header.destinationDomain,
146150
burnMsg.header.nonce,
147151
foreignCaller,
148-
burnMsg.mintRecipient,
152+
burnMsg.body.mintRecipient,
149153
payload
150154
)
151155
);
@@ -165,10 +169,10 @@ contract WormholeCctpSimulator {
165169
burnMsg.header.sender = FOREIGN_TOKEN_MESSENGER;
166170
burnMsg.header.recipient = address(tokenMessenger).toUniversalAddress();
167171
burnMsg.header.destinationCaller = destinationCaller.toUniversalAddress();
168-
burnMsg.burnToken = FOREIGN_USDC;
169-
burnMsg.mintRecipient = mintRecipient.toUniversalAddress();
170-
burnMsg.amount = amount;
171-
burnMsg.messageSender = foreignSender;
172+
burnMsg.body.burnToken = FOREIGN_USDC;
173+
burnMsg.body.mintRecipient = mintRecipient.toUniversalAddress();
174+
burnMsg.body.amount = amount;
175+
burnMsg.body.messageSender = foreignSender;
172176

173177
encodedCctpMessage = burnMsg.encode();
174178
cctpAttestation = messageTransmitter.sign(burnMsg);

0 commit comments

Comments
 (0)