Skip to content

Commit 54fa75d

Browse files
authored
large refactor with a focus on WormholeRelayer (#83)
* replace old WormholeRelayer integration helpers with new, streamlined, more "batteries included" implementation * implement WormholeForkTest for convenient fork testing of Wormhole and CCTP * implement WormholeRelayerTest for convenient fork testing of WormholeRelayer * implement WormholeRelayer demo integration + tests that leverage new test utils * implement DeliveryProviderStub + ability to override default delivery provider for better testability * introduce integrator friendly ICoreBridge and ITokenBridge interfaces * add token decimal normalization (for token bridge) * make TypedUnits implementation more uniform/consistent * update constants to be in line with latest ts-sdk release
1 parent baa0850 commit 54fa75d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3938
-3942
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ This SDK comes with its own IERC20 interface and SafeERC20 implementation. Given
4444

4545
For additional documentation of components, see the docs directory.
4646

47+
## Style
48+
49+
This SDK largely follows [the Solidity style guide](https://docs.soliditylang.org/en/latest/style-guide.html) with some exceptions/modifications:
50+
* indentation uses 2 instead of 4 spaces
51+
* maximum line length where feasible is 100 characters (urls in comments are an exception)
52+
* [order of functions](https://docs.soliditylang.org/en/latest/style-guide.html) is roughly followed but more important functions might be sorted to the top.
53+
* additional whitespace are at times added to create more compelling visual blocks (e.g. for a block of related assignments)
54+
* function modifiers do not get a separate line each once line length is exceeded
55+
* NatSpec is avoided because it favors a "box-checking" approach towards documentation rather than focusing on essentials and rationales
56+
4757
## Philosophy/Creeds
4858

4959
In This House We Believe:

gen/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
GENERATORS = chains cctpDomains
1+
GENERATORS = chains cctpDomains chainConsts
22
chains_TARGET = constants/Chains.sol
33
cctpDomains_TARGET = constants/CctpDomains.sol
4+
chainConsts_TARGET = testing/ChainConsts.sol
45

56
fnGeneratorTarget = ../src/$($(1)_TARGET)
67
GENERATOR_TARGETS = $(foreach generator,$(GENERATORS),$(call fnGeneratorTarget,$(generator)))

gen/chainConsts.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { toCapsSnakeCase } from "./utils";
2+
import {
3+
platformToChains,
4+
contracts,
5+
rpc,
6+
circle,
7+
} from "@wormhole-foundation/sdk-base";
8+
import { EvmAddress } from "@wormhole-foundation/sdk-evm";
9+
10+
const {coreBridge, tokenBridge, relayer, circleContracts} = contracts;
11+
const {usdcContract, circleChainId} = circle;
12+
13+
console.log(
14+
`// SPDX-License-Identifier: Apache 2
15+
pragma solidity ^0.8.0;
16+
17+
// ╭──────────────────────────────────────╮
18+
// │ Auto-generated by gen/chainConsts.ts │
19+
// ╰──────────────────────────────────────╯
20+
21+
// This file contains 2 libraries and an associated set of free standing functions:
22+
//
23+
// The libraries:
24+
// 1. MainnetChainConstants
25+
// 2. TestnetChainConstants
26+
//
27+
// Both provide the same set of functions, which are also provided as free standing functions
28+
// with an additional network parameter, though library functions use an underscore prefix to
29+
// avoid a declaration shadowing bug that has been first reported in the solc Github in 2020...
30+
// see https://github.com/ethereum/solidity/issues/10155
31+
//
32+
// Function │ Parameter │ Returns
33+
// ────────────────────────┼───────────┼────────────
34+
// chainName │ chainId │ string
35+
// defaultRPC │ chainId │ string
36+
// coreBridge │ chainId │ address
37+
// tokenBridge │ chainId │ address
38+
// wormholeRelayer │ chainId │ address
39+
// cctpDomain │ chainId │ cctpDomain
40+
// usdc │ chainId │ address
41+
// cctpMessageTransmitter │ chainId │ address
42+
// cctpTokenMessenger │ chainId │ address
43+
//
44+
// Empty fields return invalid values (empty string, address(0), INVALID_CCTP_DOMAIN)
45+
46+
import "wormhole-sdk/constants/Chains.sol";
47+
import "wormhole-sdk/constants/CctpDomains.sol";
48+
49+
uint32 constant INVALID_CCTP_DOMAIN = type(uint32).max;
50+
error UnsupportedChainId(uint16 chainId);
51+
error UnsupportedCctpDomain(uint32 cctpDomain);
52+
`
53+
);
54+
55+
const evmChains = platformToChains("Evm");
56+
type EvmChain = typeof evmChains[number];
57+
const networks = ["Mainnet", "Testnet"] as const;
58+
59+
const networkChains = {
60+
Mainnet: evmChains.filter(chain =>
61+
contracts.coreBridge.has("Mainnet", chain)),
62+
//remove testnets that were superseded by Sepolia testnets
63+
Testnet: evmChains.filter(chain =>
64+
contracts.coreBridge.has("Testnet", chain) &&
65+
chain !== "Ethereum" &&
66+
!evmChains.includes(chain + "Sepolia" as EvmChain)
67+
)
68+
}
69+
70+
enum AliasType {
71+
String = "string",
72+
Address = "address",
73+
CctpDomain = "uint32",
74+
}
75+
76+
const emptyValue = {
77+
[AliasType.String ]: "",
78+
[AliasType.Address ]: "address(0)",
79+
[AliasType.CctpDomain]: "INVALID_CCTP_DOMAIN",
80+
}
81+
82+
const returnParam = (aliasType: AliasType) =>
83+
aliasType === AliasType.String ? "string memory" : aliasType;
84+
85+
const toReturnValue = (value: string, returnType: AliasType) => {
86+
if (returnType === AliasType.Address && value)
87+
return new EvmAddress(value).toString(); //ensures checksum format
88+
89+
const ret = value || emptyValue[returnType];
90+
return returnType === AliasType.String
91+
? `\"${ret}\"`
92+
: ret;
93+
}
94+
95+
const functions = [
96+
["chainName", AliasType.String ],
97+
["defaultRPC", AliasType.String ],
98+
["coreBridge", AliasType.Address ],
99+
["tokenBridge", AliasType.Address ],
100+
["wormholeRelayer", AliasType.Address ],
101+
["cctpDomain", AliasType.CctpDomain],
102+
["usdc", AliasType.Address ],
103+
["cctpMessageTransmitter", AliasType.Address ],
104+
["cctpTokenMessenger", AliasType.Address ],
105+
] as const;
106+
107+
const indent = (lines: string[]) => lines.map(line => ` ${line}`);
108+
109+
console.log(
110+
functions.map(([name, returnType]) => [
111+
`function ${name}(bool mainnet, uint16 chainId)` +
112+
` pure returns (${returnParam(returnType)}) {`,
113+
...indent([
114+
`return mainnet`,
115+
` ? MainnetChainConstants._${name}(chainId)`,
116+
` : TestnetChainConstants._${name}(chainId);`,
117+
]),
118+
`}`
119+
].join("\n")
120+
).join("\n\n")
121+
);
122+
123+
for (const network of networks) {
124+
const functionDefinition = (
125+
name: string,
126+
returnType: AliasType,
127+
mapping: (chain: EvmChain) => string,
128+
) => indent([
129+
`function _${name}(uint16 chainId) internal pure returns (${returnParam(returnType)}) {`,
130+
...indent([
131+
...networkChains[network].map(chain => [
132+
`if (chainId == CHAIN_ID_${toCapsSnakeCase(chain)})`,
133+
` return ${toReturnValue(mapping(chain), returnType)};`
134+
]).flat(),
135+
`revert UnsupportedChainId(chainId);`
136+
]),
137+
`}`
138+
]).join("\n");
139+
140+
const cctpDomainConst = (chain: EvmChain) =>
141+
circleChainId.has(network, chain)
142+
? `CCTP_DOMAIN_${toCapsSnakeCase(chain)}`
143+
: "INVALID_CCTP_DOMAIN";
144+
145+
const mappings = {
146+
chainName: chain => chain,
147+
defaultRPC: chain => rpc.rpcAddress(network, chain),
148+
coreBridge: chain => coreBridge.get(network, chain),
149+
tokenBridge: chain => tokenBridge.get(network, chain),
150+
wormholeRelayer: chain => relayer.get(network, chain),
151+
cctpDomain: chain => cctpDomainConst(chain),
152+
usdc: chain => usdcContract.get(network, chain),
153+
cctpMessageTransmitter: chain => circleContracts.get(network, chain)?.messageTransmitter,
154+
cctpTokenMessenger: chain => circleContracts.get(network, chain)?.tokenMessenger,
155+
};
156+
157+
console.log([
158+
``,
159+
`library ${network}ChainConstants {`,
160+
functions
161+
.map(([name, returnType]) => functionDefinition(name, returnType, mappings[name]))
162+
.join("\n\n"),
163+
`}`
164+
].join("\n")
165+
);
166+
}

gen/testing.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TEST_RPC_URL=https://ethereum-rpc.publicnode.com
2-
TEST_USDC_ADDRESS=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
2+
TEST_USDC_ADDRESS=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
33
TEST_CCTP_TOKEN_MESSENGER_ADDRESS=0xbd3fa81b58ba92a82136038b25adec7066af3155
44
TEST_CCTP_MESSAGE_TRANSMITTER_ADDRESS=0x0a992d191deec32afe36203ad87d7d289a738f81
55
TEST_WNATIVE_ADDRESS=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2

src/Utils.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ import {
2121
eagerAnd,
2222
eagerOr
2323
} from "wormhole-sdk/utils/EagerOps.sol";
24+
import {
25+
normalizeAmount,
26+
deNormalizeAmount
27+
} from "wormhole-sdk/utils/DecimalNormalization.sol";

src/WormholeRelayer.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.19;
3+
4+
import "wormhole-sdk/WormholeRelayer/Keys.sol";
5+
import "wormhole-sdk/WormholeRelayer/Sender.sol";
6+
import "wormhole-sdk/WormholeRelayer/Receiver.sol";
7+
import "wormhole-sdk/WormholeRelayer/AdditionalMessages.sol";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-License-Identifier: Apache 2
2+
pragma solidity ^0.8.19;
3+
4+
import {WORD_SIZE} from "wormhole-sdk/constants/Common.sol";
5+
6+
// The `additionalMessages` parameter of `receiveWormholeMessages` contains the list of
7+
// VAAs, CCTP messages, and potentially other messages that were requested for delivery in
8+
// their speficied order.
9+
//
10+
// VAAs requested via VaaKey are delivered as normally encoded VAAs, just as one would expect.
11+
//
12+
// CCTP messages on the other hand do not include the associated attestation in their format but
13+
// instead expect the attestation to be provided separately.
14+
// So the message associated with a CctpKey is a tuple of (CCTP message, attestation) and
15+
// `unpackAdditionalCctpMessage` can be used to extract them.
16+
//
17+
// To further decode VAAs or CctpMessages, check out the `VaaLib` and `CctpLib` libraries.
18+
// To verify VAAs more efficiently, check out the `CoreBridge` library.
19+
20+
function unpackAdditionalCctpMessage(
21+
bytes calldata message
22+
) pure returns (bytes calldata cctpMessage, bytes calldata attestation) {
23+
assembly ("memory-safe") {
24+
// message.offset points to ABI-encoded struct {bytes cctpMessage, bytes attestation}
25+
// First word is offset to cctpMessage bytes (always 0x40)
26+
// Second word is offset to attestation bytes
27+
let attestationRelativeOffset := calldataload(add(message.offset, WORD_SIZE))
28+
29+
let cctpMessageLengthOffset := add(message.offset, 0x40)
30+
cctpMessage.offset := add(cctpMessageLengthOffset, WORD_SIZE)
31+
cctpMessage.length := calldataload(cctpMessageLengthOffset)
32+
33+
let attestationLengthOffset := add(message.offset, attestationRelativeOffset)
34+
attestation.offset := add(attestationLengthOffset, WORD_SIZE)
35+
attestation.length := calldataload(attestationLengthOffset)
36+
}
37+
}

src/WormholeRelayer/Base.sol

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)