diff --git a/config.ts b/config.ts index bde06db..74ae4ae 100644 --- a/config.ts +++ b/config.ts @@ -18,7 +18,7 @@ const config: Config = { appId: 'S36OISFKTX', apiKey: 'fc77ad501873a0a81b4c39e02a4aaaac', indexName: 'crawler_Toposware docs platform (new)', - placeholder: 'Search documentation' + placeholder: 'Search documentation', }, googleSiteVerification: 'ewK_x9p9N-cr_rA0dKgdo6YGqZGgnRcBVn2bZeaZQ_o', navigation: [ @@ -27,123 +27,136 @@ const config: Config = { content: [ { label: 'Chapter overview: What is Topos?', - path: '/content/module-1' + path: '/content/module-1', }, { label: 'Introduction', - path: '/content/module-1/1-introduction.html' + path: '/content/module-1/1-introduction.html', }, { label: 'Why Topos?', - path: '/content/module-1/2-why-topos.html' + path: '/content/module-1/2-why-topos.html', }, { label: 'Use cases', - path: '/content/module-1/3-use-cases.html' + path: '/content/module-1/3-use-cases.html', }, { label: 'Topos protocol', - path: '/content/module-1/4-protocol.html' + path: '/content/module-1/4-protocol.html', }, { label: 'Cross-subnet messages', - path: '/content/module-1/5-cross-subnet.html' + path: '/content/module-1/5-cross-subnet.html', }, { label: 'Messaging protocol', - path: '/content/module-1/6-messaging-protocol.html' - } - ] + path: '/content/module-1/6-messaging-protocol.html', + }, + ], }, { label: 'First steps with Topos', content: [ { label: 'Chapter overview: First steps with Topos', - path: '/content/module-2' + path: '/content/module-2', }, { label: 'ERC20 Messaging', - path: '/content/module-2/1-ERC20-Messaging.html' + path: '/content/module-2/1-ERC20-Messaging.html', }, { label: 'Topos Explorer', - path: '/content/module-2/2-explorer.html' + path: '/content/module-2/2-explorer.html', }, { label: 'Topos Playground', - path: '/content/module-2/3-topos-playground.html' + path: '/content/module-2/3-topos-playground.html', }, { label: 'Topos CLI', - path: '/content/module-2/4-cli.html' + path: '/content/module-2/4-cli.html', }, { label: 'Topos Testnet', - path: '/content/module-2/5-testnet.html' - } - ] + path: '/content/module-2/5-testnet.html', + }, + ], + }, + { + label: 'Next steps', + content: [ + { + label: 'Chapter overview: Next steps', + path: '/content/module-3', + }, + { + label: 'Create Your Messaging Protocol', + path: '/content/module-3/1-create-your-messaging-protocol.html', + }, + ], }, { label: 'Network', content: [ { label: 'Faucet', - path: 'https://faucet.testnet-1.topos.technology/' + path: 'https://faucet.testnet-1.topos.technology/', }, { label: 'Topos Blockscout', - path: 'https://topos.blockscout.testnet-1.topos.technology/' + path: 'https://topos.blockscout.testnet-1.topos.technology/', }, { label: 'Incal Blockscout', - path: 'https://incal.blockscout.testnet-1.topos.technology/' + path: 'https://incal.blockscout.testnet-1.topos.technology/', }, { label: 'Explorer', - path: 'https://explorer.testnet-1.topos.technology/' + path: 'https://explorer.testnet-1.topos.technology/', }, { label: 'Important Addresses', - path: '/content/topos-reference/network.html' - } - ] + path: '/content/topos-reference/network.html', + }, + ], }, { label: 'Topos Reference', content: [ { label: 'FAQ', - path: '/content/topos-reference/faq.html' + path: '/content/topos-reference/faq.html', }, { label: 'Weak Causal Probabilistic Reliable Broadcast', - path: '/content/topos-reference/wcprb.html' + path: '/content/topos-reference/wcprb.html', }, { label: 'Glossary', - path: '/content/glossary.html' - } - ] + path: '/content/glossary.html', + }, + ], }, { label: 'Legal', content: [ { label: 'Privacy policy', - path: '/legal/privacy-policy.html' - } - ] - } + path: '/legal/privacy-policy.html', + }, + ], + }, ], footerLinks: [ { label: 'Privacy Policy', - path: '/legal/privacy-policy.html' - } + path: '/legal/privacy-policy.html', + }, ], storage: { - cookieConsentKey: 'cookieConsent' + cookieConsentKey: 'cookieConsent', }, }; diff --git a/content/module-1/6-messaging-protocol.md b/content/module-1/6-messaging-protocol.md index f130696..f349878 100644 --- a/content/module-1/6-messaging-protocol.md +++ b/content/module-1/6-messaging-protocol.md @@ -9,20 +9,16 @@ The Topos protocol provides for the creation, verification and delivery of certi The developers of dApps and cross-subnet messaging flows can leverage these certificates to design **secure messaging protocols**. By storing relevant certificates on-chain, it is possible to benefit from their security guarantees when handling cross-subnet messages. -To demonstrate best practice and help bootstrap subnet administrators, Topos has created a series of smart contracts: +To demonstrate best practices and help bootstrap subnet administrators, Topos has created a series of smart contracts: 1. The **ToposCore** smart contract can be used to store certificates on-chain. Although not compulsory, it makes sense for a subnet to have a single instance of such a certificate-storing smart contract. -2. The **ToposMessaging** smart contract makes use of ToposCore. It has functions to verify proofs of inclusion and to mark completed messages as used, thereby preventing replay. It is not meant to be deployed directly, but instead to be used as an inherited smart contract for the messaging protocol proper. +2. The **ToposMessaging** smart contract gathers features that are core to messaging protocols. It has functions to verify proofs of inclusion and to mark completed messages as used, thereby preventing replay. To ease the development of new messaging protocols, these features can be had for free by creating a messaging protocol smart contract that inherits ToposMessaging. 3. **ERC20Messaging** is an example of a messaging protocol smart contract that implements the semantics of a cross-subnet ERC-20 token transfer. It inherits from ToposMessaging. With ERC20Messaging, it is possible to deploy new ERC-20 token smart contracts, and to use those to send and receive cross-subnet token transfers. With this contract, a cross-subnet token transfer was chosen to consist of a _burn_ on the source subnet and a _mint_ on the target subnet. The **delivery** of cross-subnet messages is not part of the Topos protocol, but instead is left to the administrators of subnets. Here again, Topos has created an example of a delivery mechanism, called the **executor service**, which is compatible with ToposCore and ERC20Messaging. - - To develop intuition about how this works, consider this non-exhaustive description of the process and the concerns that are addressed by the protocol. It demonstrates a cross-subnet ERC-20 transfer implemented via ERC20Messaging and the executor service. - - 1. On the source subnet: * The user **allows** the ERC20Messaging contract instance to be a spender (and therefore burner) of a number of its tokens. * The user sends a transaction that is a call to the **`sendToken`** function of ERC20Messaging, **specifying** a target subnet ID where the same amount of tokens are to be minted, along with the amount and a recipient. @@ -45,7 +41,7 @@ To develop intuition about how this works, consider this non-exhaustive descript 4. On the target subnet: * The sequencer **hears about** the relevant certificate. * The sequencer **confirms** that the certificate mentions its subnet ID as a target and **records** the certificate in ToposCore (if it is found acceptable). - * The executor service **finds** that the certificate is already in ToposCore. Or the executor service **detects** the `CertStored` event from ToposCore with the receipts root matching that of the original transaction's state transition. + * The executor service queries ToposCore for the certificate (passing the receipts root matching that of the original transaction's state transition). * The executor service sends a transaction that is a call to the **`execute`** function of the ERC20Messaging contract (inherited from ToposMessaging), including the previously collected receipt and its Merkle proof of inclusion. * The ERC20Messaging contract **verifies** the inclusion proof with the stored certificate. * Upon successful verification, the ERC20Messaging contract **is confident** that the original transaction and its receipt **are valid**. diff --git a/content/module-3/1-create-your-messaging-protocol.md b/content/module-3/1-create-your-messaging-protocol.md new file mode 100644 index 0000000..51be99a --- /dev/null +++ b/content/module-3/1-create-your-messaging-protocol.md @@ -0,0 +1,251 @@ +--- +title: Create your messaging protocol +description: Create your custom messaging protocol +--- + +# Create your messaging protocol + +Developing a custom messaging protocol is in essence about writing a smart contract, in the fashion of **ERC20Messaging**, that 1. inherits **ToposMessaging** for the core features needed for cross-subnet messaging (e.g., emitting a special event to announce a cross-subnet message, or verifying a proof of receipt inclusion) on a sending and a receiving subnet, and 2. adds the custom business logic that governs the cross-subnet message execution (e.g., burning and minting an internal ERC20 token in **ERC20Messaging**). + +We can see that a messaging protocol developer merely needs to focus on the second point, i.e., writing the protocol custom business logic, and does not have to care about the deeper technical details of validating a cross-subnet message. That's the power offered by the **ToposMessaging** smart contract. + +Before we dive into the technical details, let's go over the steps that, from a high-level perspective, compose a messaging protocol flow. + +## Messaging protocol flow + +From start to end, a messaging protocol walks the following path: + +1- Some business logic is executed on the sending subnet + +2- A specific event is emitted on the sending subnet to announce the cross-subnet message + +3- A specific transaction is sent to the receiving subnet to validate and execute the cross-subnet message + +4- Some business logic is executed on the receiving subnet + +As mentioned before, all a messaging protocol developer needs to care about, are points 1. and 4., the rest being inherited for free by **ToposMessaging**. + + + +**ERC20Messaging**, being a messaging protocol to send ERC20 tokens across subnets, implements the points 1. and 4. as follows: + +1- Burn the amount that the caller requested to send to another subnet + +4- Verify that the address of the **ERC20Messaging** contract on the sending subnet corresponds to the current one (i.e., on the receiving subnet), verify that the requested receiving subnet is the correct one, retrieve the requested ERC20 token, and finally mint the right amount to the right recipient + + + +Now, we will detail the ToposMessaging interface and its relevant methods for you to use it. + +## ToposMessaging + +Like messaging protocol contracts inheriting it, **ToposMessaging** exposes features that relate either to the sending subnet or the receiving subnet in a cross-subnet messaging flow. + +### Sending subnet (1) + +- `_emitMessageSentEvent(SubnetId targetSubnetId)`: This function expects a target subnet id which is the id of the receiving subnet, and takes care of emitting the specific event mentioned in Messaging protocol flow, point 2. This event is **ToposCore**'s [CrossSubnetMessageSent](https://github.com/topos-protocol/topos-smart-contracts/blob/main/contracts/interfaces/IToposCore.sol#L28) event, which is not defined in **ToposMessaging**, hence the `_emitMessageSentEvent` helper function. + +### Receiving subnet (1) + +- `execute(uint256[] calldata logIndexes, bytes calldata proofBlob, bytes32 receiptRoot)`: This is the function to be called as part of the transaction mentioned in Messaging protocol flow, point 3. + +Let's dive deeper into its signature and implementation. + +As you can see, calling the `execute` function, i.e., executing a cross-subnet message, demands an array of log indexes, a proof blob, and a receipt root. + +The `proof` in question is a merkle proof proving the inclusion of a receipt in a certain receipt trie (hence the receipt trie root third argument). That receipt is the one of the first transaction that we have listed above, the one executing on the sending subnet the arbitrary business logic at the origin of the cross-subnet message (Messaging protocol flow, point 1). This receipt plays a critical role in the cross-subnet message execution for it ensures the transaction on the sending subnet was executed correctly and it contains the logs which speak to the business logic of the messaging protocol. + + + +**ERC20Messaging** emits, on a sending subnet, the following `TokenSent` event to detail which cross-subnet ERC20 transfer was requested: + +```solidity +event TokenSent( + SubnetId indexed targetSubnetId, + string symbol, + address tokenAddress, + address receiver, + uint256 amount +); +``` + +By passing the index of the corresponding log in the transaction receipt, the **ERC20Messaging** contract instance on the receiving subnet can retrieve the event log, parse it, and get all the data it needs to execute the ERC20 transfer, i.e., mint the right amount of the right token to the right recipient. + + + +As you can see, the receipt is not itself part of the `execute` function arguments. That is because the receipt is retrieved from the merkle proof (the proof blob argument) as the code that verifies the proof is executed. That code is part of **ToposMessaging**'s code that a messaging protocol inherits (a code that is part of the `execute` function code). + +Before yielding the receipt, the very first role of the merkle proof is to allow the messaging protocol smart contract deployed on the receiving subnet to verify that the transaction behind the receipt was part of a certified state transition from the sending subnet. This is possible thanks to the parallel work handled by subnet sequencers and the TCE that verify, broadcast and deliver subnet certificates across the ecosystem. It's the **ToposCore** contract's role to store certificates on-chain. + +Thus, **ToposMessaging** can interact with **ToposCore** to query for the certificate corresponding to a given `receipt trie root` (reminder: certificates contain their related receipt trie root) and verify as part of the `execute` function code that the requested cross-subnet message execution relates to an already verified certificate, ensuring that the origination of the cross-subnet message on the sending subnet is valid. + +One extra step in the **ToposMessaging** `execute` function execution is the verification that the cross-subnet message has not already been executed before. For that purpose, **ToposMessaging** stores a map of already executed cross-subnet messages, verifies any cross-subnet message execution request against it, and flag a new request as executed if valid and executed. + +Finally, `execute` concludes with the execution of the arbitrary business logic of a messaging protocol (Messaging protocol flow, point 4) by calling the `_execute` function, which we will detail below. + +## CustomMessaging + +Let's name **CustomMessaging** the custom messaging protocol we're going to develop. + +As detailed above, we want **CustomMessaging** to inherit **ToposMessaging**, i.e., `CustomMessaging is ToposMessaging`. Like so, **CustomMessaging** comes with the `_emitMessageSentEvent` and `execute` functions that we introduced before, and should at least implement two extra functions: + +1- The function that users will call on the sending subnet to originate a cross-subnet message (introduced in Messaging protocol flow, point 1). This function will at least execute `_emitMessageSentEvent` to announce the cross-subnet message. + +2- The `_execute` function, whose signature is defined in **ToposMessaging**'s interface, that will contain the arbitrary business logic to be executed on the receiving subnet (reminder: the `_execute` function is called by the **ToposMessaging**-inherited `execute` function). + +Again, we can separate these two functions by the subnet on which they are meant to be executed. + +### Sending subnet (2) + +The code of the first function is pretty much arbitrary and depends on what you want the messaging protocol to be about. See the **ERC20Messaging** example below. + + + +**ERC20Messaging** exposes the `sendToken` function, as we have seen before, which: + +1- Burns the requested amount of the requested token + +2- Emits the **TokenSent** event, i.e., the arbitrary event that carries the messaging protocol specific data used on the receiving subnet to execute the cross-subnet message + +3- Emits the **CrossSubnetMessageSent** core event to announce the cross-subnet message + +```solidity +function sendToken(SubnetId targetSubnetId, string calldata symbol, address receiver, uint256 amount) external { + if (_toposCoreAddr.code.length == uint256(0)) revert InvalidToposCore(); + Token memory token = getTokenBySymbol(symbol); + _burnTokenFrom(msg.sender, symbol, amount); // 1- Burn the requested amount of the requested token + emit TokenSent(targetSubnetId, symbol, token.addr, receiver, amount); // 2- Emit the TokenSent event + _emitMessageSentEvent(targetSubnetId); // 3- Emit the CrossSubnetMessageSent event +} +``` + + + +Only the last line that emits the **CrossSubnetMessageEvent** is a requirement for all messaging protocols. So if we were to name the first function of **CustomMessaging** `doSomething`, `doSomething` would have the following structure: + +```solidity +function doSomething(SubnetId targetSubnetId, ...otherArgs) external { + ... // 1- Do something + ... // 2- Emit an event that describes the something + _emitMessageSentEvent(targetSubnetId); // 3- Emit the CrossSubnetMessageSent event +} +``` + +### Receiving subnet (2) + +Let's now take a look at the second function (`_execute`) signature: + +```solidity +function _execute( + uint256[] memory logIndexes, + address[] memory logsAddress, + bytes[] memory logsData, + bytes32[][] memory logsTopics, + SubnetId networkSubnetId +) internal virtual {} +``` + +As detailed before, this function's signature is defined in **ToposMessaging**'s interface and the function is itself called by **ToposMessaging**'s `execute` function, so as a messaging protocol developer, you need not care about how all these arguments are retrieved. You only need to care about how you use them. + +You see that, except `networkSubnetId`, all arguments are data projected against a certain log index. + +With `logIndexes`, you can retrieve the index of the logs that semantically will describe the cross-subnet message and how it should be executed on the receiving subnet. + + + +Remember that because you use the same messaging protocol contract on the sending and receiving subnets, and you developed that messaging protocol, you know which log leads to what information. + +For example, looking at the code of **ERC20Messaging**, we can see that the `TokenSent` event is sent after a few other events related to the ERC20 allowance and burn operations. To be certain, one can inspect the receipt of a `sendToken` transaction and find the index of the `TokenSent` index. + + + +Let's say that you know that the `doSomething` function introduced in the previous section, when emitting on the sending subnet the event that describes that "something" (point 2), gets that event at index `4` in the transaction receipt. Then, your frontend application (the application that your users use to interact with your dApp/messaging protocol) should pass this index when calling on the receiving subnet the `execute` function (via the first `logIndexes` argument whose value should be `[4]`), which consecutively passes it to the `_execute` function via the argument of the same name. + +Let's now say that this event is the first one (and maybe the only one) that your messaging protocol needs in order to execute the cross-subnet message. This translates to `logIndexes[0]` being the `4` index you were interested in. + + + +Should you have other logs needed to decode in order to execute the cross-subnet message, you could have passed their indexes as subsequent values of the `logIndexes` array. + + + + +Now that you have retrieved the right log index, you can retrieve the rest of the log's data you are interested in: + +```solidity +# pseudo-code +myLogIndex = logIndexes[0] // myLog == 4 +logsAddress[myLogIndex] // This is the address of the contract which emitted that log +logsData[myLogIndex] // This is the data field of that log +logsTopics[myLogIndex] // This is the topics field of that log +``` + +From the address of the contract which emitted the log, you can verify that the address is the one you expected. From the data field, you can retrieve all the data that semantically describe the cross-subnet message. Eventually, from the topics you can retrieve all the indexed arguments of the log. + + + +**ERC20Messaging**'s `_execute` function is coded as follows: + +```solidity +function _execute( + uint256[] memory logIndexes, + address[] memory logsAddress, + bytes[] memory logsData, + bytes32[][] memory logsTopics, + SubnetId networkSubnetId +) internal override { + uint256 tokenSentEventIndex = logIndexes[0]; + if (logsAddress[tokenSentEventIndex] != address(this)) revert InvalidOriginAddress(); + + bytes32 targetSubnetId = logsTopics[tokenSentEventIndex][1]; + if (SubnetId.unwrap(networkSubnetId) != targetSubnetId) revert InvalidSubnetId(); + + (string memory symbol, , address receiver, uint256 amount) = abi.decode( + logsData[tokenSentEventIndex], + (string, address, address, uint256) + ); + _mintToken(symbol, receiver, amount); +} +``` + +
+As we can see, it starts by retrieving the expected index of the `TokenSent` event. + +```solidity +uint256 tokenSentEventIndex = logIndexes[0]; +``` +Again, we know that the event is at index `0` because it is the only event we need to decode the ERC20 transfer that we need to execute on the receiving subnet, and because **ERC20Messaging**'s smart contract and frontend application were developed to work with that event only. + +
+Then, from the right index, the address of the contract that emitted the `TokenSent` event is retrieved and compared to the current address (the address of the **ERC20Messaging** smart contract instance being used). This is because **ERC20Messaging** expects all instances of its contract to be deployed at the same address on all subnets. + +```solidity +if (logsAddress[tokenSentEventIndex] != address(this)) revert InvalidOriginAddress(); +``` + +
+Next, from the right index and the corresponding array of topics, the target subnet id is retrieved. Index `1` is used because the first topic of an EVM event is the event signature, and the next are the indexed arguments of the event and `targetSubnetId` is the first and only indexed argument of the `TokenSent` event. + +```solidity +bytes32 targetSubnetId = logsTopics[tokenSentEventIndex][1]; +if (SubnetId.unwrap(networkSubnetId) != targetSubnetId) revert InvalidSubnetId(); +``` + +
+Finally, the data field is decoded and the data to be used to execute the cross-subnet ERC20 transfer is retrieved (token symbol, receiver address, and amount), before the transfer gets executed by minting the token. + +```solidity +(string memory symbol, , address receiver, uint256 amount) = abi.decode( + logsData[tokenSentEventIndex], + (string, address, address, uint256) +); +_mintToken(symbol, receiver, amount); +``` + +
+ +## Conclusion + +You now should have a deep understanding of what messaging protocols are in the Topos ecosystem and how they work. You got to see what **ToposMessaging** is and what functionalities it offers to messaging protocol developers ready to jump into the world of cross-subnet messaging. **ERC20Messaging**, the messaging protocol that we developed and deployed on our different networks, was mentioned many times to illustrate with a concrete example the steps that compose the development and use of a messaging protocol. + +The next steps after writing your messaging protocol will be to create a dapp-frontend, if needed, to let your users start using your dApp, and to start, if needed, interacting with the Executor Service to delegate the `execute` function call to it (and prevent your users from paying transaction fees twice). diff --git a/content/module-3/index.md b/content/module-3/index.md new file mode 100644 index 0000000..72c9f0a --- /dev/null +++ b/content/module-3/index.md @@ -0,0 +1,29 @@ +--- +title: 'Chapter overview: Next steps' +description: 'Chapter overview: Next steps' +--- + +# Next steps + +The third chapter of the Topos Developer Portal introduces you to more advanced topics, notably the development of custom messaging protocols, a key feature of Topos that allows you to fully embrace its cross-subnet messaging capabilities. + + + ![](../images/topos_header.png) + + ## Start developing + + If you're ready to go, click below to begin developing your own messaging protocol. + + + + Chapter 3 covers the following specific subjects: + + + + + + + This unit goes over concepts tied to messaging protocols that were already introduced before, but this time we're going to make a hands-on exploration of how to develop a custom messaging protocol in the fashion of ERC20Messaging. + + + diff --git a/gatsby-config.ts b/gatsby-config.ts index 8f88015..34940b7 100644 --- a/gatsby-config.ts +++ b/gatsby-config.ts @@ -8,11 +8,11 @@ const env = process.env.NODE_ENV || 'development'; const config: GatsbyConfig = { flags: { - FAST_DEV: true + FAST_DEV: false, }, siteMetadata: { title: 'Topos Developer Portal', - siteUrl: 'https://docs.topos.technology' + siteUrl: 'https://docs.topos.technology', }, // More easily incorporate content into your pages through automatic TypeScript type generation and better GraphQL IntelliSense. // If you use VSCode you can also use the GraphQL plugin @@ -31,8 +31,8 @@ const config: GatsbyConfig = { start_url: '/', background_color: '#fff', display: 'minimal-ui', - icon: 'src/images/favicon.png' // This path is relative to the root of the site. - } + icon: 'src/images/favicon.png', // This path is relative to the root of the site. + }, }, 'gatsby-plugin-image', { @@ -45,8 +45,8 @@ const config: GatsbyConfig = { resolve: `gatsby-remark-katex`, options: { // Add any KaTeX options from https://github.com/KaTeX/KaTeX/blob/master/docs/options.md here - strict: `ignore` - } + strict: `ignore`, + }, }, { resolve: 'gatsby-remark-images', @@ -56,15 +56,15 @@ const config: GatsbyConfig = { // the content container as this plugin uses this as the // base for generating different widths of each image. maxWidth: 1216, - quality: env === 'development' ? 10 : 80 - } + quality: env === 'development' ? 10 : 80, + }, }, { resolve: 'gatsby-remark-code-buttons', options: { svgIcon: - '' - } + '', + }, }, { resolve: 'gatsby-remark-prismjs', @@ -79,72 +79,72 @@ const config: GatsbyConfig = { language: 'superscript', extend: 'javascript', definition: { - superscript_types: /(SuperType)/ + superscript_types: /(SuperType)/, }, insertBefore: { function: { - superscript_keywords: /(superif|superelse)/ - } - } - } + superscript_keywords: /(superif|superelse)/, + }, + }, + }, ], prompt: { user: 'root', host: 'localhost', - global: false + global: false, }, - escapeEntities: {} - } - } - ] - } + escapeEntities: {}, + }, + }, + ], + }, }, { resolve: `gatsby-plugin-google-gtag`, options: { // You can add multiple tracking ids and a pageview event will be fired for all of them. trackingIds: [ - 'GTM-MZ6B9TP5' // Google Analytics / GA + 'GTM-MZ6B9TP5', // Google Analytics / GA ], gtagConfig: { anonymize_ip: true, - cookie_expires: 0 + cookie_expires: 0, }, pluginConfig: { // Puts tracking script in the head instead of the body head: false, // Delays processing pageview events on route update (in milliseconds) - delayOnRouteUpdate: platformConfig.pageTransitionDuration * 1000 * 2 - } - } + delayOnRouteUpdate: platformConfig.pageTransitionDuration * 1000 * 2, + }, + }, }, { resolve: 'gatsby-source-filesystem', options: { name: 'images', - path: './src/images/' + path: './src/images/', }, - __key: 'images' + __key: 'images', }, { resolve: 'gatsby-source-filesystem', options: { name: 'pages', - path: './src/pages/' + path: './src/pages/', }, - __key: 'pages' + __key: 'pages', }, { resolve: 'gatsby-source-filesystem', options: { name: 'data', - path: './src/data/' + path: './src/data/', }, - __key: 'data' + __key: 'data', }, ...includeFolders(), - 'gatsby-plugin-catch-links' - ] + 'gatsby-plugin-catch-links', + ], }; export default config; diff --git a/src/gatsby/createPages.ts b/src/gatsby/createPages.ts index c10e96f..48b35cb 100644 --- a/src/gatsby/createPages.ts +++ b/src/gatsby/createPages.ts @@ -29,7 +29,7 @@ const createPages = async ({ actions, graphql }: CreatePagesArgs) => { result.data.allMdx.edges.forEach(({ node }: any) => { const path = mdxPath(node.internal.contentFilePath); const isProtectedRoute = isProtectedMdxPath( - node.internal.contentFilePath + node.internal.contentFilePath, ); if (!isProtectedRoute) { createPage({ diff --git a/src/styles/components/highlights.scss b/src/styles/components/highlights.scss index 4df6edc..3d8711a 100644 --- a/src/styles/components/highlights.scss +++ b/src/styles/components/highlights.scss @@ -1,6 +1,7 @@ .highlightbox { * { @apply text-inherit; + overflow: auto; } &.info {