diff --git a/app/(home)/course/page.tsx b/app/(home)/course/page.tsx index 08b8b3b2..c10fe2ca 100644 --- a/app/(home)/course/page.tsx +++ b/app/(home)/course/page.tsx @@ -4,7 +4,6 @@ import Link, { type LinkProps } from 'next/link'; import Image from 'next/image'; import { buttonVariants } from '@/components/ui/button'; import { cn } from '@/utils/cn'; -import Spot from '@/public/spot.png'; const cardIconVariants = cva( 'mb-2 size-9 rounded-lg border p-1 shadow-sm shadow-primary/50', @@ -24,13 +23,6 @@ export default function DocsPage(): React.ReactElement { />
- spot

Getting Started diff --git a/app/course/[...slug]/page.tsx b/app/course/[...slug]/page.tsx index e3374bdd..c3b3171a 100644 --- a/app/course/[...slug]/page.tsx +++ b/app/course/[...slug]/page.tsx @@ -134,7 +134,7 @@ export function generateMetadata({ params }: { params: Param }): Metadata { if (!page) notFound(); const description = - page.data.description ?? 'The library for building documentation sites'; + page.data.description ?? 'Learn how to build on Avalanche blockchain with Academy'; const imageParams = new URLSearchParams(); imageParams.set('title', page.data.title); diff --git a/app/global.css b/app/global.css index 8545fb26..999207e3 100644 --- a/app/global.css +++ b/app/global.css @@ -43,3 +43,30 @@ padding: 0 16px; } +svg.lucide.lucide-box, svg.lucide.lucide-layers { + color: #E84142; +} + +svg.lucide.lucide-bookmark { + color: #b821d5; +} + +svg.lucide.lucide-terminal { + color: #ff5e13; +} + +svg.lucide.lucide-users { + color: #8b54ff; +} + +svg.lucide.lucide-binary { + color: #ce3427; +} + +svg.lucide.lucide-rocket { + color: #ff2e2ec4; +} + +svg.lucide.lucide-send { + color: #0070ff; +} diff --git a/components/gallery.tsx b/components/gallery.tsx new file mode 100644 index 00000000..ecb9c6e0 --- /dev/null +++ b/components/gallery.tsx @@ -0,0 +1,18 @@ +export default function Gallery({ url1, url2 } : { url1 : string, url2 : string }){ + return ( +
+
+ +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/content/blog/2024-5-15.mdx b/content/blog/2024-5-15.mdx deleted file mode 100644 index 5fcf1ef8..00000000 --- a/content/blog/2024-5-15.mdx +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: How Fumadocs works -description: The framework for building documentation -author: Fuma Nama ---- - -1 year ago, I was having fun with Next.js App Rouer. -While experimenting it on my toy [No Deploy](https://nodeploy-neon.vercel.app), I planned to build a documentation. -However, Nextra does not support App Router. - -To handle this, I implemented a small documentation site with solely Contentlayer and the new features from App Router. -It was working great, looks blazing-fast and minimal. -I cloned the logic from No Deploy and built this documentation framework. -With a few months of development, it soon became powerful and stable. - -It was originally named `next-docs`, I renamed it to Fumadocs as it conflicts with Next.js Docs. - -Thanks to the support from **Next.js** community, I've received a lot of suggestions along the way. -[Fumadocs](https://fumadocs.vercel.app) is now a framework used by my libraries and some other amazing projects. - -## My Opinion - -In Web development, most _"robust"_ frameworks/libraries are incredibly heavy and fabulous, but it indeed made our developer experience fancy. - -On the top of Javascript, people bulit bundlers, transpilers, and even Typescript. -It feels very surprisingly that Javascript, a high-level scripting language, is more similar to assembly code in modern Web development. -We rarely use them without things like Webpack. This also applies to CSS, at least as my experience, I seldom use CSS without PostCSS. - -While they might be necessary for compatibility and DX, the landing of React Server Component and Next.js App Router made the experience even more mindblowing. -It feels like magic. The cunning magical frameworks, and web development miracles. -This kind of design is insane, but it also makes us mindlessly forget the boundaries. - -Beginners use Metadata API, while they have no idea how meta tag works. -They put server-side logic in a server component, while they have no idea how expensive the calculation is. -Even when we looked at the code, we can't predict the result without running it in production mode. -I saw too many of these misconceptions. - -This happens on many frameworks, they are overly magical. -**I wanted to make it less-magic, and straightforward at least for most Next.js developers.** - -## Fumadocs MDX - -As the recommended content source, It is actually a webpack hack. -Since Next.js could only optimize static imports, it first transform your `.map.ts` to a file that roughy yields: - -```ts -export default [import("./my/file.mdx"), ...]; -``` - -And then transform MDX files with a custom loader. It makes all magic possible, but it doesn't have the ablility of lazy-loading MDX files. -Comparing to Nextra, it might be a suboptimal approach. - -Nextra does it even easier, it directly transforms MDX files into pages. Because the Pages Router adapts Javascript files as a single page, it is possible. -In App Router, this is not possible anymore. Therefore, I didn't take this approach. - -## Fumadocs Core - -The core of Fumadocs is a bunch of utilties and MDX plugins. - -- **Source API** construct page trees from content source, integrated with other content providers. -- **Headless components** accelerate Fumadocs UI and other custom UI implementations. -- **MDX plugins** bring a perfect developer experience to all integrations. -- **Search utilities** make it way easier to implement document search. - -In addition, it also established the definitions of Page Tree and Page Conventions. -Over all, it is not yet a framework without Fumadocs UI. - -In my opinion, the most valuable part in the codebase are MDX plugins. -I learnt a lot more about ASTs and the eco-system of remark/rehype while working on them. -Absolutely an amazing experience. - -## Fumadocs UI - -The UI implementation of Fumadocs using Tailwind CSS and Radix UI. -Its design system was inspired by Shadcn UI, using CSS variables for color utilities. - -Although the structure of Fumadocs UI is even simpler than core, I've used some subtle hacks to solve the problem of `"use client"` directive. -The bundler I am using, [TSX](https://github.com/privatenumber/tsx), can't handle nested structures like client components imported from a server comopnent. -Therefore, I made a little hack to build server components and client components as an individual entry, then inject import statements after the process. - -Also it took me some time to come up with the [preset approach](https://fumadocs.vercel.app/docs/ui/theme#docsui-plugin) for integrating Fumadocs UI with Tailwind CSS projects. - -## Docs Generators - -We have a few built-in integrations, like `fumadocs-openapi` which takes an OpenAPI schema and output MDX files. - -For the OpenAPI one, it simply parse the schema and convert it to MDX file through string templates. - -The Typescript integration does a bit more, it obtain type information with [Typscript Compiler API](https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API). Based on the information, it yields MDX files. -You can use it inside a server component, which is how `` works. - -## CL/CI - -As a project with very few contributors, I built the CL/CI process as convenient as possible for a better efficiency. -The entire release process is handled by [Changesets](https://github.com/changesets/changesets), and I wrote the scripts to update [the template repository](https://github.com/fuma-nama/fumadocs-ui-template) automatically. -It worked great so far. - -## Thanks - -[The Github repository of Fumadocs](https://github.com/fuma-nama/fumadocs) has reached 300 stars in 2024 March, it is a new achievement for me. -Welcome to give it a star to support my work! - -> Original -> https://fuma-nama.vercel.app/blog/fumadocs diff --git a/content/blog/2024-5-16.mdx b/content/blog/2024-5-16.mdx deleted file mode 100644 index 1ecfb12e..00000000 --- a/content/blog/2024-5-16.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: 500 Stars -description: The first 500 stars of Fumadocs -author: Fuma Nama ---- - -It was surprising when I first saw [the video from Web Dev Cody](https://youtu.be/7HUmDAgXI2E?si=CR9fC-f3nysPJJCE), it feels like I've finally built something worth mentioning, instead of another neglected side project that nobody cares. - -So far I've worked on this project about 1 year, and it is a precious experience to me. The best lecture that you can't find in an university. - -## Open Source - -I received many feedback, issues and questions. Some are brutal but straightforward, like _"please fix it, help me please"_ _"Support XXX please"_. Some are kind and helpful, willing to provide a PR. -I felt both the good and down sides of open source. - -Sometimes, people are being eager and impulsive because of stress and may spread their frustration on the library maintainers. -Even myself, can be affected by stress or a bad mood. - -People may hope for instant answers, and maintainers will face questions like _"Why are you running away from my questions?"_. -I understand a library performing bad can cause your precious time to be wasted, but even if we putted all the efforts in maintaining the library, it can't be flawless. - -Since then, I feel I'm way more respectful to open-source project maintainers. -At least, I'll check my attribute before firing an issue on somebody else's Github repository, and try to be as respectful as I can ~~with my poor language skills~~. - -### Issues - -My favourite dev was [Anthony Shew](https://shew.dev), I must mention him because [the issue he opened](https://github.com/fuma-nama/fumadocs/issues/264), **is the best I've ever seen in my open-source career**. -He actually cared about my vision and opinion about the API design, and gave a really concise and constructive feature request. - -Obviously, I was not a perfect, neither an experienced library dev. -Fumadocs wasn't capable of many things, such a well-explained feature request and idea is powerful. His passion is inspiring. - -Before setting up the YAML issue template, there were very few issues that actually follow the issue template, most of them don't even provide a reproducible repository, or an explanation. - -**Open a proper issue, follow the instructions, and give maintainers some positive feedback.** This is the biggest motivation you can give to the maintainers without money. - -## Docs - -It's fun to work on the docs of fumadocs. When first building it, I had very less experience on authoring documentation and tutorial content. -Reading through the feedback from developers, the most common problem is that they can't find the docs of something. - -I realized the entire docs is unfriendly for beginners to start with, my friends are senior Next.js devs, and of course they are fine with that. -However, not every dev could understand the docs. - -So recently, I started to re-write some of the sections, making it more easier and appealing for beginners. Welcome to share your feedback on Github Discussions! - -## What is Next? - -Web Development is an ever-evolving industry, but I believe the spirit of open source won't disappear. -I don't know what will I be building in the future, and that doesn't matter. -Let's build a better web! diff --git a/content/blog/img.png b/content/blog/img.png deleted file mode 100644 index b4ccc9f5..00000000 Binary files a/content/blog/img.png and /dev/null differ diff --git a/content/blog/tbd.mdx b/content/blog/tbd.mdx new file mode 100644 index 00000000..096f5458 --- /dev/null +++ b/content/blog/tbd.mdx @@ -0,0 +1,7 @@ +--- +title: Yet to Decide +description: TBD +author: TBD +--- + +TBD \ No newline at end of file diff --git a/content/blog/why-docs.mdx b/content/blog/why-docs.mdx deleted file mode 100644 index 70b5b2ce..00000000 --- a/content/blog/why-docs.mdx +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: Why do you need a docs? -description: You've read so many docs, but are they necessary? -date: 2024-05-26 -author: Fuma Nama ---- - -People often ask me, do we really need a framework to build docs sites? Well, **You don't**. - -Documentation sites are so important in software development, -an internal docs for developers in your company to understand the architecture, -a docs for frameworks, -a docs for web standards... - -Building docs is simple, but difficult. - -There are so many paradigms to build a docs, but writing a beginner-friendly docs could be difficult. -As a result, people tends to use powerful docs frameworks, making the docs site interactive and straightforward. - -## Over-Engineered - -For the docs of a small library/API service, you probably don't need to setup a Next.js site and spend time writing your site. -Neither Nextra nor Fumadocs could be better than GitHub wiki and Swagger docs in this case. - -They offer a good, descent UI, basic functionalities, and a proper workflow of authoring docs. -The DX is good enough, I can't think of a reason to switch to a full-powered docs framework just to make your docs looks fancy. - -I'll just recommend writing your docs in markdown, make it accessible on your GitHub repository. - -## Why Framework? - -Now you may wonder, why major services and frameworks have their own docs sites built with docs frameworks? - -Of course, _Usually_ using things like GitHub Wiki is adequate, but it is not always true. -Let's take Component Library for example, you cannot showcase your components with Markdown. -You will constantly find an ordinary Markdown document lacks of capability and flexibility. - -Documentation frameworks aim to solve this problem, with the ability to integrate with major libraries like **React.js** and **Vue.js**. -Good examples are Vitepress, Mintlify and Nextra - They made writing a docs more convenient and effective, while offering a better, dedicated experience to readers. - -For anything more than a simple library or API service, **it is worth a try.** - -### Reinvent the Wheels - -I would never recommend building a "custom docs site" on your own, without a proper docs framework. -Despite the **Don't re-invent the wheels** principle, your hand-made docs site actually takes way more efforts to make it descent. - -1. Document Search -2. A user-friendly navigation experience -3. Reading experience -4. UI/UX Design - -Implementing them properly already sounds nerve-racking, right? - -The docs itself, is definitely not your first priority. Never should you spend your precious time re-inventing the wheels - **it isn't worth**. - -From my perspective, I'd rather use GitHub Wiki than re-inventing the wheels. -Why don't pick a descent solution? It saves your indispensable time, and help reducing the shitty docs sites on the internet. - -## What we focus at Fumadocs? - -I personally value reading experience more than a fancy eye-catching UI. -You may notice, we do not have animations everywhere, and we avoided many fancy designs. - -Fanciness of UI should stay only in landing page, a docs site should focus on **content.** -Navigation elements are helpers to browse your site, never should they take up too much space on the screen. - -One thing I hated the most is the design of _two sidebars_, it is confusing and meaningless. -You can just organize all items to a single, clean sidebar, but people instead added two hamburger buttons to the navbar. - -
- -![Next.js Docs](./img.png) - -
- -My favourite docs site is still [Linear docs](https://linear.app/docs), looks good and simple. - -## Conclusion - -1. You don't need a full-powered docs framework for a small library -2. Don't make a docs site on your own, use a proper docs framework -3. Fumadocs focuses on reading experience -4. You should focus on content, too diff --git a/content/common/avalanche-starter-kit/set-up.mdx b/content/common/avalanche-starter-kit/set-up.mdx index 997ba35f..649ee3d8 100644 --- a/content/common/avalanche-starter-kit/set-up.mdx +++ b/content/common/avalanche-starter-kit/set-up.mdx @@ -31,7 +31,7 @@ The Codespace will open in a new tab. Wait until it's fully built Open the terminal with `` Ctrl + ` `` or by opening it through the menu: -![](/course-images/avalanche-starter-kit/new-terminal.png) +![](/common-images/avalanche-starter-kit/new-terminal.png) Now enter avalanche -h to verify everything is working: diff --git a/content/common/codespaces/close-and-reopen-codespace.mdx b/content/common/codespaces/close-and-reopen-codespace.mdx index bd2bf9c0..e0a5a676 100644 --- a/content/common/codespaces/close-and-reopen-codespace.mdx +++ b/content/common/codespaces/close-and-reopen-codespace.mdx @@ -2,9 +2,9 @@ The codespace time out after 30 minutes of inactivity by default. So if you are not too worried about the 30 hour/month limit, just close the window or the tab. Alternatively, you can open the command prompt (Cmd + Shift + P) and issue the Codespace: Stop Current Codespace. -![](/course-images/codespaces/stop-codespace.png) +![](/common-images/codespaces/stop-codespace.png) # Reopen Codespaces You can see your Codespaces on [github.com/codespaces/](https://github.com/codespaces/). There you can stop them, reopen them and delete them. -![](/course-images/codespaces/list-codespaces.png) \ No newline at end of file +![](/common-images/codespaces/list-codespaces.png) \ No newline at end of file diff --git a/content/common/evm-precompiles/precompiles.mdx b/content/common/evm-precompiles/precompiles.mdx index 609bd569..f7df77c7 100644 --- a/content/common/evm-precompiles/precompiles.mdx +++ b/content/common/evm-precompiles/precompiles.mdx @@ -2,7 +2,7 @@ Precompiled contracts are a way to execute code written in the low-level coding If you are familiar with Python programming, you might be familiar with a similar concept. Many Python functions and libraries are written in the programming language C, since it is much more efficient than Python. Python developers can import these precompile modules and call these functions just as if they were written in Python. The only difference is that the modules are faster and more efficient. -![Precompiles](/course-images/evm-precompiles/precompiles.png) +![Precompiles](/common-images/evm-precompiles/precompiles.png) Precompiles can be called from a Solidity smart contract in the same way, as if they were another contract written in Solidity. The EVM keeps a list of addresses reserved and mapped to the precompiles. When a smart contract calls a function of a contract with an address on that list, the EVM executes the precompile written in Go instead of the smart contract. For instance, if we mapped the address 0x030...01 to the SHA256 precompile that hashes its input using the SHA256 Hash Function, we can call the Precompile like this: diff --git a/content/common/intro/instructors.mdx b/content/common/intro/instructors.mdx index 639c8efb..6d3b252b 100644 --- a/content/common/intro/instructors.mdx +++ b/content/common/intro/instructors.mdx @@ -1,5 +1,13 @@ -# Martin +## Martin Eckardt -# Andrea +Martin works as a Senior Developer Relations Engineer at Ava Labs. -# Ash \ No newline at end of file +## Andrea + +Andrea Vargas works as a DevRel Engineer at Ava Labs. + +## Ash + +Ash currently works as a DevRel Engineer at Ava Labs, the team behind Avalanche blockchain. + +[Twitter](https://twitter.com/ashngmi) | [GitHub](https://github.com/ashucoder9) | [Website](https://ashngmi.xyz) \ No newline at end of file diff --git a/content/common/primary-network/p-chain.mdx b/content/common/primary-network/p-chain.mdx index aac4d81f..c5529729 100644 --- a/content/common/primary-network/p-chain.mdx +++ b/content/common/primary-network/p-chain.mdx @@ -1,6 +1,6 @@ In the Avalanche Network, the P-Chain of the Primary Network is responsible for all validator and Subnet-level operations. The P-Chain supports the creation of new blockchains and Subnets, the addition of validators to Subnets, staking operations, and other platform-level operations. -![P-Chain](/course-images/primary-network/p-chain.png) +![P-Chain](/common-images/primary-network/p-chain.png) Each Avalanche validator can register a BLS public key alongside its NodeID on the Avalanche P-Chain. Validators of Subnets can stake different amounts of the Subnet's staking token. Through the P-Chain, we can determine a weighted set of BLS Public Keys that correspond to the validators of each Subnet on the Avalanche Network. diff --git a/content/course/avalanche-fundamentals/01-intro/02-meet-your-instructors.mdx b/content/course/avalanche-fundamentals/01-intro/02-meet-your-instructors.mdx index 9af9a4d0..f8962eed 100644 --- a/content/course/avalanche-fundamentals/01-intro/02-meet-your-instructors.mdx +++ b/content/course/avalanche-fundamentals/01-intro/02-meet-your-instructors.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘¨β€πŸ« Meet your Instructors +title: Meet your Instructors description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Users --- import Instructors from "@/content/common/intro/instructors.mdx" diff --git a/content/course/avalanche-fundamentals/01-intro/03-prerequisites.mdx b/content/course/avalanche-fundamentals/01-intro/03-prerequisites.mdx index 3de49b63..f7ae4438 100644 --- a/content/course/avalanche-fundamentals/01-intro/03-prerequisites.mdx +++ b/content/course/avalanche-fundamentals/01-intro/03-prerequisites.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“š Prerequisites +title: Prerequisites description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Binary --- This course is for people with some blockchain knowledge. Familiarity with the basic design of modern distributed software systems and common blockchain systems such as Bitcoin and Ethereum is also recommended. You do not need to know how to write code to successfully complete this course. diff --git a/content/course/avalanche-fundamentals/01-intro/04-learning-outcomes.mdx b/content/course/avalanche-fundamentals/01-intro/04-learning-outcomes.mdx index 086502ba..597c0026 100644 --- a/content/course/avalanche-fundamentals/01-intro/04-learning-outcomes.mdx +++ b/content/course/avalanche-fundamentals/01-intro/04-learning-outcomes.mdx @@ -1,8 +1,9 @@ --- -title: 🎯 Learning Outcomes +title: Learning Outcomes description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Rocket --- By the end of this course, you will: diff --git a/content/course/avalanche-fundamentals/01-intro/05-join-course-chat.mdx b/content/course/avalanche-fundamentals/01-intro/05-join-course-chat.mdx index 1754d428..14746c00 100644 --- a/content/course/avalanche-fundamentals/01-intro/05-join-course-chat.mdx +++ b/content/course/avalanche-fundamentals/01-intro/05-join-course-chat.mdx @@ -1,8 +1,9 @@ --- -title: 🀝 Join Course Chat +title: Join Course Chat description: Join the Avalanche Academy Telegram group to chat with other students and instructors. updated: 2024-05-31 authors: [ashucoder9] +icon: Send --- import CourseChat from "@/content/common/intro/course-chat.mdx"; diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/00-consensus-mechanisms.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/00-consensus-mechanisms.mdx index dfe89979..37ba628f 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/00-consensus-mechanisms.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/00-consensus-mechanisms.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Consensus Mechanisms +title: Consensus Mechanisms description: Learn how blockchains arrive at consensus using different mechanisms. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- Consensus plays a crucial role in blockchain networks by resolving conflicts and ensuring that all validators agree on the current state of the distributed ledger. The main objective of a consensus mechanism is to create a single version of truth that is universally accepted by network participants. diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-snowman-consensus.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-snowman-consensus.mdx index 16af239c..40b24d7f 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-snowman-consensus.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/01-snowman-consensus.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Snowman Consensus +title: Snowman Consensus description: Learn about the Avalanche Snowman consensus protocol. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- Protocols in the Avalanche family operate through repeated sub-sampled voting. When a validator is determining whether a block should be accepted, it asks a small, random subset of validators on their preferences. Based on the responses the validator gets, it might change its own preference. diff --git a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-tps-vs-ttf.mdx b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-tps-vs-ttf.mdx index fd13882c..9c13bc5d 100644 --- a/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-tps-vs-ttf.mdx +++ b/content/course/avalanche-fundamentals/02-avalanche-consensus-intro/02-tps-vs-ttf.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Throughput vs. Time to Finality +title: Throughput vs. Time to Finality description: Learn how metrics like throughput and time to finality are different. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- To measure blockchain performance we can use two metrics: diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/00-architecture.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/00-architecture.mdx index fe3edc0d..7817f412 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/00-architecture.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/00-architecture.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Multi-Chain Architecture +title: Multi-Chain Architecture description: Learn about the Subnet architecture. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- Multi-chain systems are a significant innovation, which provide greater scalability, customizability, and independence. At the core of multi-chain systems is the ability to run multiple blockchains simultaneously. Each blockchain is optimized for specialized use cases, thereby boosting the network's overall performance. diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/01-benefits.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/01-benefits.mdx index f21fe902..124f6cc6 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/01-benefits.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/01-benefits.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Features & Benefits of Subnets +title: Features & Benefits of Subnets description: Learn about various benefits of using Avalanche Subnets, updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- Subnets are autonomous. Their creators can define their execution logic, set their own fees, manage their state, handle their networking, and ensure their security. They operate independently from other Subnets and the Primary Network, effectively enabling the greater Avalanche network to scale while delivering the benefits of lower latency, higher transactions per second (TPS), and lower transaction costs. diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-setup-core.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-setup-core.mdx index ecdd238c..6c13ed90 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-setup-core.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/02-setup-core.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘©β€πŸ’» Setup Core Wallet +title: Setup Core Wallet description: Learn about how to setup your own Core Wallet. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- To grasp the concept of the Subnets better, interact with some chains. To do that, we will use Core. Core is an all-in-one command center built for multi-chain systems. It supports Avalanche, Bitcoin, Ethereum, and all EVM-compatible blockchains. Core has a browser extension, a web wallet, and a mobile app. It is optimized for multiple chains and makes navigating between them easy. diff --git a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-use-dexalot.mdx b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-use-dexalot.mdx index 4178938c..74c6343e 100644 --- a/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-use-dexalot.mdx +++ b/content/course/avalanche-fundamentals/03-multi-chain-architecture-intro/03-use-dexalot.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘©β€πŸ’» Use Dexalot Subnet +title: Use Dexalot Subnet description: Get first hand experience with an Avalanche Subnet. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- In this activity, we'll interact with our first Subnet. The Dexalot Subnet aims to replicate the user experience of a centralized exchange (CEX) with greater decentralization and transparency. By using a Subnet, Dexalot can offer cheaper transaction fees than other traditional decentralized exchanges while keeping the transparency that centralized exchanges lack. diff --git a/content/course/avalanche-fundamentals/04-creating-a-blockchain/00-using-avacloud.mdx b/content/course/avalanche-fundamentals/04-creating-a-blockchain/00-using-avacloud.mdx index 0a8cab15..d4d69aa9 100644 --- a/content/course/avalanche-fundamentals/04-creating-a-blockchain/00-using-avacloud.mdx +++ b/content/course/avalanche-fundamentals/04-creating-a-blockchain/00-using-avacloud.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘©β€πŸ’» Sign Up for AvaCloud +title: Sign Up for AvaCloud description: Signing up for the Avacloud service. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- Now that we've gone over what a Subnet is and what the benefits of creating a Subnet on Avalanche are, you are probably eager to test out the functionality of Subnets by creating one yourself! diff --git a/content/course/avalanche-fundamentals/04-creating-a-blockchain/01-create-on-avacloud.mdx b/content/course/avalanche-fundamentals/04-creating-a-blockchain/01-create-on-avacloud.mdx index 44263256..06e66c2c 100644 --- a/content/course/avalanche-fundamentals/04-creating-a-blockchain/01-create-on-avacloud.mdx +++ b/content/course/avalanche-fundamentals/04-creating-a-blockchain/01-create-on-avacloud.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘©β€πŸ’» Create & Deploy a Blockchain +title: Create & Deploy a Blockchain description: Learn how you can configure and launch your subnet using AvaCloud. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- In this section, we will get our first taste of deploying a Subnet. To start, please make sure your have a wallet set up - the reason for this is that we will need to specify an administrative address for our Subnet. diff --git a/content/course/avalanche-fundamentals/04-creating-a-blockchain/02-connect.mdx b/content/course/avalanche-fundamentals/04-creating-a-blockchain/02-connect.mdx index a5467525..2655d5a2 100644 --- a/content/course/avalanche-fundamentals/04-creating-a-blockchain/02-connect.mdx +++ b/content/course/avalanche-fundamentals/04-creating-a-blockchain/02-connect.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘©β€πŸ’» Connecting to Your Blockchain +title: Connecting to Your Blockchain description: Learn how to connect a wallet to your newly created blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- In the final section of this chapter, we will go over the process of connecting your wallet to your Subnet along with sending some funds. diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/00-state-machine.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/00-state-machine.mdx index 304fcf21..4368f177 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/00-state-machine.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/00-state-machine.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• What is a State Machine? +title: What is a State Machine? description: Learn about the State Machines in blockchain systems. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- A virtual machine in the context of a blockchain system is like a decentralized computer that can execute a program in a controlled environment. A Virtual Machine (VM) defines the application-level logic of a blockchain. In technical terms, it specifies the blockchain’s state, state transition function, transactions, and the API through which users can interact with the blockchain. diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/01-blockchains.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/01-blockchains.mdx index 9def50b3..fc6b999d 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/01-blockchains.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/01-blockchains.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Blockchains +title: Blockchains description: Learn about how VMs work in blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- Let’s look at how VMs work in blockchain. Each validator operates an instance of our hypothetical soda dispenser. So, they have their own instance of a machine running on their server. They do this so they do not have to trust a single party with the operation of a soda dispenser, and to make it easy for everyone to verify the outcome of operations. diff --git a/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-variety-of-vm.mdx b/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-variety-of-vm.mdx index c9ed89d6..84f52042 100644 --- a/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-variety-of-vm.mdx +++ b/content/course/avalanche-fundamentals/05-vms-and-blockchains/02-variety-of-vm.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Variety of Virtual Machines +title: Variety of Virtual Machines description: Learn about different types of Virtual Machines. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- We can take this concept even further: The same network of validators can run two or more blockchains. These could operate different VMs, like a soda dispenser and a candy dispenser, or identical VMs, like two soda dispensers. When a user wants to issue an operation, they specify which blockchain to interact with. diff --git a/content/course/avalanche-fundamentals/06-vm-customization/00-intro.mdx b/content/course/avalanche-fundamentals/06-vm-customization/00-intro.mdx index 45b0a915..9124b778 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/00-intro.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/00-intro.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• Introduction to VM Customization +title: Introduction to VM Customization description: Learn about customizing Virtual Machines. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- For some use cases, it may be necessary to use a customized VM too. This is the case if an application cannot be built on a regular EVM on the C-Chain, or if it would result in gas costs too high to be economical for its users or creators. diff --git a/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx b/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx index 89c7ee85..f4b5b8d1 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/01-configuration.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• VM Configuration +title: VM Configuration description: Learn about customizing Virtual Machines. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- When building a VM, it is possible to define certain parameters that can change the behavior of the VM. In our soda dispenser analogy these may be the products and prices offered by the dispenser. We might want to have two dispenser blockchains that offer different products and prices. If the VM is built in a way that it has parameters for the products and prices, it can be easily reused for different use items. diff --git a/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx b/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx index f871af90..4190f084 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/02-modification.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• VM Modification +title: VM Modification description: Learn about modifying Virtual Machines. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- The next more powerful way to customize a VM is VM Modification. To meet our requirements, we can modify and extend our existing VM. In our soda dispenser analogy, we might want to customize our soda dispenser to accept card payments. Our current soda dispenser machine simply does not have this feature. Instead of reinventing the wheel and building a new dispenser with the new feature from the ground up, we simply extend the existing VM using a plugin. diff --git a/content/course/avalanche-fundamentals/06-vm-customization/03-creation.mdx b/content/course/avalanche-fundamentals/06-vm-customization/03-creation.mdx index edc1eb39..b1b1bda8 100644 --- a/content/course/avalanche-fundamentals/06-vm-customization/03-creation.mdx +++ b/content/course/avalanche-fundamentals/06-vm-customization/03-creation.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“• VM Creation +title: VM Creation description: Learn about creating Virtual Machines. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- For some use cases it might be necessary to create entirely new VMs. diff --git a/content/course/avalanche-fundamentals/index.mdx b/content/course/avalanche-fundamentals/index.mdx index 3363d399..f5fef77c 100644 --- a/content/course/avalanche-fundamentals/index.mdx +++ b/content/course/avalanche-fundamentals/index.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘‹ Welcome to the Course +title: Welcome to the Course description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Layers --- Welcome to the Avalanche Fundamentals, an online course introducing you to the exciting world of the Avalanche technology! This course will provide you with a comprehensive understanding of the basic concepts making Avalanche unique. diff --git a/content/course/customizing-evm/01-intro/02-meet-your-instructors.mdx b/content/course/customizing-evm/01-intro/02-meet-your-instructors.mdx index 9af9a4d0..f8962eed 100644 --- a/content/course/customizing-evm/01-intro/02-meet-your-instructors.mdx +++ b/content/course/customizing-evm/01-intro/02-meet-your-instructors.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘¨β€πŸ« Meet your Instructors +title: Meet your Instructors description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Users --- import Instructors from "@/content/common/intro/instructors.mdx" diff --git a/content/course/customizing-evm/01-intro/03-prerequisites.mdx b/content/course/customizing-evm/01-intro/03-prerequisites.mdx index 0e5769f6..ee1aeef8 100644 --- a/content/course/customizing-evm/01-intro/03-prerequisites.mdx +++ b/content/course/customizing-evm/01-intro/03-prerequisites.mdx @@ -1,8 +1,9 @@ --- -title: πŸ“š Prerequisites +title: Prerequisites description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Binary --- ## Avalanche diff --git a/content/course/customizing-evm/01-intro/04-learning-outcomes.mdx b/content/course/customizing-evm/01-intro/04-learning-outcomes.mdx index 17a55f9c..1bc3e38c 100644 --- a/content/course/customizing-evm/01-intro/04-learning-outcomes.mdx +++ b/content/course/customizing-evm/01-intro/04-learning-outcomes.mdx @@ -1,8 +1,9 @@ --- -title: 🎯 Learning Outcomes +title: Learning Outcomes description: Learn about the basics of Avalanche. updated: 2024-05-31 authors: [ashucoder9] +icon: Rocket --- By the end of this course, students will: diff --git a/content/course/customizing-evm/01-intro/05-join-course-chat.mdx b/content/course/customizing-evm/01-intro/05-join-course-chat.mdx index 1754d428..14746c00 100644 --- a/content/course/customizing-evm/01-intro/05-join-course-chat.mdx +++ b/content/course/customizing-evm/01-intro/05-join-course-chat.mdx @@ -1,8 +1,9 @@ --- -title: 🀝 Join Course Chat +title: Join Course Chat description: Join the Avalanche Academy Telegram group to chat with other students and instructors. updated: 2024-05-31 authors: [ashucoder9] +icon: Send --- import CourseChat from "@/content/common/intro/course-chat.mdx"; diff --git a/content/course/customizing-evm/02-intro-to-evm/00-evm-and-precompiles.mdx b/content/course/customizing-evm/02-intro-to-evm/00-evm-and-precompiles.mdx index f52edd96..90c9b006 100644 --- a/content/course/customizing-evm/02-intro-to-evm/00-evm-and-precompiles.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/00-evm-and-precompiles.mdx @@ -2,7 +2,8 @@ title: The EVM and Precompiles description: Learn about the basics of the EVM and precompiles updated: 2024-05-31 -authors: [ashucoder9] +authors: [ashucoder9, martineckardt] +icon: Box --- In this section we will explore the basics of the EVM and precompiles. Further, you will get to interact with a precompile available on the Avalanche C-Chain: diff --git a/content/course/customizing-evm/02-intro-to-evm/01-origin-of-evm.mdx b/content/course/customizing-evm/02-intro-to-evm/01-origin-of-evm.mdx index 86aaa9d5..da57a998 100644 --- a/content/course/customizing-evm/02-intro-to-evm/01-origin-of-evm.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/01-origin-of-evm.mdx @@ -3,6 +3,7 @@ title: Origin of the EVM description: Learn about the origin of Ethereum Virtual Machine (EVM). updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- The Ethereum Virtual Machine (EVM) is a key component of Ethereum’s infrastructure, responsible for executing smart contracts across the network. The origins of the EVM can be traced back to the creation of the Ethereum blockchain itself, which was proposed in a whitepaper by Vitalik Buterin in late 2013. diff --git a/content/course/customizing-evm/02-intro-to-evm/02-accounts-keys-address.mdx b/content/course/customizing-evm/02-intro-to-evm/02-accounts-keys-address.mdx index 127265d0..40020cfd 100644 --- a/content/course/customizing-evm/02-intro-to-evm/02-accounts-keys-address.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/02-accounts-keys-address.mdx @@ -3,6 +3,7 @@ title: Accounts, Keys and Addresses description: Learn about Accounts, Keys and Addresses in EVM. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Accounts diff --git a/content/course/customizing-evm/02-intro-to-evm/03-transactons-and-blocks.mdx b/content/course/customizing-evm/02-intro-to-evm/03-transactons-and-blocks.mdx index b7fc7041..9f8b74a1 100644 --- a/content/course/customizing-evm/02-intro-to-evm/03-transactons-and-blocks.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/03-transactons-and-blocks.mdx @@ -3,6 +3,7 @@ title: Transactions and Blocks description: Learn about another fundamental aspect of the Ethereum ecosystem - Transactions and Blocks. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Transactions diff --git a/content/course/customizing-evm/02-intro-to-evm/04-what-are-precompiles.mdx b/content/course/customizing-evm/02-intro-to-evm/04-what-are-precompiles.mdx index 6d389394..f2f18895 100644 --- a/content/course/customizing-evm/02-intro-to-evm/04-what-are-precompiles.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/04-what-are-precompiles.mdx @@ -3,6 +3,7 @@ title: What are Precompiles? description: Learn about Precompiled Smart Contracts in EVM. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- Precompiled contracts are a way to execute code written in the low-level coding language Go from the EVM. Go is much faster and more efficient than Solidity. diff --git a/content/course/customizing-evm/02-intro-to-evm/05-why-precompiles.mdx b/content/course/customizing-evm/02-intro-to-evm/05-why-precompiles.mdx index c1bbabb3..5b6e9776 100644 --- a/content/course/customizing-evm/02-intro-to-evm/05-why-precompiles.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/05-why-precompiles.mdx @@ -3,6 +3,7 @@ title: Why Precompiles? description: Learn about why you should utilize Precompiles in your smart contracts. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- Adding precompiles to the Ethereum Virtual Machine (EVM) has several value propositions. We have described a few of them briefly in this chapter. diff --git a/content/course/customizing-evm/02-intro-to-evm/06-setup-core-wallet.mdx b/content/course/customizing-evm/02-intro-to-evm/06-setup-core-wallet.mdx index 9b62c7a0..5c348bc9 100644 --- a/content/course/customizing-evm/02-intro-to-evm/06-setup-core-wallet.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/06-setup-core-wallet.mdx @@ -3,4 +3,5 @@ title: Setup Core Wallet (Draft) description: Learn how to setup Core Wallet. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- \ No newline at end of file diff --git a/content/course/customizing-evm/02-intro-to-evm/07-interact-wtih-precompile.mdx b/content/course/customizing-evm/02-intro-to-evm/07-interact-wtih-precompile.mdx index 166b2a7b..ccb6c49a 100644 --- a/content/course/customizing-evm/02-intro-to-evm/07-interact-wtih-precompile.mdx +++ b/content/course/customizing-evm/02-intro-to-evm/07-interact-wtih-precompile.mdx @@ -3,5 +3,6 @@ title: Interact with a Precompile (draft) description: Learn about why you should utilize Precompiles in your smart contracts. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- diff --git a/content/course/customizing-evm/03-development-env-setup/00-introduction.mdx b/content/course/customizing-evm/03-development-env-setup/00-intro.mdx similarity index 98% rename from content/course/customizing-evm/03-development-env-setup/00-introduction.mdx rename to content/course/customizing-evm/03-development-env-setup/00-intro.mdx index 0872c912..b7735d1c 100644 --- a/content/course/customizing-evm/03-development-env-setup/00-introduction.mdx +++ b/content/course/customizing-evm/03-development-env-setup/00-intro.mdx @@ -3,6 +3,7 @@ title: Setting Up Our Development Environment description: Start by setting up your Development Environment. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- It's time to set up the development environment required for us to develop customizing the EVM! Although teaching you about precompiles is the foremost objective of this course, we also want you to be able to configure a development environment that is instrumental to achieving a smooth and efficient workflow. diff --git a/content/course/customizing-evm/03-development-env-setup/01-different-evm-versions.mdx b/content/course/customizing-evm/03-development-env-setup/01-different-evm-versions.mdx index 349a9796..6a6e5f60 100644 --- a/content/course/customizing-evm/03-development-env-setup/01-different-evm-versions.mdx +++ b/content/course/customizing-evm/03-development-env-setup/01-different-evm-versions.mdx @@ -3,6 +3,7 @@ title: Different Versions of EVM description: Learn about the different versions of Ethereum Virtual Machine (EVM) in Avalanche ecosystem. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Geth diff --git a/content/course/customizing-evm/03-development-env-setup/02-create-precompile-repo.mdx b/content/course/customizing-evm/03-development-env-setup/02-create-precompile-repo.mdx index 692099cc..f9378b87 100644 --- a/content/course/customizing-evm/03-development-env-setup/02-create-precompile-repo.mdx +++ b/content/course/customizing-evm/03-development-env-setup/02-create-precompile-repo.mdx @@ -3,6 +3,7 @@ title: Creating a Precompile-EVM Repository description: Learn about the different versions of Ethereum Virtual Machine (EVM) in Avalanche ecosystem. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- import { Callout } from 'fumadocs-ui/components/callout'; diff --git a/content/course/customizing-evm/03-development-env-setup/03-setup-test-core-account.mdx b/content/course/customizing-evm/03-development-env-setup/03-setup-test-core-account.mdx index e4cebecd..5bb8bf4d 100644 --- a/content/course/customizing-evm/03-development-env-setup/03-setup-test-core-account.mdx +++ b/content/course/customizing-evm/03-development-env-setup/03-setup-test-core-account.mdx @@ -3,6 +3,7 @@ title: Setup Test Account in Core Wallet description: Learn how to setup a test account in your Core Wallet. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- import { Callout } from 'fumadocs-ui/components/callout'; @@ -27,12 +28,8 @@ In order to use this account, import it to the Core browser extension. Click on The private key for this address is: `56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027` -![](/course/customizing-evm/2.png) + -![](/course/customizing-evm/3.png) +To make sure we don't confuse this account with another account, let's rename it: -To make sure we don't confuse this account with another Account, let's rename it: - -![](/course/customizing-evm/4.png) - -![](/course/customizing-evm/5.png) \ No newline at end of file + \ No newline at end of file diff --git a/content/course/customizing-evm/03-development-env-setup/04-choose-your-setup.mdx b/content/course/customizing-evm/03-development-env-setup/04-choose-your-setup.mdx index ae4ae2b6..5dd0666b 100644 --- a/content/course/customizing-evm/03-development-env-setup/04-choose-your-setup.mdx +++ b/content/course/customizing-evm/03-development-env-setup/04-choose-your-setup.mdx @@ -3,6 +3,7 @@ title: Choose Your Development Setup description: Learn about different development setups available for developing on the Avalanche EVM. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Setup Steps diff --git a/content/course/customizing-evm/04-setup-codespaces/00-what-is-codespaces.mdx b/content/course/customizing-evm/04-setup-codespaces/00-what-is-codespaces.mdx index f5b0f14e..adf4a028 100644 --- a/content/course/customizing-evm/04-setup-codespaces/00-what-is-codespaces.mdx +++ b/content/course/customizing-evm/04-setup-codespaces/00-what-is-codespaces.mdx @@ -3,6 +3,7 @@ title: What are Codespaces? description: Learn how to setup GitHub Codespaces. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- You've chosen to set up your development environment using Codespaces! By leveraging Codespaces, you will be able to develop in the cloud in a sandboxed environment that is both persistent and is able to run in any browser. In this section, we will: diff --git a/content/course/customizing-evm/04-setup-codespaces/01-create-codespaces.mdx b/content/course/customizing-evm/04-setup-codespaces/01-create-codespaces.mdx index d4804844..9751eb1c 100644 --- a/content/course/customizing-evm/04-setup-codespaces/01-create-codespaces.mdx +++ b/content/course/customizing-evm/04-setup-codespaces/01-create-codespaces.mdx @@ -3,6 +3,7 @@ title: Create Codespaces description: Learn how to create GitHub Codespaces. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## Open Precompile-EVM Repository diff --git a/content/course/customizing-evm/04-setup-codespaces/02-codespace-in-vscode.mdx b/content/course/customizing-evm/04-setup-codespaces/02-codespace-in-vscode.mdx index 1c6c4485..d11be022 100644 --- a/content/course/customizing-evm/04-setup-codespaces/02-codespace-in-vscode.mdx +++ b/content/course/customizing-evm/04-setup-codespaces/02-codespace-in-vscode.mdx @@ -3,6 +3,7 @@ title: Open Codespace in VS Code description: Learn how to setup GitHub Codespaces. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## Switch from Browser to VS Code diff --git a/content/course/customizing-evm/07-your-evm-blockchain/00-intro.mdx b/content/course/customizing-evm/07-your-evm-blockchain/00-intro.mdx index ea7a21c8..07f98aaf 100644 --- a/content/course/customizing-evm/07-your-evm-blockchain/00-intro.mdx +++ b/content/course/customizing-evm/07-your-evm-blockchain/00-intro.mdx @@ -3,6 +3,7 @@ title: Your Own EVM Blockchain description: Learn how to spin up your own EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- In this part of our course, we'll look at running your own Avalanche Network with your own EVM. The ability to run your own EVM enables us to cater to specific use cases and is one of the key advantages of multi-chain systems over monolithic systems. diff --git a/content/course/customizing-evm/07-your-evm-blockchain/01-avalanche-network-runner.mdx b/content/course/customizing-evm/07-your-evm-blockchain/01-avalanche-network-runner.mdx index ec4b0fa0..653d7f2a 100644 --- a/content/course/customizing-evm/07-your-evm-blockchain/01-avalanche-network-runner.mdx +++ b/content/course/customizing-evm/07-your-evm-blockchain/01-avalanche-network-runner.mdx @@ -3,6 +3,7 @@ title: Avalanche Network Runner description: Learn about the Avalanche Network Runner tooling. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## What is the Avalanche Network Runner? diff --git a/content/course/customizing-evm/07-your-evm-blockchain/02-create-your-blockchain.mdx b/content/course/customizing-evm/07-your-evm-blockchain/02-create-your-blockchain.mdx index f09a1b36..d9a9dc66 100644 --- a/content/course/customizing-evm/07-your-evm-blockchain/02-create-your-blockchain.mdx +++ b/content/course/customizing-evm/07-your-evm-blockchain/02-create-your-blockchain.mdx @@ -3,6 +3,7 @@ title: Create Your Blockchain description: Learn how to use Avalanche Network Runner to spin up your own EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## Launch Avalanche-Network-Runner diff --git a/content/course/customizing-evm/07-your-evm-blockchain/03-connecting-core.mdx b/content/course/customizing-evm/07-your-evm-blockchain/03-connecting-core.mdx index 0ee71265..93187a45 100644 --- a/content/course/customizing-evm/07-your-evm-blockchain/03-connecting-core.mdx +++ b/content/course/customizing-evm/07-your-evm-blockchain/03-connecting-core.mdx @@ -3,6 +3,7 @@ title: Connect Core description: Learn how to connect your blockchain to Core wallet, updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## Connecting to a Local Blockchain @@ -69,9 +70,7 @@ Follow these steps to create the RPC Urls for one of the Avalanche nodes running Now, open the Core browser extension and navigate to the screen to add a new network. -![](/course/customizing-evm/20.png) - -![](/course/customizing-evm/21.png) + Now, enter the RPC URL for your network. Make sure it ends with /rpc: `http://127.0.0.1:node_port/ext/bc/your_blockchain_id/rpc` @@ -81,9 +80,7 @@ Also, add the following information: - ChainId: Use the number you assigned at the top of your Genesis JSON file. (If you have been following this tutorial, that should be 99999) - Token Symbol: Any symbol name for your native gas token, such as TOK -![](/course/customizing-evm/22.png) - -![](/course/customizing-evm/23.png) + Hit **Save**. Then make sure to **switch** to the newly created Network. @@ -93,17 +90,13 @@ This Subet uses the default address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC Private Key: `56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027` -![](/course/customizing-evm/24.png) - -![](/course/customizing-evm/25.png) + ## Send Tokens Send tokens to your other account by clicking the circle with the plus and then send. Now, click on the icon in the address field and switch to the tab My Account to select another account. -![](/course/customizing-evm/26.png) - -![](/course/customizing-evm/27.png) + While sending the tokens, reflect on how this is influenced by: - The initial token allocation you have set diff --git a/content/course/customizing-evm/08-chain-configuration/00-vm-configuration.mdx b/content/course/customizing-evm/08-chain-configuration/00-vm-configuration.mdx index aab51404..0db50297 100644 --- a/content/course/customizing-evm/08-chain-configuration/00-vm-configuration.mdx +++ b/content/course/customizing-evm/08-chain-configuration/00-vm-configuration.mdx @@ -3,6 +3,7 @@ title: VM Configuration description: Learn about Virtual Machine configuration. updated: 2024-05-31 authors: [ashucoder9] +icon: Box --- In this part of our course, we will look at optimizing your EVM for specific use cases via the chain configuration. Tailoring EVM configurations to fit specific use cases is one of the key advantages of multi-chain systems. diff --git a/content/course/customizing-evm/08-chain-configuration/01-genesis-block.mdx b/content/course/customizing-evm/08-chain-configuration/01-genesis-block.mdx index 005d8190..818dfa2d 100644 --- a/content/course/customizing-evm/08-chain-configuration/01-genesis-block.mdx +++ b/content/course/customizing-evm/08-chain-configuration/01-genesis-block.mdx @@ -3,6 +3,7 @@ title: Genesis Block description: Learn about the Genesis Block. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Background diff --git a/content/course/customizing-evm/08-chain-configuration/02-create-your-genesis.mdx b/content/course/customizing-evm/08-chain-configuration/02-create-your-genesis.mdx index 8947fca3..f0ec86a5 100644 --- a/content/course/customizing-evm/08-chain-configuration/02-create-your-genesis.mdx +++ b/content/course/customizing-evm/08-chain-configuration/02-create-your-genesis.mdx @@ -3,6 +3,7 @@ title: Create Your Genesis File description: Learn how to create your own genesis file. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- import { Callout } from 'fumadocs-ui/components/callout'; diff --git a/content/course/customizing-evm/08-chain-configuration/03-setup-chainid.mdx b/content/course/customizing-evm/08-chain-configuration/03-setup-chainid.mdx index fa352881..da845f70 100644 --- a/content/course/customizing-evm/08-chain-configuration/03-setup-chainid.mdx +++ b/content/course/customizing-evm/08-chain-configuration/03-setup-chainid.mdx @@ -3,6 +3,7 @@ title: Setup Your ChainID description: Learn how to create your own genesis file. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## What is ChainID? diff --git a/content/course/customizing-evm/08-chain-configuration/04-gas-fees-and-limit.mdx b/content/course/customizing-evm/08-chain-configuration/04-gas-fees-and-limit.mdx index 4923f86e..7eb75183 100644 --- a/content/course/customizing-evm/08-chain-configuration/04-gas-fees-and-limit.mdx +++ b/content/course/customizing-evm/08-chain-configuration/04-gas-fees-and-limit.mdx @@ -3,6 +3,7 @@ title: Gas Fees and Gas Limit description: Learn about Gas Fees and Gas Limit in the context of the EVM. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Background diff --git a/content/course/customizing-evm/08-chain-configuration/05-gas-fee-configuration.mdx b/content/course/customizing-evm/08-chain-configuration/05-gas-fee-configuration.mdx index 25ad4372..ba945a0b 100644 --- a/content/course/customizing-evm/08-chain-configuration/05-gas-fee-configuration.mdx +++ b/content/course/customizing-evm/08-chain-configuration/05-gas-fee-configuration.mdx @@ -3,6 +3,7 @@ title: Gas Fees Configuration description: Learn how to configure gas fees in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- ## Configuration Format diff --git a/content/course/customizing-evm/08-chain-configuration/06-configuring-gas-fees.mdx b/content/course/customizing-evm/08-chain-configuration/06-configuring-gas-fees.mdx index 95d5ad63..d7243616 100644 --- a/content/course/customizing-evm/08-chain-configuration/06-configuring-gas-fees.mdx +++ b/content/course/customizing-evm/08-chain-configuration/06-configuring-gas-fees.mdx @@ -3,6 +3,7 @@ title: Configure Gas Fees description: Learn how to configure gas fees in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- ## Benchmarks diff --git a/content/course/customizing-evm/08-chain-configuration/07-initial-token-allocation.mdx b/content/course/customizing-evm/08-chain-configuration/07-initial-token-allocation.mdx index f475c0de..b7a82a71 100644 --- a/content/course/customizing-evm/08-chain-configuration/07-initial-token-allocation.mdx +++ b/content/course/customizing-evm/08-chain-configuration/07-initial-token-allocation.mdx @@ -3,6 +3,7 @@ title: Initial Token Allocation description: Learn about the initial token allocation and configure in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- import { Callout } from 'fumadocs-ui/components/callout'; diff --git a/content/course/customizing-evm/08-chain-configuration/08-avalanche-network-runner.mdx b/content/course/customizing-evm/08-chain-configuration/08-avalanche-network-runner.mdx index 5414ebba..8a3fd330 100644 --- a/content/course/customizing-evm/08-chain-configuration/08-avalanche-network-runner.mdx +++ b/content/course/customizing-evm/08-chain-configuration/08-avalanche-network-runner.mdx @@ -3,4 +3,5 @@ title: Avalanche Network Runner (draft) description: Learn about the initial token allocation and configure in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Bookmark --- \ No newline at end of file diff --git a/content/course/customizing-evm/08-chain-configuration/09-setup-thunderclient.mdx b/content/course/customizing-evm/08-chain-configuration/09-setup-thunderclient.mdx index c688f662..b95f7c01 100644 --- a/content/course/customizing-evm/08-chain-configuration/09-setup-thunderclient.mdx +++ b/content/course/customizing-evm/08-chain-configuration/09-setup-thunderclient.mdx @@ -3,4 +3,5 @@ title: Setup ThunderClient (draft) description: Learn about the initial token allocation and configure in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- \ No newline at end of file diff --git a/content/course/customizing-evm/08-chain-configuration/10-build-and-run-custom-genesis-blockchain.mdx b/content/course/customizing-evm/08-chain-configuration/10-build-and-run-custom-genesis-blockchain.mdx index c79826d5..fb1fc3f3 100644 --- a/content/course/customizing-evm/08-chain-configuration/10-build-and-run-custom-genesis-blockchain.mdx +++ b/content/course/customizing-evm/08-chain-configuration/10-build-and-run-custom-genesis-blockchain.mdx @@ -3,4 +3,5 @@ title: Build and Run Custom Genesis Blockchain (draft) description: Learn about the initial token allocation and configure in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- \ No newline at end of file diff --git a/content/course/customizing-evm/08-chain-configuration/11-connect-cre.mdx b/content/course/customizing-evm/08-chain-configuration/11-connect-core.mdx similarity index 91% rename from content/course/customizing-evm/08-chain-configuration/11-connect-cre.mdx rename to content/course/customizing-evm/08-chain-configuration/11-connect-core.mdx index b1df75bb..23c852ad 100644 --- a/content/course/customizing-evm/08-chain-configuration/11-connect-cre.mdx +++ b/content/course/customizing-evm/08-chain-configuration/11-connect-core.mdx @@ -3,4 +3,5 @@ title: Connect Core (draft) description: Learn about the initial token allocation and configure in your EVM blockchain. updated: 2024-05-31 authors: [ashucoder9] +icon: Terminal --- \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/00-intro.mdx b/content/course/customizing-evm/09-hash-function-precompile/00-intro.mdx new file mode 100644 index 00000000..35352738 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/00-intro.mdx @@ -0,0 +1,47 @@ +--- +title: Creating a Hash Function Precompile +description: Learn how to create a Hash Function Precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Box +--- + +## What We're Building + +In this section, we'll create a precompile for the hash function md5. Since there's a Go library for this hash function, we can avoid reimplementing the algorithm in Solidity and make use of the better efficiency of Go over Solidity. + + + + +A hash function is a special type of function that takes input data of any size (a letter, a word, the text of a book, all combined texts available on the internet, ...) and converts it into a fixed-size output. This output, called a hash value, represents the original data. The process is often referred to as "hashing." + +Hash functions have a few properties: + +- **Deterministic**: Given the same input, a hash function will always produce the same output. If you hash the word "apple" a thousand times, you'll get the same hash value each time. +- **Fixed Size**: No matter the size of the input data, whether it's a single character or an entire novel, the hash function always produces an output (the hash value) of the same length. +- **Fast Computation**: Hash functions are designed to be quick and efficient, returning a hash value with minimal computational resources and time. +- **Preimage Resistance**: It's computationally infeasible to retrieve the original input data if you only have the hash value. This property is especially important in cryptographic hash functions, where it helps to secure data. +- **Avalanche Effect**: A small change to the input should produce such drastic changes in output that the new hash value appears uncorrelated with the old hash value. If you hash "apple" and "Apple", the resulting hash values should look completely different. +- **Collision Resistance**: It's very unlikely that two different pieces of input data will result in the same hash value. However, due to the limited length of hash values, this is theoretically possible in a scenario called a "collision." Good hash functions make these collisions exceedingly rare. + +Hash functions are used in various applications in computer science, such as data retrieval, data integrity verification, password storage, and in blockchain technology for cryptocurrencies like Bitcoin. + + + + +## Reference Implementation + +To help you understand how precompiles are built, we'll compare each step with a reference example: A precompile for the sha256 hash function. Both precompiles are very similar and just have a single function, which returns the hashed value. + +## Overview of Steps + +Here's an overview of the steps: + +1. Create a Solidity interface for the precompile +2. Generate the ABI +3. Write the precompile code in Go +4. Configure and register the precompile +5. Build and run your customized EVM +6. Connect Remix to your customized EVM and interact with the precompile + +Let's begin! \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/01-create-solidity-interface.mdx b/content/course/customizing-evm/09-hash-function-precompile/01-create-solidity-interface.mdx new file mode 100644 index 00000000..b71700f4 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/01-create-solidity-interface.mdx @@ -0,0 +1,93 @@ +--- +title: Create an MD5 Solidity Interface +description: Learn how to create an MD5 Solidity Interface +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +The first step is defining the interface that will wrap the precompile implementation and that other contracts and users will interact with. In addition to declaring the way users can interact with our MD5 precompile, defining the MD5 interface will also allow us to utilize a generator script. A precompile consists of many files, and generating boilerplate Go files will make implementing the precompile much easier. + +## SHA-256 Precompile Interface + +Before defining the MD5 interface, we will first look at the interface of the very similar SHA-256 precompile. This reference implementation is included in the repository we have created earlier. + +```solidity title="contracts/contracts/interfaces/ISHA256.sol" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface ISHA256 { + /// Compute the hash of value + /// @param value the value to hash + /// @return the hashed value + function hashWithSHA256(string memory value) external view returns(bytes32 hash); + } +``` + +ISHA256 contains a single function `hashWithSHA256`. `hashWithSHA256` takes in a value of type string, which is the value which is to be hashed, and outputs a 32-byte hash. + +## Creating the Solidity Interface For The MD5 Precompile + +Now it's your turn to define a precompile interface! + +Create the interface for the MD5 hash function. Start by going into the same directory where `ISHA256.sol` lives (`contracts/contracts/interfaces/`) and create a new file named `IMD5.sol`. Note that: + +→ MD5 returns a 16-byte hash instead of a 32-byte hash + +→ Make sure to name all parameters and return values + + + + +```solidity title="contracts/contracts/interfaces/IMD5.sol" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface IMD5 { + function hashWithMD5(string memory value) external view returns (bytes16 hash); +} +``` + + + + +## Generate the ABI + +Now that we have an interface of our precompile, let's create an ABI of our Solidity interface. Open the integrated VS Code terminal (control + `), change to the subdirectory /contracts directory, and run the command to compile to solidity interface to the ABI: + +```bash +# Change directory to /contracts +cd contracts +# Compile Imd5.sol to ABI +npx solc@latest --abi ./contracts/interfaces/IMD5.sol -o ./abis --base-path . --include-path ./node_modules +# Rename +mv ./abis/contracts_interfaces_IMD5_sol_IMD5.abi ./abis/IMD5.abi +``` + +Now, you should have a file called `IMD5.abi` in the folder `/contracts/abis` with the following content: + +```json +[ + { + "inputs": [ + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "hashWithMD5", + "outputs": [ + { + "internalType": "bytes16", + "name": "hash", + "type": "bytes16" + } + ], + "stateMutability": "view", + "type": "function" + } +] +``` diff --git a/content/course/customizing-evm/09-hash-function-precompile/02-generate-the-precompile.mdx b/content/course/customizing-evm/09-hash-function-precompile/02-generate-the-precompile.mdx new file mode 100644 index 00000000..a9b45b19 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/02-generate-the-precompile.mdx @@ -0,0 +1,198 @@ +--- +title: Generating the Precompile +description: Learn how to generate your precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +import { File, Files, Folder } from 'fumadocs-ui/components/files'; + +In the last section, we created the ABI for our precompile contract. Now, we'll use the precompile generation script provided by the precompile-evm template to generate a boilerplate code in go for the precompile implementation that will be wrapped in the solidity interface we created in the previous step. + +## Running the Generation Script + +To start, go to the root directory of your precompile-evm project and generate the files necessary for the precompile. + +```bash +# Change to root +cd $GOPATH/src/github.com/ava-labs/precompile-evm +# Generate go files +./scripts/generate_precompile.sh --abi ./contracts/abis/IMD5.abi --type Md5 --pkg md5 --out ./md5 +``` + +Now you should have a new directory in your root directory called `md5`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### `contract.go` + +For the rest of this chapter, we'll work with the `md5/contract.go` file. If you generated the Go files related to your precompile, `contract.go` should look like the code below. + +Do not be intimidated if much of this code does not make sense to you. We'll cover the different parts and add some to code to implement the logic of our MD5 precompile. + +```go +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package md5 + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + HashWithMD5GasCost uint64 = 1 /* SET A GAS COST HERE */ +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = abi.JSON + _ = errors.New + _ = big.NewInt + _ = vmerrs.ErrOutOfGas + _ = common.Big0 +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + + // Md5RawABI contains the raw ABI of Md5 contract. + //go:embed contract.abi + Md5RawABI string + + Md5ABI = contract.ParseABI(Md5RawABI) + + Md5Precompile = createMd5Precompile() +) + +// UnpackHashWithMD5Input attempts to unpack [input] into the string type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackHashWithMD5Input(input []byte) (string, error) { + res, err := Md5ABI.UnpackInput("hashWithMD5", input) + if err != nil { + return "", err + } + unpacked := *abi.ConvertType(res[0], new(string)).(*string) + return unpacked, nil +} + +// PackHashWithMD5 packs [value] of type string into the appropriate arguments for hashWithMD5. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackHashWithMD5(value string) ([]byte, error) { + return Md5ABI.Pack("hashWithMD5", value) +} + +// PackHashWithMD5Output attempts to pack given hash of type [16]byte +// to conform the ABI outputs. +func PackHashWithMD5Output(hash [16]byte) ([]byte, error) { + return Md5ABI.PackOutput("hashWithMD5", hash) +} + +// UnpackHashWithMD5Output attempts to unpack given [output] into the [16]byte type output +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackHashWithMD5Output(output []byte) ([16]byte, error) { + res, err := Md5ABI.Unpack("hashWithMD5", output) + if err != nil { + return [16]byte{}, err + } + unpacked := *abi.ConvertType(res[0], new([16]byte)).(*[16]byte) + return unpacked, nil +} + +func hashWithMD5(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, HashWithMD5GasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the HashWithMD5Input. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackHashWithMD5Input(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output [16]byte // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackHashWithMD5Output(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createMd5Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. + +func createMd5Precompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "hashWithMD5": hashWithMD5, + } + + for name, function := range abiFunctionMap { + method, ok := Md5ABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} +``` \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/03-unpack-input-pack-output.mdx b/content/course/customizing-evm/09-hash-function-precompile/03-unpack-input-pack-output.mdx new file mode 100644 index 00000000..50d87622 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/03-unpack-input-pack-output.mdx @@ -0,0 +1,54 @@ +--- +title: Unpacking Inputs and Packing Outputs +description: Learn how to unpack inputs and pack outputs. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +In this first segment of examining the `contract.go` file generated for us, we will go over the packing and unpacking functions in the `contracts.go`. + +## The Notion of Packing + +Those eager to implement the MD5 algorithm might be wondering why we're discussing packing. However, there is good reason to discuss packing, and it comes down to the specification of the `staticcall` function in Solidity. + +We begin by referring to the example of calling the SHA-256 precompiled contract: + +```go +(bool ok, bytes memory out) = address(2).staticcall(abi.encode(numberToHash)); +``` + +As seen above, the `staticcall` function accepts input in bytes format, generated by `abi.encode`, and returns a boolean value indicating success, along with a bytes format output. + +Therefore, our precompiled contract should be designed to accept and return data in bytes format, involving the packing and unpacking of values. Since packing is a deterministic process, there's no concern about data corruption during translation. However, some preprocessing or postprocessing is necessary to ensure the contract functions correctly. + +## Unpacking Inputs + +In `contract.go`, the function `UnpackHashWithMd5Input` unpacks our data and converts it into a type relevant to us. It takes a byte array as an input and returns a Go string. We will look at more complex precompiles that have multiple functions that may take multiple inputs later. + +```go +// UnpackHashWithMD5Input attempts to unpack [input] into the string type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackHashWithMD5Input(input []byte) (string, error) { + res, err := Md5ABI.UnpackInput("hashWithMD5", input) + if err != nil { + return "", err + } + unpacked := *abi.ConvertType(res[0], new(string)).(*string) + return unpacked, nil +} +``` + +## Packing Outputs + +Ignoring `hashWithMD5` for now, note that whatever value `hashWithMD5` outputs, we will need to postprocess it (i.e. pack it). `PackHashWithMD5Output` does just this, taking in an input of type [16]byte and outputting a byte array which can be returned by `staticcall`. + +```go +// PackHashWithMd5Output attempts to pack given hash of type [16]byte +// to conform the ABI outputs. +func PackHashWithMd5Output(hash [16]byte) ([]byte, error) { + return Md5ABI.PackOutput("hash_with_md5", hash) +} +``` + +This may seem trivial, but if our Solidity interface defined our function to return a uint or string, the type of our input to this function would differ accordingly. \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/04-implementing-precompile.mdx b/content/course/customizing-evm/09-hash-function-precompile/04-implementing-precompile.mdx new file mode 100644 index 00000000..60c2eb09 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/04-implementing-precompile.mdx @@ -0,0 +1,112 @@ +--- +title: Implementing the Precompile +description: Learn how to implement the Precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Now, we'll implement the logic of the precompile in Go. We'll hash our string using the MD5 algorithm. + +## SHA-256 Precompile Implementation + +Before defining the logic of our MD5 precompile, let's look at the logic of the function `hashWithSHA256` (located in `sha256/contract.go`), which computes the SHA-256 hash of a string: + +```go title="sha256/contract.go" +import ( + "crypto/sha256" + //... +) + +// ... + +func hashWithSHA256(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, HashWithSHA256GasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the HashWithSHA256Input. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackHashWithSHA256Input(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output [32]byte // CUSTOM CODE FOR AN OUTPUT + + output = sha256.Sum256([]byte(inputStruct)) + + packedOutput, err := PackHashWithSHA256Output(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + +As you can see, we're performing the following steps: + +- Line 1: Importing the sha256 function from the crypto library (at the top of the Go file) +- Line 15: Unpacking the input to the variable inputStruct. It doesn't make sense that the variable has Struct in its name, but you will see why it is done like this when we have multiple inputs in a later example +- Line 24: Calling the sha256 function and assign its result to the output variable +- Line 26: Packing the output into a byte array +- Line 32: Returning the packed output, the remaining gas and nil, since no error has occurred + +## Implementing the MD5 Precompile in `contract.go` + +Go ahead and implement the `md5/contract.go` for the MD5 precompile. You should only have to write a few lines of code. If you're unsure which function to use, the following documentation might help: [Go Documentation Crypto/md5](https://pkg.go.dev/crypto/md5#Sum) + + + + +```go title="md5/contract.go" +import ( + "crypto/md5" + // ... +) + +// ... + +func hashWithMD5(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {func hashWithMD5(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, HashWithMD5GasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the HashWithMD5Input. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackHashWithMD5Input(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output [16]byte // CUSTOM CODE FOR AN OUTPUT + output = md5.Sum([]byte(inputStruct)) + + packedOutput, err := PackHashWithMD5Output(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + +To solve this task, we did the following things: + +- Import the md5 function from the crypto library +- Unpack the input to a variable inputStruct (It does not make sense that the variable has Struct in its name, but you will see why it is done like this when we have multiple inputs in a later example) +- Call the md5 function and assign it's result to the output variable +- Pack the output into a byte array +- Return the packed output, the remaining gas and nil, since no error has occurred + + + \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/05-configkey-and-contractaddr.mdx b/content/course/customizing-evm/09-hash-function-precompile/05-configkey-and-contractaddr.mdx new file mode 100644 index 00000000..c954e368 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/05-configkey-and-contractaddr.mdx @@ -0,0 +1,88 @@ +--- +title: Setting Config Key and Contract Address +description: Learn how to set ConfigKey and ContractAddress +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +## Config Key + +The precompile config key is used to configure the precompile in the `chainConfig`. It's set in the `module.go` file of our precompile. + +The generator chooses an initial value that should be sufficient for many cases. For the sha256 precompile this is: + +```go title="sha256/module.go" +// ConfigKey is the key used in json config files to specify this precompile precompileconfig. +// must be unique across all precompiles. +const ConfigKey = "sha256Config" +sha256/module.go +This key is later used in the chainConfig to set the activation timestamp of the precompile: +{ + "config": { + "chainId": 99999, + // ... + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "sha256Config": { + "blockTimestamp": 0 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + // ... +} +``` + +We'll see later how we can use this config to do perform more sophisticated actions, like setting the initial state of the precompile. + +## Contract Address + +Each precompile has a unique contract address we can use to call it. This is the address we used earlier to instantiate the precompile in our solidity code or in remix when we interacted with the precompile. + +```go title="sha256/module.go" +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See precompile/registry/registry.go for registered precompile contracts and more information. + +var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000001") +``` + +The `0x01` range is reserved for precompiles added by Ethereum. + +The `0x02` range is reserved for precompiles provided by Avalanche. + +The `0x03` range is reserved for custom precompiles. + +## Setting ConfigKey and ContractAddress for MD5 Precompile + +Set a custom precompile address in the `md5/module.go` file. + + + + +```go title="md5/module.go" +// ConfigKey is the key used in json config files to specify this precompile precompileconfig. +// must be unique across all precompiles. +const ConfigKey = "md5Config" +​ +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See precompile/registry/registry.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("0x0300000000000000000000000000000000000002") +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/06-register-precompile.mdx b/content/course/customizing-evm/09-hash-function-precompile/06-register-precompile.mdx new file mode 100644 index 00000000..b78baa95 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/06-register-precompile.mdx @@ -0,0 +1,102 @@ +--- +title: Registering Your Precompile +description: Learn how to register your precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +The next step in developing our precompile is to **register it with precompile-evm**. For this, take a look at `plugin/main.go`. You should see the following file: + +```go title="plugin/main.go" +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "fmt" + + "github.com/ava-labs/avalanchego/version" + "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ava-labs/subnet-evm/plugin/runner" + + // Each precompile generated by the precompilegen tool has a self-registering init function + // that registers the precompile with the subnet-evm. Importing the precompile package here + // will cause the precompile to be registered with the subnet-evm. + // ADD YOUR PRECOMPILE HERE + //_ "github.com/ava-labs/precompile-evm/{yourprecompilepkg}" +) + +const Version = "v0.1.4" + +func main() { + versionString := fmt.Sprintf("Precompile-EVM/%s Subnet-EVM/%s [AvalancheGo=%s, rpcchainvm=%d]", Version, evm.Version, version.Current, version.RPCChainVMProtocol) + runner.Run(versionString) +} +``` + +As of now, we do not have any precompile registered. As a first step, register the SHA-256 precompile. Your updated file should look like the following: + +```go title="plugin/main.go" +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "fmt" + + "github.com/ava-labs/avalanchego/version" + "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ava-labs/subnet-evm/plugin/runner" + + // Each precompile generated by the precompilegen tool has a self-registering init function + // that registers the precompile with the subnet-evm. Importing the precompile package here + // will cause the precompile to be registered with the subnet-evm. + _ "github.com/ava-labs/precompile-evm/sha256" +) + +const Version = "v0.1.4" + +func main() { + versionString := fmt.Sprintf("Precompile-EVM/%s Subnet-EVM/%s [AvalancheGo=%s, rpcchainvm=%d]", Version, evm.Version, version.Current, version.RPCChainVMProtocol) + runner.Run(versionString) +} +``` + +Now, register the MD5 precompile using the same format as for the SHA-256 precompile. Add the necessary line to `main.go` so that we can use the MD5 precompile. + + + + +```go title="plugin/main.go" +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package main + +import ( + "fmt" + + "github.com/ava-labs/avalanchego/version" + "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ava-labs/subnet-evm/plugin/runner" + + // Each precompile generated by the precompilegen tool has a self-registering init function + // that registers the precompile with the subnet-evm. Importing the precompile package here + // will cause the precompile to be registered with the subnet-evm. + _ "github.com/ava-labs/precompile-evm/sha256" + _ "github.com/ava-labs/precompile-evm/md5" +) + +const Version = "v0.1.4" + +func main() { + versionString := fmt.Sprintf("Precompile-EVM/%s Subnet-EVM/%s [AvalancheGo=%s, rpcchainvm=%d]", Version, evm.Version, version.Current, version.RPCChainVMProtocol) + runner.Run(versionString) +} +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/07-creating-genesis-block.mdx b/content/course/customizing-evm/09-hash-function-precompile/07-creating-genesis-block.mdx new file mode 100644 index 00000000..f4dac0b7 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/07-creating-genesis-block.mdx @@ -0,0 +1,118 @@ +--- +title: Creating Genesis Block with precompileConfig +description: Learn how to create the genesis block with precompileConfig. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In order to create a new Blockchain from our customized EVM, we must define a Genesis block just as we did in our section earlier. + +```json title="tests/precompile/genesis/sha256.json" +{ + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "sha256Config": { + "blockTimestamp": 0 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} +``` + +The precompile config only has a single entry called `blockTimestamp`. This is the activation time of the precompile. Initially, we will set this 0 so that the precompile will be available from the start. Later we will see more advanced configurations. + +## Adding PrecompileConfig for MD5 Precompile + +To incorporate the MD5 into the gensis block, we need to update the config JSON to include a key for the MD5 precompile. This key is the `configKey` that we specified in the `module.go` file of the MD5 precompile in previous section. Copy the `sha256.json` into a new file called `MD5.json`, and add the necessary key-pair value to the config. + + + + +```json title="tests/precompile/genesis/MD5.json" +{ + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "sha256Config": { + "blockTimestamp": 0 + }, + "md5Config": { + "blockTimestamp": 0 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/09-hash-function-precompile/08-build-and-run.mdx b/content/course/customizing-evm/09-hash-function-precompile/08-build-and-run.mdx new file mode 100644 index 00000000..6d07a682 --- /dev/null +++ b/content/course/customizing-evm/09-hash-function-precompile/08-build-and-run.mdx @@ -0,0 +1,60 @@ +--- +title: Build and Run +description: Learn how to build and run your custom VM on a local network. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +## Build Your Custom VM + +There's a simple build script in the Precompile-EVM we can utilize to build. First, make sure you are in the root folder of you Precompile-EVM: + +```bash +cd $GOPATH/src/github.com/ava-labs/precompile-evm +``` + +Then run the command to initiate the build script: + +```bash +./scripts/build.sh +``` + +If you do not see any error, the build was successful. + +## Run a Local Network with Your Custom VM + +First, make sure the gRPC of the Avalanche Network Runner is running. If not, start it with: + +```bash +avalanche-network-runner server \ +--log-level debug \ +--port=":8080" \ +--grpc-gateway-port=":8081" +``` + + + + +If the above command fails with the error: "listen tcp :8080: bind: address already in use", then your server is probably still running from the previous exercise. + +To see what is running on port 8080 run the following command: `sudo lsof -i tcp:8080` + +You can kill the process by using the PID stated in the earlier output: `sudo kill -9 ` + +Now run the command above again and everything should work as expected. + + + + +If it wasn't running, start a network with the blockchain spec like we did in the [previous chapter](./content/course/customizing-evm/07-your-evm-blockchain/02-create-your-blockchain.mdx). + +If it was still running, we do not need to call the `control start` command, but can simply add a new blockchain to the already running local network using the HTTP Client. Don't forget to add the path to your new `md5.json` genesis file. + +![](/course/customizing-evm/29.png) + +Alternatively, you can also open a new terminal and issue the following command to create the new blockchain: + +```bash +avalanche-network-runner control create-blockchains '[{"vm_name":"subnetevm","genesis":"tests/precompile/genesis/sha256.json"}]' +``` \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/00-intro.mdx b/content/course/customizing-evm/10-calculator-precompile/00-intro.mdx new file mode 100644 index 00000000..907f7d9a --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/00-intro.mdx @@ -0,0 +1,57 @@ +--- +title: Overview +description: Learn how to create a Calculator precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Box +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +## Reference Implementation + +In this section, we will showcase how to build a more complex precompile that offers selected simple math operations. Our calculator will support the following operations: + +1. Add two numbers and return the result (3 + 5 = 8) +2. Get the next two greater numbers (7 => 8, 9) +3. Repeat a string x times (4, b => bbbb) + +This is somewhat odd calculator and real-life usability might not be the best, but as you will see in a bit the operations have been chosen to demonstrate some different scenarios we might face while building precompiles. + +## What You Are Building + +Similar to the Calculator precompile, you will be building a precompile called **CalculatorPlus** which contains the following mathematical functions: + +- **Powers of Three**: takes in as input an integer base; returns the square, cube, and 4th power of the input +- **Modulo+**: takes in as input two arguments: the dividend and the divisor. Returns how many times the dividend fits in the divisor, and the remainder. +- **Simplify Fraction**: takes in two arguments: the numerator and the denominator. Returns the simplfied version of the fraction (if the denominator is 0, we return 0) + +## Overview of Steps + +Compared to the process before, we will now also add tests for our precompile. Here's a quick overview of the steps we're going to follow: + + + +Create a Solidity interface for the precompile + + +Generate the ABI + + +Write the precompile code in Go + + +Configure and register the precompile + + +Add and run tests + + +Build and run your customized EVM + + +Connect Remix to your customized EVM and interact with the precompile + + + +This tutorial will help you understand how to create more complex precompiles. Let's begin! diff --git a/content/course/customizing-evm/10-calculator-precompile/01-create-solidity-interface.mdx b/content/course/customizing-evm/10-calculator-precompile/01-create-solidity-interface.mdx new file mode 100644 index 00000000..117ea2d9 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/01-create-solidity-interface.mdx @@ -0,0 +1,158 @@ +--- +title: Create Solidity Interface +description: Learn how to create the Solidity interface for your calculator precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Just like in the MD5 section, we will start off by first demonstrating the Solidity interface for the Calculator precompile before guiding you on how to build the **CalculatorPlus** precompile. To start, let's take a look at the Calculator Solidity interface: + +```solidity title="solidity/interfaces/ICalculator.sol" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface ICalculator { + function add(uint value1, uint value2) external view returns(uint result); + + function nextTwo(uint value1) external view returns(uint result1, uint result2); + + function repeat(uint times, string memory text) external view returns(string memory result); +} +``` + +With this in mind, let's define the CalculatorPlus Solidity interface. Your interface should have the following three functions: + +1. `powOfThree`: takes in an unsigned integer base, and returns three unsigned integers named secondPow, thirdPow, fourthPow . +2. `moduloPlus`: takes in unsigned integers dividend and divisor as input, and returns two unsigned integers named multiple and remainder . +3. `simplFrac`: takes in unsigned integers named numerator and denominator, and returns two unsigned integers named simplNum and simplDenom + + + + +```solidity title="solidity/interfaces/ICalculatorPlus.sol" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface ICalculatorPlus { + function powOfThree(uint256 base) external view returns(uint256 secondPow, uint256 thirdPow, uint256 fourthPow); + + function moduloPlus(uint256 dividend, uint256 divisor) external view returns(uint256 multiple, uint256 remainder); + + function simplFrac(uint256 numerator, uint256 denominator) external view returns(uint256 simplNum, uint256 simplDenom); +} +``` + + + + +## Generate the ABI + +Now that we have an interface of our precompile, let's create an ABI of our Solidity interface. Open the terminal (control + `), change to the `/contracts` directory and run the following command to compile solidity interface to the ABI: + +```bash +# Go to the upper contracts directory of your project +cd contracts + +# Compile ICalculatorPlus.sol to ABI +npx solc@latest --abi ./contracts/interfaces/ICalculatorPlus.sol -o ./abis --base-path . --include-path ./node_modules + +# Rename using this script or manually +mv ./abis/contracts_interfaces_ICalculatorPlus_sol_ICalculatorPlus.abi ./abis/ICalculatorPlus.abi +``` + +Now you should have a file called `ICalculatorPlus.abi` in the folder `/contracts/abis` with the following content: + +```json title="/contracts/abis/ICalculatorPlus.abi" +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "dividend", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "divisor", + "type": "uint256" + } + ], + "name": "moduloPlus", + "outputs": [ + { + "internalType": "uint256", + "name": "multiple", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "remainder", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "base", + "type": "uint256" + } + ], + "name": "powOfThree", + "outputs": [ + { + "internalType": "uint256", + "name": "secondPow", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "thirdPow", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fourthPow", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numerator", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "denominator", + "type": "uint256" + } + ], + "name": "simplFrac", + "outputs": [ + { + "internalType": "uint256", + "name": "simplNum", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "simplDenom", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] +``` \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/02-generating-precompile.mdx b/content/course/customizing-evm/10-calculator-precompile/02-generating-precompile.mdx new file mode 100644 index 00000000..5bee640c --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/02-generating-precompile.mdx @@ -0,0 +1,303 @@ +--- +title: Generating the Precompile +description: Learn how to generating the precompile +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In this step, we will again utilize the precompile generation script to generate all the Go files based on the ABI for your calculator. + +## Run Generation Script + +Change to the root directory of your precompile-evm project and run the command to generate the go files: + +```bash +# Change to root +cd .. + +# Generate go files +./scripts/generate_precompile.sh --abi ./contracts/abis/ICalculatorPlus.abi --type Calculatorplus --pkg calculatorplus --out ./calculatorplus +``` + +Now you should have a new directory called `calculatorplus` in the root directory of your project. + +If you check our the generated contract.go file you will see right away that it is much longer than in our hash function precompile from earlier. This is due to the fact that our calculator precompile has more functions and parameters. Browse through the code and see if you can spot the new elements: + +```go title="contract.go" +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package calculatorplus + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + ModuloPlusGasCost uint64 = 1 /* SET A GAS COST HERE */ + PowOfThreeGasCost uint64 = 1 /* SET A GAS COST HERE */ + SimplFracGasCost uint64 = 1 /* SET A GAS COST HERE */ +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = abi.JSON + _ = errors.New + _ = big.NewInt + _ = vmerrs.ErrOutOfGas + _ = common.Big0 +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + // CalculatorplusRawABI contains the raw ABI of Calculatorplus contract. + //go:embed contract.abi + CalculatorplusRawABI string + + CalculatorplusABI = contract.ParseABI(CalculatorplusRawABI) + + CalculatorplusPrecompile = createCalculatorplusPrecompile() +) + +type ModuloPlusInput struct { + Dividend *big.Int + Divisor *big.Int +} + +type ModuloPlusOutput struct { + Multiple *big.Int + Remainder *big.Int +} + +type PowOfThreeOutput struct { + SecondPow *big.Int + ThirdPow *big.Int + FourthPow *big.Int +} + +type SimplFracInput struct { + Numerator *big.Int + Denominator *big.Int +} + +type SimplFracOutput struct { + SimplNum *big.Int + SimplDenom *big.Int +} + +// UnpackModuloPlusInput attempts to unpack [input] as ModuloPlusInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackModuloPlusInput(input []byte) (ModuloPlusInput, error) { + inputStruct := ModuloPlusInput{} + err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "moduloPlus", input) + + return inputStruct, err +} + +// PackModuloPlus packs [inputStruct] of type ModuloPlusInput into the appropriate arguments for moduloPlus. +func PackModuloPlus(inputStruct ModuloPlusInput) ([]byte, error) { + return CalculatorplusABI.Pack("moduloPlus", inputStruct.Dividend, inputStruct.Divisor) +} + +// PackModuloPlusOutput attempts to pack given [outputStruct] of type ModuloPlusOutput +// to conform the ABI outputs. +func PackModuloPlusOutput(outputStruct ModuloPlusOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("moduloPlus", + outputStruct.Multiple, + outputStruct.Remainder, + ) +} + +// UnpackModuloPlusOutput attempts to unpack [output] as ModuloPlusOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackModuloPlusOutput(output []byte) (ModuloPlusOutput, error) { + outputStruct := ModuloPlusOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "moduloPlus", output) + + return outputStruct, err +} + +func moduloPlus(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ModuloPlusGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the ModuloPlusInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackModuloPlusInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output ModuloPlusOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackModuloPlusOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackPowOfThreeInput attempts to unpack [input] into the *big.Int type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackPowOfThreeInput(input []byte) (*big.Int, error) { + res, err := CalculatorplusABI.UnpackInput("powOfThree", input) + if err != nil { + return new(big.Int), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} + +// PackPowOfThree packs [base] of type *big.Int into the appropriate arguments for powOfThree. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackPowOfThree(base *big.Int) ([]byte, error) { + return CalculatorplusABI.Pack("powOfThree", base) +} + +// PackPowOfThreeOutput attempts to pack given [outputStruct] of type PowOfThreeOutput +// to conform the ABI outputs. +func PackPowOfThreeOutput(outputStruct PowOfThreeOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("powOfThree", + outputStruct.SecondPow, + outputStruct.ThirdPow, + outputStruct.FourthPow, + ) +} + +// UnpackPowOfThreeOutput attempts to unpack [output] as PowOfThreeOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackPowOfThreeOutput(output []byte) (PowOfThreeOutput, error) { + outputStruct := PowOfThreeOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "powOfThree", output) + + return outputStruct, err +} + +func powOfThree(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, PowOfThreeGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the PowOfThreeInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackPowOfThreeInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output PowOfThreeOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackPowOfThreeOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackSimplFracInput attempts to unpack [input] as SimplFracInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackSimplFracInput(input []byte) (SimplFracInput, error) { + inputStruct := SimplFracInput{} + err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "simplFrac", input) + + return inputStruct, err +} + +// PackSimplFrac packs [inputStruct] of type SimplFracInput into the appropriate arguments for simplFrac. +func PackSimplFrac(inputStruct SimplFracInput) ([]byte, error) { + return CalculatorplusABI.Pack("simplFrac", inputStruct.Numerator, inputStruct.Denominator) +} + +// PackSimplFracOutput attempts to pack given [outputStruct] of type SimplFracOutput +// to conform the ABI outputs. +func PackSimplFracOutput(outputStruct SimplFracOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("simplFrac", + outputStruct.SimplNum, + outputStruct.SimplDenom, + ) +} + +// UnpackSimplFracOutput attempts to unpack [output] as SimplFracOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackSimplFracOutput(output []byte) (SimplFracOutput, error) { + outputStruct := SimplFracOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "simplFrac", output) + + return outputStruct, err +} + +func simplFrac(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SimplFracGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the SimplFracInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSimplFracInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output SimplFracOutput // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackSimplFracOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createCalculatorplusPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. + +func createCalculatorplusPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "moduloPlus": moduloPlus, + "powOfThree": powOfThree, + "simplFrac": simplFrac, + } + + for name, function := range abiFunctionMap { + method, ok := CalculatorplusABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} +``` \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/03-unpacking-and-packing.mdx b/content/course/customizing-evm/10-calculator-precompile/03-unpacking-and-packing.mdx new file mode 100644 index 00000000..c94f3348 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/03-unpacking-and-packing.mdx @@ -0,0 +1,109 @@ +--- +title: Unpacking Multiple Inputs & Packing Multiple Outputs +description: Learn how to unpack multiple inputs and packing multiple outputs. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +## Unpack/Pack Functions For Each Operation + +As with the MD5 precompile, the generator created the pack/unpack functions for each operation of our Calculator and CalculatorPlus precompiles. However, you might have noticed that the generator also created some struct in each respective `contract.go` file. In the case of CalculatorPrecompile, we have: + +```go title="contract.go" +type AddInput struct { + Value1 *big.Int + Value2 *big.Int +} + +type NextTwoOutput struct { + Result1 *big.Int + Result2 *big.Int +} + +type RepeatInput struct { + Times *big.Int + Text string +} +``` + +The generator creates these structs whenever a function defined in the respective Solidity interface has more than one input or more than one output. These multiple values are now stored together via structs. + +## Unpacking Multiple Inputs + +To understand how unpacking works when structs are introduced, let's take a look at the add function in the Calculator precompile, which takes in two inputs, and the nextTwo, which takes in only one input. + +```go +// UnpackAddInput attempts to unpack [input] as AddInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackAddInput(input []byte) (AddInput, error) { + inputStruct := AddInput{} + err := CalculatorABI.UnpackInputIntoInterface(&inputStruct, "add", input) + return inputStruct, err +} + +// UnpackNextTwoInput attempts to unpack [input] into the *big.Int type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackNextTwoInput(input []byte) (*big.Int, error) { + res, err := CalculatorABI.UnpackInput("nextTwo", input) + if err != nil { + return big.NewInt(0), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} +``` + +As you can see, both unpacker functions take a byte array as an input but the `UnpackAddInput` returns a value of the type `AddInput` (which contains two `big.Int` values) and `UnpackNextTwoInput` returns a value of the type `big.Int`. + +In each case, the respective unpacking function takes in a byte array as an input. However, `UnpackAddInput` returns a value of the type `AddInput`, which is a struct that contains two `big.Int` values. `UnpackNextTwo`, meanwhile, returns an value of type `big.Int`. + +To demonstrate how one could use the output of unpacking functions like `UnpackAddInput`, below is the implementation of the add function of the Calculator precompile: + +```go +func add(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AddGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the AddInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackAddInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + var output *big.Int // CUSTOM CODE FOR AN OUTPUT + + output = big.NewInt(0).Add(inputStruct.Value1, inputStruct.Value2) + + packedOutput, err := PackAddOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + +## Packing Multiple Outputs + +To understand how structs affect the packing of outputs, lets refer to `PackNextTwoOutput` for the Calculator precompile: + +```go +// PackHashWithMd5Output attempts to pack given hash of type [16] +// PackNextTwoOutput attempts to pack given [outputStruct] of type NextTwoOutput +// to conform the ABI outputs. +func PackNextTwoOutput(outputStruct NextTwoOutput) ([]byte, error) { + return CalculatorABI.PackOutput("nextTwo", + outputStruct.Result1, + outputStruct.Result2, + ) +} +``` + +Notice here that even though we are no longer working with singular types, the generator still is able to pack our struct so that it is of the type bytes. + +As a result, we are able to return the packed version of any `NextTwoOutput` struct as bytes at the end of nextTwo. diff --git a/content/course/customizing-evm/10-calculator-precompile/04-implementing-precompile.mdx b/content/course/customizing-evm/10-calculator-precompile/04-implementing-precompile.mdx new file mode 100644 index 00000000..ee5bc6b9 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/04-implementing-precompile.mdx @@ -0,0 +1,593 @@ +--- +title: Implementing Precompile +description: Learn how to implement the precompile in `contract.go` +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In this section, we will go define the logic for our CalculatorPlus precompile; in particular, we want to add the logic for the following three functions: `powOfThree`, `moduloPlus`, and `simplFrac`. + +For those worried about this section - don't be! Our solution only added 12 lines of code to `contract.go`. + +## Looking at Calculator + +Before we define the logic of CalculatorPlus, we first will examine the implementation of the Calculator precompile: + +```go +func add(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AddGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the AddInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackAddInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output *big.Int // CUSTOM CODE FOR AN OUTPUT + + output = big.NewInt(0).Add(inputStruct.Value1, inputStruct.Value2) + + packedOutput, err := PackAddOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +// ... + +func repeat(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, RepeatGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the RepeatInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackRepeatInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output string // CUSTOM CODE FOR AN OUTPUT + + output = strings.Repeat(inputStruct.Text, int(inputStruct.Times.Int64())) + + packedOutput, err := PackRepeatOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// ... + +func nextTwo(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, NextTwoGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the NextTwoInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackNextTwoInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + + var output NextTwoOutput // CUSTOM CODE FOR AN OUTPUT + + output.Result1 = big.NewInt(0).Add(inputStruct, big.NewInt(1)) + output.Result2 = big.NewInt(0).Add(inputStruct, big.NewInt(2)) + + packedOutput, err := PackNextTwoOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + +Although the code snippet above may be long, you might notice that we added only four lines of code to the autogenerated code provided to us by Precompile-EVM! In particular, we only added lines 19, 48, 79, and 80. In general, note the following: + +- Structs vs Singular Values: make sure to keep track which inputs/outputs are structs and which one are values like big.Int. As an example, in add, we are dealing with a big.Int type input. However, in repeat, we are passed in a input of type struct RepeatInput. +- Documentation: for both Calculator and CalculatorPlus, the big package documentation is of great reference: https://pkg.go.dev/math/big + +Now that we have looked at the implementation for the Calculator precompile, its time you define the CalculatorPlus precompile! + +## Implementing moduloPlus + +We start by looking at the starter code for `moduloPlus`: + +```go +func moduloPlus(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ModuloPlusGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the ModuloPlusInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackModuloPlusInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output ModuloPlusOutput // CUSTOM CODE FOR AN OUTPUT + + packedOutput, err := PackModuloPlusOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + +We want to note the following: + +- `inputStruct` is the input that we want to work with (i.e. `inputStruct` contains the two numbers that we want to use for the modulo calculation) +- All of our code will go after line 15 +- We want the struct output to contain the result of our modulo operation (the struct will contain the multiple and remainder) + +With this in mind, try to implement `moduloPlus`. + + + + +```go +func moduloPlus(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {func moduloPlus(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ModuloPlusGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the ModuloPlusInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackModuloPlusInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output ModuloPlusOutput // CUSTOM CODE FOR AN OUTPUT + output.Multiple, output.Remainder = big.NewInt(0).DivMod(inputStruct.Dividend, inputStruct.Divisor, output.Remainder) + + packedOutput, err := PackModuloPlusOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + + +## Implementing powOfThree + +Likewise, for `powOfThree`, we want to define the logic of the function in the custom code section. However, note that while we are working with an output struct, our input is a singular value. With this in mind, take a crack at implementing `powOfThree`: + + + + +```go +func powOfThree(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {func powOfThree(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, PowOfThreeGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the PowOfThreeInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackPowOfThreeInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output PowOfThreeOutput // CUSTOM CODE FOR AN OUTPUT + + output.SecondPow = big.NewInt(0).Exp(inputStruct, big.NewInt(2), nil) + output.ThirdPow = big.NewInt(0).Exp(inputStruct, big.NewInt(3), nil) + output.FourthPow = big.NewInt(0).Exp(inputStruct, big.NewInt(4), nil) + + packedOutput, err := PackPowOfThreeOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + + +## Implementing simplFrac + +For implementing `simplFrac`, note the following: + +- The documentation for the big package will be of use here +- Remember to take care of the case when the denominator is 0 + + + + +```go +func simplFrac(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {func simplFrac(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SimplFracGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the SimplFracInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSimplFracInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output SimplFracOutput // CUSTOM CODE FOR AN OUTPUT + + // If denominator is 0, return both 0 + if inputStruct.Denominator.Cmp(big.NewInt(0)) == 0 { + output.SimplDenom = big.NewInt(0) + output.SimplNum = big.NewInt(0) + } else { + // First, find common denominator + var gcd big.Int + gcd.GCD(nil, nil, inputStruct.Numerator, inputStruct.Denominator) + + // Now, simplify fraction + output.SimplNum = big.NewInt(0).Div(inputStruct.Numerator, &gcd) + output.SimplDenom = big.NewInt(0).Div(inputStruct.Denominator, &gcd) + } + + packedOutput, err := PackSimplFracOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + + +## Final Solution + +Below is the final solution for the CalculatorPlus precompile: + + + + +```go +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package calculatorplus + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + ModuloPlusGasCost uint64 = 1 /* SET A GAS COST HERE */ + PowOfThreeGasCost uint64 = 1 /* SET A GAS COST HERE */ + SimplFracGasCost uint64 = 1 /* SET A GAS COST HERE */ +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = abi.JSON + _ = errors.New + _ = big.NewInt + _ = vmerrs.ErrOutOfGas + _ = common.Big0 +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + + // CalculatorplusRawABI contains the raw ABI of Calculatorplus contract. + //go:embed contract.abi + CalculatorplusRawABI string + + CalculatorplusABI = contract.ParseABI(CalculatorplusRawABI) + + CalculatorplusPrecompile = createCalculatorplusPrecompile() +) + +type ModuloPlusInput struct { + Dividend *big.Int + Divisor *big.Int +} + +type ModuloPlusOutput struct { + Multiple *big.Int + Remainder *big.Int +} + +type PowOfThreeOutput struct { + SecondPow *big.Int + ThirdPow *big.Int + FourthPow *big.Int +} + +type SimplFracInput struct { + Numerator *big.Int + Denominator *big.Int +} + +type SimplFracOutput struct { + SimplNum *big.Int + SimplDenom *big.Int +} + +// UnpackModuloPlusInput attempts to unpack [input] as ModuloPlusInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackModuloPlusInput(input []byte) (ModuloPlusInput, error) { + inputStruct := ModuloPlusInput{} + err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "moduloPlus", input) + + return inputStruct, err +} + +// PackModuloPlus packs [inputStruct] of type ModuloPlusInput into the appropriate arguments for moduloPlus. +func PackModuloPlus(inputStruct ModuloPlusInput) ([]byte, error) { + return CalculatorplusABI.Pack("moduloPlus", inputStruct.Dividend, inputStruct.Divisor) +} + +// PackModuloPlusOutput attempts to pack given [outputStruct] of type ModuloPlusOutput +// to conform the ABI outputs. +func PackModuloPlusOutput(outputStruct ModuloPlusOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("moduloPlus", + outputStruct.Multiple, + outputStruct.Remainder, + ) +} + +// UnpackModuloPlusOutput attempts to unpack [output] as ModuloPlusOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackModuloPlusOutput(output []byte) (ModuloPlusOutput, error) { + outputStruct := ModuloPlusOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "moduloPlus", output) + + return outputStruct, err +} + +func moduloPlus(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ModuloPlusGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the ModuloPlusInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackModuloPlusInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output ModuloPlusOutput // CUSTOM CODE FOR AN OUTPUT + output.Multiple, output.Remainder = big.NewInt(0).DivMod(inputStruct.Dividend, inputStruct.Divisor, output.Remainder) + + packedOutput, err := PackModuloPlusOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackPowOfThreeInput attempts to unpack [input] into the *big.Int type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackPowOfThreeInput(input []byte) (*big.Int, error) { + res, err := CalculatorplusABI.UnpackInput("powOfThree", input) + if err != nil { + return new(big.Int), err + } + unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) + return unpacked, nil +} + +// PackPowOfThree packs [base] of type *big.Int into the appropriate arguments for powOfThree. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackPowOfThree(base *big.Int) ([]byte, error) { + return CalculatorplusABI.Pack("powOfThree", base) +} + +// PackPowOfThreeOutput attempts to pack given [outputStruct] of type PowOfThreeOutput +// to conform the ABI outputs. +func PackPowOfThreeOutput(outputStruct PowOfThreeOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("powOfThree", + outputStruct.SecondPow, + outputStruct.ThirdPow, + outputStruct.FourthPow, + ) +} + +// UnpackPowOfThreeOutput attempts to unpack [output] as PowOfThreeOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackPowOfThreeOutput(output []byte) (PowOfThreeOutput, error) { + outputStruct := PowOfThreeOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "powOfThree", output) + + return outputStruct, err +} + +func powOfThree(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, PowOfThreeGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the PowOfThreeInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackPowOfThreeInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output PowOfThreeOutput // CUSTOM CODE FOR AN OUTPUT + + output.SecondPow = big.NewInt(0).Exp(inputStruct, big.NewInt(2), nil) + output.ThirdPow = big.NewInt(0).Exp(inputStruct, big.NewInt(3), nil) + output.FourthPow = big.NewInt(0).Exp(inputStruct, big.NewInt(4), nil) + + packedOutput, err := PackPowOfThreeOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackSimplFracInput attempts to unpack [input] as SimplFracInput +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackSimplFracInput(input []byte) (SimplFracInput, error) { + inputStruct := SimplFracInput{} + err := CalculatorplusABI.UnpackInputIntoInterface(&inputStruct, "simplFrac", input) + + return inputStruct, err +} + +// PackSimplFrac packs [inputStruct] of type SimplFracInput into the appropriate arguments for simplFrac. +func PackSimplFrac(inputStruct SimplFracInput) ([]byte, error) { + return CalculatorplusABI.Pack("simplFrac", inputStruct.Numerator, inputStruct.Denominator) +} + +// PackSimplFracOutput attempts to pack given [outputStruct] of type SimplFracOutput +// to conform the ABI outputs. +func PackSimplFracOutput(outputStruct SimplFracOutput) ([]byte, error) { + return CalculatorplusABI.PackOutput("simplFrac", + outputStruct.SimplNum, + outputStruct.SimplDenom, + ) +} + +// UnpackSimplFracOutput attempts to unpack [output] as SimplFracOutput +// assumes that [output] does not include selector (omits first 4 func signature bytes) +func UnpackSimplFracOutput(output []byte) (SimplFracOutput, error) { + outputStruct := SimplFracOutput{} + err := CalculatorplusABI.UnpackIntoInterface(&outputStruct, "simplFrac", output) + + return outputStruct, err +} + +func simplFrac(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SimplFracGasCost); err != nil { + return nil, 0, err + } + // attempts to unpack [input] into the arguments to the SimplFracInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSimplFracInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + var output SimplFracOutput // CUSTOM CODE FOR AN OUTPUT + + // If denominator is 0, return both 0 + if inputStruct.Denominator.Cmp(big.NewInt(0)) == 0 { + output.SimplDenom = big.NewInt(0) + output.SimplNum = big.NewInt(0) + } else { + // First, find common denominator + var gcd big.Int + gcd.GCD(nil, nil, inputStruct.Numerator, inputStruct.Denominator) + + // Now, simplify fraction + output.SimplNum = big.NewInt(0).Div(inputStruct.Numerator, &gcd) + output.SimplDenom = big.NewInt(0).Div(inputStruct.Denominator, &gcd) + } + + packedOutput, err := PackSimplFracOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createCalculatorplusPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. + +func createCalculatorplusPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "moduloPlus": moduloPlus, + "powOfThree": powOfThree, + "simplFrac": simplFrac, + } + + for name, function := range abiFunctionMap { + method, ok := CalculatorplusABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} +``` + + + diff --git a/content/course/customizing-evm/10-calculator-precompile/05-set-configkey-contractaddr.mdx b/content/course/customizing-evm/10-calculator-precompile/05-set-configkey-contractaddr.mdx new file mode 100644 index 00000000..0c265347 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/05-set-configkey-contractaddr.mdx @@ -0,0 +1,19 @@ +--- +title: Setting the ConfigKey & ContractAddress +description: Learn how to set the ConfigKey, ContractAddress and register the Precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +## ConfigKey + +Just as with the MD5 precompile in the previous section, go to the `module.go` file and set a `ConfigKey`. + +## Contract Address + +In the same file, set a `ContractAddress`. Choose one that has not been used by other precompiles of earlier sections. + +## Registration + +Go to the `plugin/main.go` file and register the new precompile. \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/06-create-genesis-block.mdx b/content/course/customizing-evm/10-calculator-precompile/06-create-genesis-block.mdx new file mode 100644 index 00000000..2b554342 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/06-create-genesis-block.mdx @@ -0,0 +1,72 @@ +--- +title: Creating Genesis Block with precompileConfig +description: Learn how to create a genesis block with precompileConfig. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In order to create a new Blockchain from our customized EVM, we have to define a Genesis block just as we did in out section earlier. + +To incorporate the CalculatorPlus precompile as part of the genesis block, we need to create a new genesis file and include a key for the CalculatorPlus precompile in the configuration JSON. This key, in particular, is the `configKey` that we specified in the `module.go` file of the Calculator precompile in the previous section. + +Copy over `Calculator.json` into a new file called `CalculatorPlus.json` and add the necessary key-pair value to the config. + + + + +```json title="CalculatorPlus.json" +{ + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "sha256Config": { + "blockTimestamp": 0 + }, + "calculatorConfig": { + "blockTimestamp": 0 + }, + "calculatorplusConfig" : { + "blockTimestamp": 0 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/07-testing-precompile.mdx b/content/course/customizing-evm/10-calculator-precompile/07-testing-precompile.mdx new file mode 100644 index 00000000..2c765103 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/07-testing-precompile.mdx @@ -0,0 +1,25 @@ +--- +title: Testing Precompiles via Go +description: Learn how to test your Precompiles using Golang. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +> Program testing can be used to show the presence of bugs, but never to show their absence. - Edsger W. Dijkstra + +Let's once again examine the functions of the Calculator precompile and what each one does (in layman terms): + +- `add`: computes the sum of two numbers +- `nextTwo`: returns the next two numbers that come after the input given +- `repeat`: repeats a string N number of times + +While we spent a good amount of time in the previous sections examining Calculator Solidity interface and the Go logic, we forgot to do one thing: *test the functionality of Calculator*. + +But why test? For some, it may seems pointless to test such basic functions. However, testing all functions is important because it helps validate our belief that the logic of our functions is what we intended them to be. + +In this section, we will focus on utilizing three types of tests for our Calculator precompile: + +1. Autogenerated Tests +2. Unit Tests +3. Fuzz Tests \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/08-autogenerated-tests.mdx b/content/course/customizing-evm/10-calculator-precompile/08-autogenerated-tests.mdx new file mode 100644 index 00000000..be092489 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/08-autogenerated-tests.mdx @@ -0,0 +1,172 @@ +--- +title: Modify Autogenerated Tests +description: Learn how to modify autogenerated tests in Go. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +The autogenerated tests will help us making sure, that our precompile throw an error in case not enough gas is supplied. To start, go to calculator/contract_test.go, where you will see the following: + +```go +// Code generated +// This file is a generated precompile contract test with the skeleton of test functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +package calculator + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// These tests are run against the precompile contract directly with +// the given input and expected output. They're just a guide to +// help you write your own tests. These tests are for general cases like +// allowlist, readOnly behaviour, and gas cost. You should write your own +// tests for specific cases. +var ( + tests = map[string]testutils.PrecompileTest{ + "insufficient gas for add should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := AddInput{} + input, err := PackAdd(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: AddGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for nextTwo should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + var testInput *big.Int + input, err := PackNextTwo(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: NextTwoGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for repeat should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := RepeatInput{} + input, err := PackRepeat(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: RepeatGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } +) + +// TestCalculatorEmptyRun tests the Run function of the precompile contract. +func TestCalculatorEmptyRun(t *testing.T) { + // Run tests. + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Run(t, Module, state.NewTestStateDB(t)) + }) + } +} + +func BenchmarkCalculatorEmpty(b *testing.B) { + // Benchmark tests. + for name, test := range tests { + b.Run(name, func(b *testing.B) { + test.Bench(b, Module, state.NewTestStateDB(b)) + }) + } +} +``` + +There is a lot to digest in `contract_test.go`, but the file can be divided into the following three sections: + +- Unit Tests +- TestCalculatorEmptyRun +- BenchmarkCalculator + +The autogenerated unit tests are stored in the variable var, which is a mapping from strings (the description of the tests cases) to individual unit tests (`testutils.PrecompileTest`). Upon inspecting the keys of each pair, you will see that all three autogenerated unit tests are checking the same thing that: `add`, `nextTwo`, and `repeat` fail if not enough gas is provided. Each test expects to fail when supplying too little gas: + +```go +{ + // ... + SuppliedGas: RepeatGasCost - 1, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), +} +``` + +As of currently, if you attempt to build and run the test cases, you will not get very far because there is one step we need to do to: pass in arguments for each autogenerated unit test. For each variable testInput defined in each unit test, add an argument. What argument to put down does not matter, it just needs to be a valid argument. An example of what arguments to put in can be found below: + +```go +var ( + tests = map[string]testutils.PrecompileTest{ + "insufficient gas for add should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := AddInput{big.NewInt(1), big.NewInt(1)} + input, err := PackAdd(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: AddGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for nextTwo should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + // var testInput *big.Int + testInput := big.NewInt(1) + input, err := PackNextTwo(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: NextTwoGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for repeat should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := RepeatInput{big.NewInt(1), "EGS"} + input, err := PackRepeat(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: RepeatGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } +) +``` + +Now run the test by running the following command from the project root: + +```bash +./scripts/build_test.sh +``` \ No newline at end of file diff --git a/content/course/customizing-evm/10-calculator-precompile/09-unit-tests.mdx b/content/course/customizing-evm/10-calculator-precompile/09-unit-tests.mdx new file mode 100644 index 00000000..c6680fe1 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/09-unit-tests.mdx @@ -0,0 +1,110 @@ +--- +title: Adding Unit Tests +description: Learn how to add unit tests. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Although the autogenerated units tests were useful in finding whether if the gas requirements of our functions were being fulfilled, we are yet to test the actual logic of our functions. We can add more unit tests by simply adding more mappings to the tests variable. + +To start, lets define the tests that we want to write for each function within our Calculator precompile: + +- `add`: check that **add(1, 2)** returns 3 +- `nextTwo`: check that **nextTwo(1)** returns 2, 3 +- `repeat`: check that **repeat(2, "EGS")** returns "EGSEGS" + +With this in mind, lets add the three units tests to the tests variable! Below is a code excerpt which shows the three unit tests incorporated: + +```go +var ( + expectedNextTwoOutcome, _ = PackNextTwoOutput(NextTwoOutput{big.NewInt(2), big.NewInt(3)}) + expectedRepeatOutcome, _ = PackRepeatOutput("EGSEGS") + expectedAddOutcome = common.LeftPadBytes(big.NewInt(3).Bytes(), common.HashLength) + + tests = map[string]testutils.PrecompileTest{ + "insufficient gas for add should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := AddInput{big.NewInt(1), big.NewInt(1)} + input, err := PackAdd(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: AddGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for nextTwo should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // set test input to a value here + // var testInput *big.Int + testInput := big.NewInt(1) + input, err := PackNextTwo(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: NextTwoGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas for repeat should fail": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + // CUSTOM CODE STARTS HERE + // populate test input here + testInput := RepeatInput{big.NewInt(1), "EGS"} + input, err := PackRepeat(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: RepeatGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "testing add": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + value1 := big.NewInt(1) + value2 := big.NewInt(2) + testInput := AddInput{value1, value2} + input, err := PackAdd(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: AddGasCost, + ReadOnly: true, + ExpectedRes: expectedAddOutcome, + }, + "testing nextTwo": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + testInput := big.NewInt(1) + input, err := PackNextTwo(testInput) + require.NoError(t, err) + return input + }, + SuppliedGas: NextTwoGasCost, + ReadOnly: true, + ExpectedRes: expectedNextTwoOutcome, + }, + "testing repeat": { + Caller: common.Address{1}, + InputFn: func(t testing.TB) []byte { + baseString := "EGS" + timesToRepeat := big.NewInt(2) + input, err := PackRepeat(RepeatInput{timesToRepeat, baseString}) + require.NoError(t, err) + return input + }, + SuppliedGas: RepeatGasCost, + ReadOnly: true, + ExpectedRes: expectedRepeatOutcome, + }, + } +) +``` diff --git a/content/course/customizing-evm/10-calculator-precompile/10-fuzz-tests.mdx b/content/course/customizing-evm/10-calculator-precompile/10-fuzz-tests.mdx new file mode 100644 index 00000000..1aced6d7 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/10-fuzz-tests.mdx @@ -0,0 +1,80 @@ +--- +title: Adding Fuzz Tests +description: Learn how to add Fuzz tests. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Units tests are useful when comparing the outcome of a predetermined input with the expected outcome. If we wanted to check that our function works as expected for all given inputs, the only way to show this would be to test every possible input. + +However, this is not possible for types such as uint256, as it would require us to test for 2^256 possible inputs. Even if we tested 1 input per second (or 2, 3, 4, 1000, etc.), this process would outlast the universe itself. + +Rather than testing every possible output, we can strengthen our confidence of the expected behavior of a function by testing a random sample of inputs. By testing randomly, we can test for inputs that we wouldn't have originally thought of and are able to see how our function behaves over a range of values. + +However, given that generating random inputs is not deterministic, we cannot use the tests variable itself. Rather, we will need to leverage the `TestCalculatorRun` function: + +```go +// TestCalculatorEmptyRun tests the Run function of the precompile contract. +func TestCalculatorRun(t *testing.T) { + // Run tests. + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Run(t, Module, state.NewTestStateDB(t)) + }) + } +} +``` + +The `testCalculatorRun` function, by default, iterates through each unit test defined in the tests variable on runs said tests. However, we are not limited to just using `TestCalculatorRun` for the unit tests in tests. We can expand the functionality of `TestCalculatorRun` by adding our fuzz tests. + +In particular, we can define the following logic for `TestCalculatorRun`: + +- Iterate N number of times +- For each iteration, pick two numbers in the range from 0 to n +- After picking two random numbers, create a unit test with the two random numbers as inputs and execute said unit test + +With this logic in mind, below is the code for how we would go about implementing fuzzing in `TestCalculatorRun`: + +```go +// TestCalculatorRun tests the Run function of the precompile contract. +func TestCalculatorRun(t *testing.T) { + // Run tests. + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Run(t, Module, state.NewTestStateDB(t)) + }) + } + // Defining own test cases here + N := 1_000 + n := new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(128)), nil) + + // Fuzzing N times + for i := 0; i < N; i++ { + // Adding randomization test here + randomInt1, err := rand.Int(rand.Reader, n) + randomInt2, err := rand.Int(rand.Reader, n) + // Expected outcome + expectedRandOutcome := common.LeftPadBytes(big.NewInt(0).Add(randomInt1, randomInt2).Bytes(), common.HashLength) + + // Pack add input + randTestInput := AddInput{randomInt1, randomInt2} + randInput, err := PackAdd(randTestInput) + require.NoError(t, err) + + randTest := testutils.PrecompileTest{ + Caller: common.Address{1}, + Input: randInput, + SuppliedGas: AddGasCost, + ReadOnly: true, + ExpectedRes: expectedRandOutcome, + } + + t.Run("Testing random sum!", func(t *testing.T) { + randTest.Run(t, Module, state.NewTestStateDB(t)) + }) + + } + +} +``` diff --git a/content/course/customizing-evm/10-calculator-precompile/11-test-calculatorplus.mdx b/content/course/customizing-evm/10-calculator-precompile/11-test-calculatorplus.mdx new file mode 100644 index 00000000..d4e6ae84 --- /dev/null +++ b/content/course/customizing-evm/10-calculator-precompile/11-test-calculatorplus.mdx @@ -0,0 +1,20 @@ +--- +title: Testing CalculatorPlus +description: Learn how to test CalculatorPlus precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +Now that we have gone through an example of how to test the Calculator precompile, it is time that you develop your own tests for CalculatorPlus. + +Rather than explicitly tell you what test cases to write, we decided to leave that to you. After all, testing is a subjective profession - a test suite that fits one security needs might be inadequate for someone else. + +However, if you want an idea for what to test for, the following are some recommendations: + +- Unit Tests: create some unit tests involving values that you can come up with. Ideally, write 3-5 unit tests for each function. +- Fuzz Tests: test more than 1000 random inputs for each function + +For both unit and fuzz tests, make sure to account for the case when the denominator is equal to 0 in `simplFrac`. \ No newline at end of file diff --git a/content/course/customizing-evm/11-stateful-precompiles/00-intro.mdx b/content/course/customizing-evm/11-stateful-precompiles/00-intro.mdx new file mode 100644 index 00000000..42909315 --- /dev/null +++ b/content/course/customizing-evm/11-stateful-precompiles/00-intro.mdx @@ -0,0 +1,142 @@ +--- +title: What are Stateful Precompiles? +description: Learn about Stateful Precompiles in Subnet EVM. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Box +--- + +When building the MD5 and Calculator precompiles, we emphasized their behavior. We focused on building precompiles that developers could call in Solidity to perform some algorithm and then simply return a result. + +However, one aspect that we have yet to explore is the statefulness of precompiles. Simply put, precompiles can store data which is persistent. To understand how this is possible, recall the interface that our precompile needed to implement: + +```go +// StatefulPrecompiledContract is the interface for executing a precompiled contract +type StatefulPrecompiledContract interface { + // Run executes the precompiled contract. + Run(accessibleState AccessibleState, + caller common.Address, + addr common.Address, + input []byte, + suppliedGas uint64, + readOnly bool) + (ret []byte, remainingGas uint64, err error) +} +``` + +We also examined all parameters except for the `AccessibleState` parameter. As the name suggests, this parameter lets us access the blockchain's state. Looking at the interface of `AccessibleState`, we have the following: + +```go +// AccessibleState defines the interface exposed to stateful precompile contracts +type AccessibleState interface { + GetStateDB() StateDB + GetBlockContext() BlockContext + GetSnowContext() *snow.Context +} +``` + +Looking closer, we see that `AccessibleState` gives us access to **StateDB**, which is used to store the state of the EVM. However, as we will see throughout this section, `AccessibleState` also gives us access to other useful parameters, such as `BlockContext` and `snow.Context`. + +## StateDB + +The parameter we will use the most when it comes to stateful precompiles, StateDB, is a key-value mapping that maps: + +- **Key**: a tuple consisting of an address and the storage key of the type Hash +- **Value**: any data encoded in a Hash, also called a word in the EVM + +A Hash in go-ethereum is a 32-byte array. In this context, we do not refer to hashing in the cryptographic sense. Rather, "hashing a value" means encoding it to a hash, a 32-byte array usually represented in hexadecimal digits in Ethereum. + +```go +const ( + // HashLength is the expected length of the hash + HashLength = 32 +) + +// Hash represents the 32 byte of arbitrary data. +type Hash [HashLength]byte + +// Example of a data encoded in a Hash: +// 0x00000000000000000000000000000000000000000048656c6c6f20576f726c64 +Below is the interface of StateDB: +// StateDB is the interface for accessing EVM state +type StateDB interface { + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SetNonce(common.Address, uint64) + GetNonce(common.Address) uint64 + + GetBalance(common.Address) *big.Int + AddBalance(common.Address, *big.Int) + + CreateAccount(common.Address) + Exist(common.Address) bool + + AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) + GetPredicateStorageSlots(address common.Address) ([]byte, bool) + + Suicide(common.Address) bool + Finalise(deleteEmptyObjects bool) + + Snapshot() int + RevertToSnapshot(int) +} +``` + +As you can see in the interface of the **StateDB**, the two functions for writing to and reading from the EVM state all work with the Hash type. + +1. The function **GetState** takes an address and a Hash (the storage key) as inputs and returns the Hash stored at that slot. +2. The function **SetState** takes an address, a Hash (the storage key), and another Hash (data to be stored) as inputs. + +We can also see that the **StateDB** interface allows us to read and write account balances of the native token (via GetBalance/SetBalance), check whether an account exists (via Exist), and some other methods. + +## BlockContext + +`BlockContext` provides info about the current block. In particular, we can get the current block number and the current block timestamp. Below is the interface of `BlockContext`: + +```go +// BlockContext defines an interface that provides information to a stateful precompile about the current block. +// The BlockContext may be provided during both precompile activation and execution. +type BlockContext interface { + Number() *big.Int + Timestamp() *big.Int +} +``` + +An example of how we could leverage `BlockContext` in precompiles: *building a voting precompile that only accepts votes within a certain time frame*. + +## snow.Context + +`snow.Context` gives us info regarding the environment of the precompile. In particular, `snow.Context` tells us about the state of the network the precompile is hosted on. Below is the interface of `snow.Context`: + +```go +// Context is information about the current execution. +// [NetworkID] is the ID of the network this context exists within. +// [ChainID] is the ID of the chain this context exists within. +// [NodeID] is the ID of this node +type Context struct { + NetworkID uint32 + SubnetID ids.ID + ChainID ids.ID + NodeID ids.NodeID + PublicKey *bls.PublicKey + + XChainID ids.ID + CChainID ids.ID + AVAXAssetID ids.ID + + Log logging.Logger + Lock sync.RWMutex + Keystore keystore.BlockchainKeystore + SharedMemory atomic.SharedMemory + BCLookup ids.AliaserReader + Metrics metrics.OptionalGatherer + + WarpSigner warp.Signer + + // snowman++ attributes + ValidatorState validators.State // interface for P-Chain validators + // Chain-specific directory where arbitrary data can be written + ChainDataDir string +} +``` \ No newline at end of file diff --git a/content/course/customizing-evm/11-stateful-precompiles/01-interacting-with-precompile.mdx b/content/course/customizing-evm/11-stateful-precompiles/01-interacting-with-precompile.mdx new file mode 100644 index 00000000..c87a7672 --- /dev/null +++ b/content/course/customizing-evm/11-stateful-precompiles/01-interacting-with-precompile.mdx @@ -0,0 +1,9 @@ +--- +title: Interacting with StringStore Precompile (draft) +description: Learn how to interact with StringStore Stateful precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +TBD \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/00-intro.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/00-intro.mdx new file mode 100644 index 00000000..18451f3e --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/00-intro.mdx @@ -0,0 +1,31 @@ +--- +title: Creating Counter Precompile +description: Learn how to create a Stateful Counter Precompiles in Subnet EVM. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Box +--- + +## What We Are Building + +It's time to build our first stateful precompile. In particular, we'll build a counter that keeps track of an integer. Our Counter precompile will have the following logic: + +- Users are able to get the current value of the counter +- Users are able to set a new value to the counter +- Users are able to increment the value of the counter + +To help you understand, we've provided a reference stateful precompile: **StringStore**. As the name suggests, StringStore stores a string that users can change. This string is stored in the EVM state. + +## Overview of Steps + +Compared to the process before, we must also add tests for our precompile. Here's a quick overview of the steps we'll follow: + +1. Create a Solidity interface for the precompile and generate the AB +2. Generate the precompile Go boilerplate files +3. Write the precompile code in Go with access to the EVM state +4. Set the initial state in the configurator and register the precompile +5. Add and run tests +6. Build and run your customized EVM +7. Connect Remix to your customized EVM and interact with the counter + +This tutorial will help you create more complex precompiles. Let's begin! \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/01-create-solidity-interface.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/01-create-solidity-interface.mdx new file mode 100644 index 00000000..104f4671 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/01-create-solidity-interface.mdx @@ -0,0 +1,74 @@ +--- +title: Create Solidity Interface +description: Learn how to create a solidity interface for your counter precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Now, we'll create a Solidity interface for our Stateful Precompile. + +## StringStore Solidity Interface + +To start, let's look at the interface of the **StringStore** precompile: + +```solidity title="contracts/contracts/interfaces/IStringStore.sol" +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface IStringStore { + function getString() external view returns (string memory value); + function setString(string memory value) external; +} +``` + +As seen above, we have the following two functions defined: + +1. `getString`: retreives the string from the stateful precompile +2. `setString`: sets a string in the stateful precompile + +## Create Solidity Interface for Counter + +Create an interface for the counter in the same directory called `ICounter.sol`. Your interface should have the following three functions declared: + +1. `getCounter`: returns the counter value from the stateful precompile +2. `incrementCounter`: when called, this function increments the current counter +3. `setCounter`: takes in a value, and sets it as the counter of the stateful precompile + +For any argument/return value, make sure it is named as `value`. + + + + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +interface ICounter { + + function getCounter() external view returns (uint value); + function incrementCounter() external; + function setCounter(uint value) external; + +} +``` + + + + +## Generate the ABI + +Now that we have an interface of our precompile, let's create an ABI of our Solidity interface. Open the terminal (control + `), change to the `/contracts` directory, and run the command to compile the solidity interface to ABI: + +```bash +# Move to contracts directory +cd contracts + +# Compile ICounter.sol to ABI +npx solc@latest --abi ./contracts/interfaces/ICounter.sol -o ./abis --base-path . --include-path ./node_modules + +# Rename +mv ./abis/contracts_interfaces_ICounter_sol_ICounter.abi ./abis/ICounter.abi +``` \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/02-store-data-in-evm.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/02-store-data-in-evm.mdx new file mode 100644 index 00000000..e7260368 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/02-store-data-in-evm.mdx @@ -0,0 +1,142 @@ +--- +title: Store Data in EVM State +description: Learn how to store data in the EVM state. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +Like all stateful machines, the EVM provides us with a way to save data. In particular, the EVM exposes a key-value mapping we can leverage to create stateful precompiles. The specifics of this mapping are as follows: + +- Key: a tuple consisting of an address and the storage key of the type Hash +- Value: any data encoded in a Hash, also called a word in the EVM + +## Storage Slots + +Each storage slot is uniquely identified by the combination of an address and a storage key. To keep things organized, smart contracts and precompiles use their own address and a storage key to store data related to them. + +Look at the reference implementation `StringStore`. The storage key is defined in the second variable group in `contract.go`: + +```go +// Singleton StatefulPrecompiledContract and signatures. +var ( + + // StringStoreRawABI contains the raw ABI of StringStore contract. + //go:embed contract.abi + StringStoreRawABI string + + StringStoreABI = contract.ParseABI(StringStoreRawABI) + + StringStorePrecompile = createStringStorePrecompile() + + // Key that defines where our string will be stored + storageKeyHash = common.BytesToHash([]byte("storageKey")) +) +``` + +We can use any string as our storage key. Then we convert it to a byte array and convert it to the hex representation. + +> The common.BytesToHash is not hashing the string, but converting it to a 32 byte array in hex representation. + +If we store multiple variables in the EVM state, we will define multiple keys here. Since we only use a single storage slot in this example, we can just call it `storageKey`. + +At this point, we are not restricted in what state we can access from the precompile. We have access to the entire `stateDB` and can modify the entire EVM state including data other precompiles saved to state or balances of accounts. **This is very powerful, but also potentially dangerous**. + +## Converting the Value to Hash Type + +Since the StateDB only stores data of the type *Hash*, we need to convert all values to the type *Hash* before passing it to the **stateDB**. + +### Converting Numbers + +To convert numbers of type `big.Int`, we can utilize a function from the common package: + +```go +valueHash := common.BigToHash(value) +``` + +Since the `big.Int` data structure already is 32-bytes long, the conversion is straightforward. + +```go +// BigToHash sets byte representation of b to hash. +// If b is larger than len(h), b will be cropped from the left. +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } +``` + +### Converting Strings + +Converting strings is more challenging, since they are variable in length. Let's see an example: + +```go +input := "Hello World" +``` + +To start, let's convert input into type bytes: + +```go +inputAsBytes := []byte(input) +// [72 101 108 108 111 32 87 111 114 108 100] +``` + +This is how you would convert input to type bytes in Go. Notice that the comment in the code snippet is the byte-representation of input, where each integer represents a byte. Right now, inputAsBytes is of length 11. We want it to be of length 32. Therefore, we pad `inputAsBytes`, adding however many zeros to the front until `inputAsBytes` has 32 integers: + +```go +inputPadded := common.LeftPadBytes(inputAsBytes, common.HashLength) +// [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 101 108 108 111 32 87 111 114 108 100] +``` + +In Go, we would do that using the function common.LeftPadBytes, which takes the byte array to be padded and the desired length. The desired length is supplied with the common.HashLength variable, which has the value 32. As seen in the comment, inputPadded is now of length 32. + +In the next step, we convert the bytes to a Hash with the function `common.BytesToHash`: + +```go +inputHash := common.BytesToHash(inputPadded) +// 0x00000000000000000000000000000000000000000048656c6c6f20576f726c64 +``` + +## Helper Functions + +To make the code reusable and keep the precompile function clean, it makes sense to create helper functions for converting and storing the data. The first one we'll see is StoreString: + +```go +// StoreString sets the value of the storage key "storageKey" in the contract storage. +func StoreString(stateDB contract.StateDB, newValue string) { + newValuePadded := common.LeftPadBytes([]byte(newValue), common.HashLength) + newValueHash := common.BytesToHash(newValuePadded) + stateDB.SetState(ContractAddress, storageKeyHash, newValueHash) +} +``` + +`StoreString` takes in the underlying key-value mapping, known as **StateDB**, and the new value, and updates the current string stored to be the new one. `StoreString` takes care of any type conversions and state management for us. Focusing now on `setString`, defining the logic is relatively easy. All we need to do is pass in **StateDB** and our string to `StoreString`. + +Thus, we have the following: + +```go +func setString(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetStringGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // attempts to unpack [input] into the arguments to the SetStringInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSetStringInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + // Get K-V Mapping + currentState := accessibleState.GetStateDB() + + // Set the value + StoreString(currentState, inputStruct) + + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/03-implement-set-counter.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/03-implement-set-counter.mdx new file mode 100644 index 00000000..84e1882c --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/03-implement-set-counter.mdx @@ -0,0 +1,64 @@ +--- +title: Implementing setCounter +description: Learn how to implement the setCounter method. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Having seen how strings are stored in `StringStore`, its time for us to store integers with Counter. The thought process for this section can be defined as follows: + +- Define the storage hash for our counter +- Implement `StoreCounterValue`, a helper function which acts like `StoreString` in the previous +- Implement `setCounter` + + + + +```go title="contract.go" +// storageKeyHash +storageKeyHash = common.BytesToHash([]byte("counterValue")) + +// StoreGreeting sets the value of the storage key in the contract storage. +func StoreCounterValue(stateDB contract.StateDB, value *big.Int) { + // Convert uint to left padded bytes + inputPadded := common.LeftPadBytes(value.Bytes(), 32) + inputHash := common.BytesToHash(inputPadded) + + stateDB.SetState(ContractAddress, storageKeyHash, inputHash) +} + +//setCounter sets the counter value in the contract storage. +func setCounter(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetCounterGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // attempts to unpack [input] into the arguments to the SetCounterInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSetCounterInput(input) + if err != nil { + return nil, remainingGas, err + } + + // CUSTOM CODE STARTS HERE + + // Get the current state + currentState := accessibleState.GetStateDB() + + // Set the value + StoreCounterValue(currentState, inputStruct) + + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/04-read-date-from-evm.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/04-read-date-from-evm.mdx new file mode 100644 index 00000000..738f5ed9 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/04-read-date-from-evm.mdx @@ -0,0 +1,51 @@ +--- +title: Read Data From EVM State +description: Learn how to read the data from EVM state. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +In the section about storing data in the EVM, we learned about how to store our string in the EVM State. An equally important skill is how to read data from the EVM state. + +In this section, we'll learn about how to retrieve our string from the EVM state. + +## Defining Helper Function + +Just like with setting the string in the EVM state, there are some conversions we'll have to perform to our string. In particular, we'll need to unhash the value stored in StateDB to get our original string. Thus, our helper function can be defined as follows: + +```go +// GetString returns the value of the storage key "storageKey" in the contract storage, +// with leading zeroes trimmed. +func GetString(stateDB contract.StateDB) string { + // Get the value set at recipient + value := stateDB.GetState(ContractAddress, storageKeyHash) + return string(common.TrimLeftZeroes(value.Bytes())) +} +``` + +With our helper function defined, we can implement the logic for `getString`. This will consists of retrieving the underlying key-value mapping and then passing it to `GetString`. + +```go +func getString(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetStringGasCost); err != nil { + return nil, 0, err + } + // no input provided for this function + + // CUSTOM CODE STARTS HERE + + var output string // CUSTOM CODE FOR AN OUTPUT + + currentState := accessibleState.GetStateDB() + output = GetString(currentState) + + packedOutput, err := PackGetStringOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/05-implement-getcounter-increment.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/05-implement-getcounter-increment.mdx new file mode 100644 index 00000000..787ebe92 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/05-implement-getcounter-increment.mdx @@ -0,0 +1,95 @@ +--- +title: Implementing getCounter & increment +description: Learn how to implement getCounter and increment. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Having seen how to retrieve strings from the EVM state with the StoreString precompile, we are now ready to implement `getCounter`. Also, now that we are familiar with reading and writing to the EVM state, we can implement increment, which requires both read and write operations. + +## Implementing getCounter + +For `getCounter`, the following thought process is helpful: + +- Create a helper function `GetCounterValue`, which takes in the current StateDB and returns the integer stored at the `storageKeyHash` +- In `getCounter`, get the current StateDB and pass it to `GetCounterValue` + + + + +```go +// GetCounterValue gets the value of the storage key in the contract storage. +func GetCounterValue(stateDB contract.StateDB) *big.Int { + // Get the value + value := stateDB.GetState(ContractAddress, storageKeyHash) + + // Convert bytes to uint + return new(big.Int).SetBytes(value.Bytes()) +} + +func getCounter(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetCounterGasCost); err != nil { + return nil, 0, err + } + // no input provided for this function + + // Get the current state + currentState := accessibleState.GetStateDB() + // Get the value set at recipient + value := GetCounterValue(currentState) + + packedOutput, err := PackGetCounterOutput(value) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + + +## Implementing increment + +For increment, the following thought process is helpful: + +- Get the current StateDB and pass it to GetCounterValue +- Once you have the current counter, increment it by one +- Store the new counter with StoreCounterValue + + + + +```go +func incrementCounter(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, IncrementCounterGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // CUSTOM CODE STARTS HERE + // Get the current state + currentState := accessibleState.GetStateDB() + + // Get the value of the counter + value := GetCounterValue(currentState) + + // Set the value + StoreCounterValue(currentState, value.Add(value, big.NewInt(1))) + + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} +``` + + + \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/06-setting-base-gasfees.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/06-setting-base-gasfees.mdx new file mode 100644 index 00000000..608030af --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/06-setting-base-gasfees.mdx @@ -0,0 +1,29 @@ +--- +title: Setting Base Gas Fees of Your Precompile +description: Learn how to set the base gas fees of your precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +Gas is used for spam prevention. If our precompile is accessible to everyone on our Subnet, it is important to set the gas cost in a way that lets users economically utilize it in their apps, yet prevents spammers from spamming our blockchain with calls to the precompile. + +In this section, we'll modify the default values of the gas costs of our Calculator precompile. By default, all user-defined functions have a gas cost of 1. While this is good news for the average user, allowing users to call computationally expensive functions can leave our blockchain vulnerable to Denial-of-Service (DoS) attacks. + +We can find the default gas cost values for our functions in calculator/contract.go. Following the import statements, you should see: + +```go title-"calculator/contract.go" +const ( + // Gas costs for each function. These are set to 1 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + AddGasCost uint64 = 1 /* SET A GAS COST HERE */ + NextTwoGasCost uint64 = 1 /* SET A GAS COST HERE */ + RepeatGasCost uint64 = 1 /* SET A GAS COST HERE */ +) +``` + +The variables `AddGasCost`, `NextTwoGasCost`, and `RepeatGasCost` are the gas costs of the `add`, `nextTwo`, and `repeat` functions, respectively. As you can see, they are currently set to 1. + +However, changing the gas cost is as easy as changing the values themselves. As an example, change the gas costs to 7. diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/07-set-configkey-contractaddr.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/07-set-configkey-contractaddr.mdx new file mode 100644 index 00000000..497b3994 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/07-set-configkey-contractaddr.mdx @@ -0,0 +1,15 @@ +--- +title: Setting ConfigKey and ContractAddress +description: Learn how to set the ConfigKey and ContractAddress, and Register the Precompile. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +## ConfigKey + +Just as with the MD5 precompile in the previous section, go to the `module.go` file and set a ConfigKey. + +## Contract Address + +In the same file, set a ContractAddress. Choose one that has not been used by other precompiles of earlier sections. \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/08-initial-state.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/08-initial-state.mdx new file mode 100644 index 00000000..ef84e813 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/08-initial-state.mdx @@ -0,0 +1,11 @@ +--- +title: Initial State +description: Setting the Initial State. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +Throughout this chapter, we've covered storing variables in the EVM state. However, all of this was through the use of StringStore/Counter Solidity interface. An important part of stateful precompiles is the ability to initialize values stored by our precompiled contracts. + +In the next few chapters, we'll see how to initialize the values of our precompiled contracts by manually setting the values or allowing for the values to be defined in the `genesis.json` file. \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/09-define-default-values-via-go.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/09-define-default-values-via-go.mdx new file mode 100644 index 00000000..17c8f1d2 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/09-define-default-values-via-go.mdx @@ -0,0 +1,51 @@ +--- +title: Defining Default Values via Golang +description: Learn how to set the default values of precompiled contracts using Go. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In this section, we'll cover defining default values of precompiled contracts using Go. We'll refer to StringStore for this section. + +## Configure Function + +To start, go to `StringStore/module.go` and scroll to the end of the file. There, you will find the Configure function: + +```go +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + return nil +} +``` + +Configure handles the initialization of a precompiled contract. We want to use Configure to define the default value for the string we are storing. + +But how? Configure gives us access to the StateDB (as one of the function parameters) and also lets us call any functions defined in `contract.go`. For example, if we wanted the default string to be "EGS," then we would just have to write one line of code: + +```go +// Configure configures [state] with the given [cfg] precompileconfig. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + StoreString(state, "EGS") + + return nil +} +``` + +We have just set a default value for our precompiled contract. \ No newline at end of file diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/10-define-default-values-via-genesis.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/10-define-default-values-via-genesis.mdx new file mode 100644 index 00000000..249ca950 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/10-define-default-values-via-genesis.mdx @@ -0,0 +1,75 @@ +--- +title: Defining Default Values via Genesis +description: Learn how to set the default values of precompiled contracts using genesis JSON file. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +In the last section, we saw how to initialize the default values of our precompiled contracts via the Configure function. While straightforward, it would be ideal for us (and developers who don't write in Go) to initialize default values via the genesis JSON. Here's how to do just that. + +## Modifying config.go + +The first step is to extract the value from the JSON file. Go to `config.go` and look at the Config struct: + +```go +// Config implements the precompileconfig.Config interface and +// adds specific configuration for StringStore. +type Config struct { + precompileconfig.Upgrade + // CUSTOM CODE STARTS HERE + // Add your own custom fields for Config here +} +``` + +Within the Config struct, we can find another struct: the Upgrade struct. It is not necessary to look at the definition of this struct. However, it is useful to know that this struct allows for the `blockTimestamp` key to be parsed in our `genesis.json` file. + +```go +"stringStoreConfig" : { + "blockTimestamp": 0, +} +``` + +To pass in default values via the genesis JSON, we must complete the following: + +- Define a key-value in the genesis JSON +- Update our Config struct so it is able to parse our new key-value + +In the case of StringStore, after defining blockTimestamp, we will add another key-pair for our default string: + +```go +"stringStoreConfig" : { + "blockTimestamp": 0, + "defaultString": "EGS" +} +``` + +Next, we will want to update our Config struct: + +```go +type Config struct { + precompileconfig.Upgrade + // CUSTOM CODE STARTS HERE + // Add your own custom fields for Config here + DefaultString string `json:"defaultString,omitempty"` +} +``` + +In the above snippet, we are defining a new field of type string named `DefaultString`. With respect to its initialization, the value of `DefaultString` is derived from the JSON key `defaultString` from our genesis JSON (the value `json:"defaultString,omitempty"` tells Go to ignore the JSON field if we do not define it in our genesis JSON). + +At this point, we have defined our new key-value pair in the genesis JSON and incorporated the logic so that precompile-evm can parse the new key-value. + +However, one step remains. Although precompile-evm can parse the new key-value, we are not actually utilizing it anywhere. Our last-step is to update `Configure` method in `module.go` so it can read our new key-value pair. + +```go +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + StoreString(state, config.DefaultString) + + return nil +} +``` diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/11-testing-precompile-hardhat.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/11-testing-precompile-hardhat.mdx new file mode 100644 index 00000000..2c1b513d --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/11-testing-precompile-hardhat.mdx @@ -0,0 +1,247 @@ +--- +title: Testing Your Precompile +description: Learn how to test your precompile using Hardhat. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Bookmark +--- + +Just like with the Calculator precompile, we want to test the functionality of our precompile to make sure it behaves as intended. + +Although we can write tests in Go, this is not the only way we can test our precompile. We can leverage Hardhat, a popular smart contract testing framework, for testing. + +In this section, we'll cover writing the smart contract component of our tests and setting up our testing environment so that precompile-evm can execute these tests for us. + +## Structure of Testing with Hardhat + +For `StringStore`, we'll need to complete the following tasks in order to leverage Hardhat: + +1. Define the Genesis JSON file we want to use for testing +2. Tell precompile-evm what Hardhat script to use to test `StringStore` +3. Write the smart contract that will hold the test cases for us +4. Write the actual script that will execute the Hardhat tests for us + +## Defining the Genesis JSON + +The easiest part of this tutorial, we will need to define the genesis state of our testing environment. For all intents and purposes, you can copy the genesis JSON that you used in the Defining Default Values section and paste it in `precompile-evm/tests/precompile/genesis`, naming it `StringStore.json` (it is important that you name the genesis file the name of your precompile). + +## Telling Precompile-EVM What To Test + +The next step is to modify `suites.go` and update the file so that precompile-evm can call the Hardhat tests for StringStore. Go to `precompile-evm/tests/precompile/solidity` and find `suites.go`. Currently, your file should look like the following: + +```go title="suites.go" +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +// Implements solidity tests. +package solidity + +import ( + "context" + "time" + + "github.com/ava-labs/subnet-evm/tests/utils" + ginkgo "github.com/onsi/ginkgo/v2" +) + +var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() { + utils.RegisterPingTest() + // Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/) + // to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contract-examples/tests/) + + // ADD YOUR PRECOMPILE HERE + /* + ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() +​ + // Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json + // and the test file in ./contracts/tests/{your_precompile}.ts + // If you want to use a different test command and genesis path than the defaults, you can + // use the utils.RunTestCMD. See utils.RunDefaultHardhatTests for an example. + utils.RunDefaultHardhatTests(ctx, "your_precompile") + }) + */ +}) +``` + +If you view the comments generated, you will already see a general structure for how we can declare our tests for precompiles. Let's take this template and use it to declare the tests for StringStore! + +```go title="suites.go" +// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +// Implements solidity tests. +package solidity + +import ( + "context" + "time" + + "github.com/ava-labs/subnet-evm/tests/utils" + ginkgo "github.com/onsi/ginkgo/v2" +) + +var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() { + utils.RegisterPingTest() + // Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/) + // to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contract-examples/tests/) + + // ADD YOUR PRECOMPILE HERE + /* + ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() +​ + // Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json + // and the test file in ./contracts/tests/{your_precompile}.ts + // If you want to use a different test command and genesis path than the defaults, you can + // use the utils.RunTestCMD. See utils.RunDefaultHardhatTests for an example. + utils.RunDefaultHardhatTests(ctx, "your_precompile") + }) + */ + + ginkgo.It("StringStore", ginkgo.Label("Precompile"), ginkgo.Label("StringStore"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.RunDefaultHardhatTests(ctx, "StringStore") + }) +}) +``` + +Again, the naming conventions are important. We recommend you use the same name as your precompile whenever possible. + +## Defining Test Contract + +In contrast to using just Go, Hardhat allows us to test our precompiles using Solidity. + +To start, create a new file called `StringStoreTest.sol` in `precompile-evm/contract/contracts`. We can start defining our file by including the following: + +```solidity title="StringStoreTest.sol" +// SPDX-License-Identifier: MIT +pragma solidity >= 0.8.0; + +import "ds-test/src/test.sol"; +import {IStringStore} from "../contracts/interfaces/IStringStore.sol"; +``` + +As of right now, there are two important things to note. + +We are first importing `test.sol`, a testing file that includes a range of assertion functions to assert that our outputs are as expected. In particular, these assertion functions are methods of the DSTest contract. + +Next, we are importing the interface of the StringStore precompile that we want to test. + +With this in mind, let's fill in the rest of our test file: + +```solidity title="StringStoreTest.sol" +contract StringStoreTest is DSTest { + + IStringStore stringStore = IStringStore(0x0300000000000000000000000000000000000005); + + function step_getString() public { + assertEq(stringStore.getString(), "Cornell"); + } + + function step_getSet() public { + string memory newStr = "Apple"; + stringStore.setString(newStr); + assertEq(stringStore.getString(), newStr); + } +} +``` + +In the contract `StringStoreTest`, we are inheriting the DSTest contract so that we can leverage the provided assertion functions. Afterward, we are declaring the variable stringStore so that we can directly call the StringStore precompile. Next, we have the two testing functions. + +Briefly looking at the logic of each test function: + +- `step_getString`: We are testing that the getString function returns the default string defined in the genesis JSON (in this example, the default string is set to "Cornell") +- `step_getSet`: We are assigning a new string to our precompile and making sure that setString does so correctly + +We now want to note the following two details regarding Solidity test functions in Hardhat: + +- Any test functions that you want to be called must start with the prefix "step" +- The assertion function you use to check your outputs is `assertEq` + +With our Solidity test contract defined, let's write actual Hardhat script! + +## Writing Your Hardhat Script + +To start, go to `precompile-evm/contracts/test` and create a new file called `StringStore.ts`. It is important to name your TypeScript file the same name as your precompile. Here are the steps to take when defining our Hardhat script: + +1. Specify the address at which our precompile is located +2. Deploy our testing contract so that we can call the test functions +3. Tell Hardhat to execute said test functions + +To make our lives even easier, Subnet-EVM (a library that Precompile-EVM leverages) has helper functions to call our test functions simply by specifying the names of the test functions. You can find the helper functions here: https://github.com/ava-labs/subnet-evm/blob/master/contracts/test/utils.ts. + +In the end, our testing file will look as follows: + +```ts title="StringStore.ts" +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +import { ethers } from "hardhat" +import { test } from "@avalabs/subnet-evm-contracts" +import { factory } from "typescript" + +const STRINGSTORE_ADDRESS = "0x0300000000000000000000000000000000000005" + +describe("StringStoreTest", function() { + this.timeout("30s") + + beforeEach("Setup DS-Test", async function () { + const stringStorePromise = ethers.getContractAt("IStringStore", STRINGSTORE_ADDRESS) + + return ethers.getContractFactory("StringStoreTest").then(factory => factory.deploy()) + .then(contract => { + this.testContract = contract + return contract.deployed().then(() => contract) + }) + }) + + test("Testing get function", "step_getString") + + test("Testing get and set function", "step_getSet") +}) +``` + +## Running Your Hardhat Test + +To run your HardHat tests, first change to the root directory of precompile-evm and run the following command: + +```bash +./scripts/build.sh +``` + +Afterward, run the following command: + +```bash +GINKGO_LABEL_FILTER=StringStore ./scripts/run_ginkgo.sh +``` + +The variable `GINKGO_LABEL_FILTER` simply tells precompile-evm which tests suite from `suites.go` to execute. Execute the `StringStore` test suite. + +However, if you have multiple precompiles to test, set `GINKGO_LABEL_FILTER` equal to "Precompile." You should see something like the following: + +```bash title="Combined output" + StringStoreTest + βœ“ Testing get function (4126ms) + βœ“ Testing get and set function (4070ms) + + 2 passing (12s) + + < Exit [It] StringStore - /Users/Rodrigo.Villar/go/src/github.com/ava-labs/precompile-evm/tests/precompile/solidity/suites.go:40 @ 07/19/23 15:37:03.574 (16.134s) +β€’ [16.134 seconds] +------------------------------ +[AfterSuite] +/Users/Rodrigo.Villar/go/pkg/mod/github.com/ava-labs/subnet-evm@v0.5.2/tests/utils/command.go:85 + > Enter [AfterSuite] TOP-LEVEL - /Users/Rodrigo.Villar/go/pkg/mod/github.com/ava-labs/subnet-evm@v0.5.2/tests/utils/command.go:85 @ 07/19/23 15:37:03.575 + < Exit [AfterSuite] TOP-LEVEL - /Users/Rodrigo.Villar/go/pkg/mod/github.com/ava-labs/subnet-evm@v0.5.2/tests/utils/command.go:85 @ 07/19/23 15:37:03.575 (0s) +[AfterSuite] PASSED [0.000 seconds] +------------------------------ + +Ran 2 of 3 Specs in 50.822 seconds +SUCCESS! -- 2 Passed | 0 Failed | 0 Pending | 1 Skipped +PASS +``` + +Congrats, you can now write Hardhat tests for your stateful precompiles! diff --git a/content/course/customizing-evm/12-stateful-counter-precompile/12-build-your-precompile.mdx b/content/course/customizing-evm/12-stateful-counter-precompile/12-build-your-precompile.mdx new file mode 100644 index 00000000..b0937461 --- /dev/null +++ b/content/course/customizing-evm/12-stateful-counter-precompile/12-build-your-precompile.mdx @@ -0,0 +1,9 @@ +--- +title: Build Your Precompile (draft) +description: Learn how to build and run your precompile in a custom EVM blockchain. +updated: 2024-05-31 +authors: [ashucoder9] +icon: Terminal +--- + +TBD \ No newline at end of file diff --git a/content/course/customizing-evm/index.mdx b/content/course/customizing-evm/index.mdx index b4ee0465..7e1fb211 100644 --- a/content/course/customizing-evm/index.mdx +++ b/content/course/customizing-evm/index.mdx @@ -1,8 +1,9 @@ --- -title: πŸ‘‹ Welcome to the Course +title: Welcome to the Course description: Learn how to customize the Ethereum Virtual Machine. updated: 2024-05-31 authors: [ashucoder9] +icon: Layers --- In this course you will learn how to customize your own EVM. We will configure the EVM using the available parameters of the chainConfig and modify the VM by adding precompiles. At the end of this course you will be able to build customized and optimized versions of the EVM. diff --git a/content/course/customizing-evm/meta.json b/content/course/customizing-evm/meta.json index b502a565..844f2c79 100644 --- a/content/course/customizing-evm/meta.json +++ b/content/course/customizing-evm/meta.json @@ -11,10 +11,6 @@ "...03-development-env-setup", "---Setup: Codespaces---", "...04-setup-codespaces", - "---Setup: DevContainers---", - "...05-setup-devcontainers", - "---Setup: Local Environment---", - "...06-setup-local-environment", "---Your Own EVM Blockchain---", "...07-your-evm-blockchain", "---Blockchain Configuration---", diff --git a/content/course/teleporter-token-bridge/04-teleporter-token-bridge/04-token-hub.mdx b/content/course/teleporter-token-bridge/04-teleporter-token-bridge/04-token-hub.mdx index 67b9070b..bfa684b7 100644 --- a/content/course/teleporter-token-bridge/04-teleporter-token-bridge/04-token-hub.mdx +++ b/content/course/teleporter-token-bridge/04-teleporter-token-bridge/04-token-hub.mdx @@ -5,4 +5,4 @@ updated: 2024-05-31 authors: [ashucoder9] --- -![Token Hub Contract Structure](/course-images/teleporter-token-bridge/hub-contract-structure.png) \ No newline at end of file +![Token Hub Contract Structure](/common-images/teleporter-token-bridge/hub-contract-structure.png) \ No newline at end of file diff --git a/content/course/teleporter-token-bridge/04-teleporter-token-bridge/05-token-spoke.mdx b/content/course/teleporter-token-bridge/04-teleporter-token-bridge/05-token-spoke.mdx index f87fba7b..5d02cbb2 100644 --- a/content/course/teleporter-token-bridge/04-teleporter-token-bridge/05-token-spoke.mdx +++ b/content/course/teleporter-token-bridge/04-teleporter-token-bridge/05-token-spoke.mdx @@ -5,7 +5,7 @@ updated: 2024-05-31 authors: [ashucoder9] --- -![Token Spoke Contract Structure](/course-images/teleporter-token-bridge/spoke-contract-structure.png) +![Token Spoke Contract Structure](/common-images/teleporter-token-bridge/spoke-contract-structure.png) - Mints assets (ERC-20 or native) diff --git a/content/course/teleporter/02-interoperability/02-source-message-destination.mdx b/content/course/teleporter/02-interoperability/02-source-message-destination.mdx index b90f3d89..1988e0e4 100644 --- a/content/course/teleporter/02-interoperability/02-source-message-destination.mdx +++ b/content/course/teleporter/02-interoperability/02-source-message-destination.mdx @@ -10,19 +10,19 @@ Interoperability is achieved by enabling blockchains to pass messages to one ano
- ![](/course-images/teleporter/source.png) + ![](/common-images/teleporter/source.png) # Source - Origin of communication - Sender calls contract
- ![](/course-images/teleporter/message.png) + ![](/common-images/teleporter/message.png) # Message - Contains source, destination, and encoded data - Signature guarantees authenticity
- ![](/course-images/teleporter/destination.png) + ![](/common-images/teleporter/destination.png) # Destination - Submission of message as transaction - Verifies signatures diff --git a/content/course/teleporter/02-interoperability/03-multi-chain-networks.mdx b/content/course/teleporter/02-interoperability/03-multi-chain-networks.mdx index 9fee5bda..2d86f97e 100644 --- a/content/course/teleporter/02-interoperability/03-multi-chain-networks.mdx +++ b/content/course/teleporter/02-interoperability/03-multi-chain-networks.mdx @@ -5,7 +5,7 @@ updated: 2024-05-31 authors: [ashucoder9] --- -![](/course-images/multi-chain-architecture/subnets.png) +![](/common-images/multi-chain-architecture/subnets.png) Avalanche is a multi-chain network, meaning the network has multiple chains being validated in parallel, while other networks, such as Bitcoin and Ethereum only have single chain. This feature provides greater scalability, independence, and customizability. Each blockchain is optimized for specialized use cases, boosting the network's overall performance. @@ -14,7 +14,7 @@ Avalanche is a multi-chain network, meaning the network has multiple chains bein To measure the performance of a blockchain, we can use two primary metrics: Time to finality (measured in seconds) and throughput (measured in transactions per second, TPS). To illustrate, we can think of a highway an as analogy. -![](/course-images/consensus/tps-vs-ttf.png) +![](/common-images/consensus/tps-vs-ttf.png) Having a short time to finality is a short highway, taking a direct route from origin (submitting a transaction) to destination (finalization of the transaction) without any unnecessary detours. A finalized transaction is irreversibly written to the ledger. Once a transaction is final, users can be sure it has executed. @@ -25,6 +25,6 @@ Having high transaction throughput is like having many lanes on the highway, mea Different blockchain networks use different scaling approaches, such as Layer 2s, including roll-ups. Networks aim to maximize the throughput. Multi-chain systems have a simple, but incredible effective way of scaling. By running independent chains in parallel, the overall network can reach a massive combined throughput. -![](/course-images/multi-chain-architecture/combined-throughput.png) +![](/common-images/multi-chain-architecture/combined-throughput.png) While roll-ups may enable a very high-throughput of a single chain, they can never outcompete the combined throughput of a multi-chain system. This is because there's no limit to the number of chains that can run in parallel. \ No newline at end of file diff --git a/content/course/teleporter/02-interoperability/04-interoperability-in-multi-chain-systems.mdx b/content/course/teleporter/02-interoperability/04-interoperability-in-multi-chain-systems.mdx index f5df25ad..213c2ff0 100644 --- a/content/course/teleporter/02-interoperability/04-interoperability-in-multi-chain-systems.mdx +++ b/content/course/teleporter/02-interoperability/04-interoperability-in-multi-chain-systems.mdx @@ -7,6 +7,6 @@ authors: [ashucoder9] Interoperability is crucial for multi-chain systems, as the price for easy scalability is fragmentation. However, this interoperability is equally important to all blockchain systems. Even though more dApps are based on the same chain in single-chain systems, many of them require interaction with other chains for liquidity or governance. Therefore, coming up with secure and efficient interoperability solutions is crucial for the entire blockchain space. -![](/course-images/multi-chain-architecture/vm-variety.png) +![](/common-images/multi-chain-architecture/vm-variety.png) Avalanche, as a multi-chain system, provides flexibility on the execution and application layer but also offers a standardized messaging protocol. The necessary cryptographic algorithms are implemented in AvalancheGo, enabling cross-Subnet communication out of the box, even when the application and execution layers of the chains may be completely different. The VMs can utilize these modules to sign and verify messages, making interoperability between Subnets much easier than between unrelated chains from different networks. \ No newline at end of file diff --git a/content/course/teleporter/04-teleporter-basics/01-teleporter-basics.mdx b/content/course/teleporter/04-teleporter-basics/01-teleporter-basics.mdx index fdefe710..5cd1276c 100644 --- a/content/course/teleporter/04-teleporter-basics/01-teleporter-basics.mdx +++ b/content/course/teleporter/04-teleporter-basics/01-teleporter-basics.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] Teleporter enables you to send messages from one blockchain to another blockchain in the Avalanche network by simply calling the `sendCrossChainMessage` on the `TeleporterMessenger` contract. This invokes a smart contract on another Subnet, where the called contract implements the `ITeleporterReceiver` interface to receive messages on the destination Subnet. We will look at what happens under the hood in a later chapter. -![](/course-images/teleporter/teleporter-source-destination.png) +![](/common-images/teleporter/teleporter-source-destination.png) # What You Will Learn diff --git a/content/course/teleporter/04-teleporter-basics/02-recap-bytes-encoding-decoding.mdx b/content/course/teleporter/04-teleporter-basics/02-recap-bytes-encoding-decoding.mdx index c3c57998..f84448cb 100644 --- a/content/course/teleporter/04-teleporter-basics/02-recap-bytes-encoding-decoding.mdx +++ b/content/course/teleporter/04-teleporter-basics/02-recap-bytes-encoding-decoding.mdx @@ -24,7 +24,7 @@ This makes it a pragmatic choice for encapsulating diverse data and data type en In solidity we can use the functions `abi.encode()` and `abi.decode()` for encoding and decoding. These are part of the solidity language, so we do not need to import anything to use them. -![Encoding and Decoding](/course-images/solidity/encoding-decoding.png) +![Encoding and Decoding](/common-images/solidity/encoding-decoding.png) ## Encoding When we encode data, we convert it into a byte array. This is useful when we want to send data from one contract to another. We can encode multiple values into a single byte array, which can then be decoded by the receiving contract. diff --git a/content/course/teleporter/04-teleporter-basics/03-sending-a-message.mdx b/content/course/teleporter/04-teleporter-basics/03-sending-a-message.mdx index 8b4eb9bf..aca5e22f 100644 --- a/content/course/teleporter/04-teleporter-basics/03-sending-a-message.mdx +++ b/content/course/teleporter/04-teleporter-basics/03-sending-a-message.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] Sending a message is nothing more than a simple contract call to the Teleporter messenger contract. - + The dApp on the Source Subnet has to call the `sendCrossChainMessage` function of the Teleporter contract. The Teleporter contract implements the `ITeleporterMessenge`r` interface below. Note that the dApp itself does not have to implement the interface. diff --git a/content/course/teleporter/05-two-way-communication/01-two-way-communication.mdx b/content/course/teleporter/05-two-way-communication/01-two-way-communication.mdx index 1c429b98..c6ae4584 100644 --- a/content/course/teleporter/05-two-way-communication/01-two-way-communication.mdx +++ b/content/course/teleporter/05-two-way-communication/01-two-way-communication.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] When we send message with Teleporter, we can see check if a message has been delivered, but we do not get any feedback wether the message has been processed correctly or any return value. If we wanted to achieve this, we need to send a message back from the receiver to the original sender. -![](/course-images/teleporter/two-way-communication.png) +![](/common-images/teleporter/two-way-communication.png) # What You Will Learn diff --git a/content/course/teleporter/06-invoking-functions/01-invoking-functions.mdx b/content/course/teleporter/06-invoking-functions/01-invoking-functions.mdx index e384eb1d..c48a4165 100644 --- a/content/course/teleporter/06-invoking-functions/01-invoking-functions.mdx +++ b/content/course/teleporter/06-invoking-functions/01-invoking-functions.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] Well done, you successfully learned how to send simple data back and forth between blockchains. In this section we will look look further and invoke a smart contract function on the destination chain. -![](/course-images/teleporter/invoking-functions.png) +![](/common-images/teleporter/invoking-functions.png) # What You Will Learn diff --git a/content/course/teleporter/06-invoking-functions/02-encoding-multiple-values.mdx b/content/course/teleporter/06-invoking-functions/02-encoding-multiple-values.mdx index 10067509..d65bbc8b 100644 --- a/content/course/teleporter/06-invoking-functions/02-encoding-multiple-values.mdx +++ b/content/course/teleporter/06-invoking-functions/02-encoding-multiple-values.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] In this section, we will learn how to pack multiple values into a single message. -![](/course-images/teleporter/message-multiple-parameters.png) +![](/common-images/teleporter/message-multiple-parameters.png) We can use `abi.encode()` to encode multiple values into a single byte array: diff --git a/content/course/teleporter/06-invoking-functions/06-encoding-function-name.mdx b/content/course/teleporter/06-invoking-functions/06-encoding-function-name.mdx index 08aa2458..ee9b1673 100644 --- a/content/course/teleporter/06-invoking-functions/06-encoding-function-name.mdx +++ b/content/course/teleporter/06-invoking-functions/06-encoding-function-name.mdx @@ -9,7 +9,7 @@ Great work. We can now pack multiple values into a message. In this section, we For the add function we need to encode two numbers. For the concatenate function we need to encode two strings. How can we go about this? The concept is easy: It's like packing an envelope into another envelope: -![](/course-images/teleporter/message-function-call.png) +![](/common-images/teleporter/message-function-call.png) ## Ecoding the Function Name and Parameters diff --git a/content/course/teleporter/07-teleporter-registry/01-teleporter-Registry.mdx b/content/course/teleporter/07-teleporter-registry/01-teleporter-Registry.mdx index 8c62bfa8..db4c956d 100644 --- a/content/course/teleporter/07-teleporter-registry/01-teleporter-Registry.mdx +++ b/content/course/teleporter/07-teleporter-registry/01-teleporter-Registry.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] When sending a message from the source chain we are calling the Teleporter contract: - + The TeleporterMessenger contract is non-upgradable. Once a version of the contract is deployed it cannot be changed. This is with the intention of preventing any changes to the deployed contract that could potentially introduce bugs or vulnerabilities. However, there could still be new versions of TeleporterMessenger contracts needed to be deployed in the future. diff --git a/content/course/teleporter/07-teleporter-registry/02-how-the-teleporter-registry-works.mdx b/content/course/teleporter/07-teleporter-registry/02-how-the-teleporter-registry-works.mdx index 98ccfceb..37fcbaa2 100644 --- a/content/course/teleporter/07-teleporter-registry/02-how-the-teleporter-registry-works.mdx +++ b/content/course/teleporter/07-teleporter-registry/02-how-the-teleporter-registry-works.mdx @@ -7,11 +7,11 @@ authors: [ashucoder9] TeleporterRegistry keeps track of TeleporterMessenger contract versions. Cross-Subnet dApps can request the latest or a specific version of the TeleporterMessenger: - + Internally the TeleporterRegistry maintains a mapping of TeleporterMessenger contract versions to their addresses. - + Each registry's mapping of version to contract address is independent of registries on other blockchains, and chains can decide on their own registry mapping entries. So the contract of version 4 on one chain does not have to be equal to that version on another chain. diff --git a/content/course/teleporter/08-avalanche-warp-messaging/05-message-pickup.mdx b/content/course/teleporter/08-avalanche-warp-messaging/05-message-pickup.mdx index ed1edf5e..dd18cca0 100644 --- a/content/course/teleporter/08-avalanche-warp-messaging/05-message-pickup.mdx +++ b/content/course/teleporter/08-avalanche-warp-messaging/05-message-pickup.mdx @@ -7,7 +7,7 @@ authors: [ashucoder9] When the AWM Relayer detects a new message, it collects the signatures of that message from the validators of the source Subnet. The validator uses the BLS private key corresponding to their BLS public key (registered on the P-Chain) to create a signature for the unsigned warp message we've learned about earlier (in the Warp Message Format). -![](/course-images/awm/relayer-pickup.png) +![](/common-images/awm/relayer-pickup.png) The AWM Relayer does not necessarily need to collect the signature of all validators. It only needs to ensure that the validators of the collected signatures represent a sufficiently large share of the total stake of the Subnet. It is up to the receiving Subnet to determine what the threshold is. diff --git a/mdx-components.tsx b/mdx-components.tsx index 584ec9c1..0bc0b236 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -13,6 +13,7 @@ import type { ReactNode } from 'react'; import { Popup, PopupContent, PopupTrigger } from 'fumadocs-ui/twoslash/popup'; import { Wrapper } from '@/components/preview/wrapper'; import YouTube from '@/components/youtube'; +import Gallery from '@/components/gallery'; import { cn } from './utils/cn'; export function useMDXComponents(components: MDXComponents): MDXComponents { @@ -34,6 +35,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { Accordions, Wrapper, YouTube, + Gallery, InstallTabs: ({ items, children, diff --git a/next.config.js b/next.config.js index 6d98b640..db0d7665 100644 --- a/next.config.js +++ b/next.config.js @@ -66,7 +66,6 @@ const withMDX = createMDX({ }, ], }, - lastModifiedTime: 'git', remarkPlugins: [ remarkMath, [remarkInstall, { Tabs: 'InstallTabs' }], diff --git a/public/course-images/avalanche-starter-kit/new-terminal.png b/public/common-images/avalanche-starter-kit/new-terminal.png similarity index 100% rename from public/course-images/avalanche-starter-kit/new-terminal.png rename to public/common-images/avalanche-starter-kit/new-terminal.png diff --git a/public/course-images/awm/relayer-pickup.png b/public/common-images/awm/relayer-pickup.png similarity index 100% rename from public/course-images/awm/relayer-pickup.png rename to public/common-images/awm/relayer-pickup.png diff --git a/public/course-images/codespaces/list-codespaces.png b/public/common-images/codespaces/list-codespaces.png similarity index 100% rename from public/course-images/codespaces/list-codespaces.png rename to public/common-images/codespaces/list-codespaces.png diff --git a/public/course-images/codespaces/stop-codespace.png b/public/common-images/codespaces/stop-codespace.png similarity index 100% rename from public/course-images/codespaces/stop-codespace.png rename to public/common-images/codespaces/stop-codespace.png diff --git a/public/course-images/consensus/tps-vs-ttf.png b/public/common-images/consensus/tps-vs-ttf.png similarity index 100% rename from public/course-images/consensus/tps-vs-ttf.png rename to public/common-images/consensus/tps-vs-ttf.png diff --git a/public/course-images/evm-precompiles/precompiles.png b/public/common-images/evm-precompiles/precompiles.png similarity index 100% rename from public/course-images/evm-precompiles/precompiles.png rename to public/common-images/evm-precompiles/precompiles.png diff --git a/public/course-images/multi-chain-architecture/combined-throughput.png b/public/common-images/multi-chain-architecture/combined-throughput.png similarity index 100% rename from public/course-images/multi-chain-architecture/combined-throughput.png rename to public/common-images/multi-chain-architecture/combined-throughput.png diff --git a/public/course-images/multi-chain-architecture/subnets.png b/public/common-images/multi-chain-architecture/subnets.png similarity index 100% rename from public/course-images/multi-chain-architecture/subnets.png rename to public/common-images/multi-chain-architecture/subnets.png diff --git a/public/course-images/multi-chain-architecture/vm-variety.png b/public/common-images/multi-chain-architecture/vm-variety.png similarity index 100% rename from public/course-images/multi-chain-architecture/vm-variety.png rename to public/common-images/multi-chain-architecture/vm-variety.png diff --git a/public/course-images/primary-network/p-chain.png b/public/common-images/primary-network/p-chain.png similarity index 100% rename from public/course-images/primary-network/p-chain.png rename to public/common-images/primary-network/p-chain.png diff --git a/public/course-images/solidity/encoding-decoding.png b/public/common-images/solidity/encoding-decoding.png similarity index 100% rename from public/course-images/solidity/encoding-decoding.png rename to public/common-images/solidity/encoding-decoding.png diff --git a/public/course-images/teleporter-token-bridge/hub-contract-structure.png b/public/common-images/teleporter-token-bridge/hub-contract-structure.png similarity index 100% rename from public/course-images/teleporter-token-bridge/hub-contract-structure.png rename to public/common-images/teleporter-token-bridge/hub-contract-structure.png diff --git a/public/course-images/teleporter-token-bridge/spoke-contract-structure.png b/public/common-images/teleporter-token-bridge/spoke-contract-structure.png similarity index 100% rename from public/course-images/teleporter-token-bridge/spoke-contract-structure.png rename to public/common-images/teleporter-token-bridge/spoke-contract-structure.png diff --git a/public/course-images/teleporter/destination.png b/public/common-images/teleporter/destination.png similarity index 100% rename from public/course-images/teleporter/destination.png rename to public/common-images/teleporter/destination.png diff --git a/public/course-images/teleporter/invoking-functions.png b/public/common-images/teleporter/invoking-functions.png similarity index 100% rename from public/course-images/teleporter/invoking-functions.png rename to public/common-images/teleporter/invoking-functions.png diff --git a/public/course-images/teleporter/message-function-call.png b/public/common-images/teleporter/message-function-call.png similarity index 100% rename from public/course-images/teleporter/message-function-call.png rename to public/common-images/teleporter/message-function-call.png diff --git a/public/course-images/teleporter/message-multiple-parameters.png b/public/common-images/teleporter/message-multiple-parameters.png similarity index 100% rename from public/course-images/teleporter/message-multiple-parameters.png rename to public/common-images/teleporter/message-multiple-parameters.png diff --git a/public/course-images/teleporter/message.png b/public/common-images/teleporter/message.png similarity index 100% rename from public/course-images/teleporter/message.png rename to public/common-images/teleporter/message.png diff --git a/public/course-images/teleporter/source.png b/public/common-images/teleporter/source.png similarity index 100% rename from public/course-images/teleporter/source.png rename to public/common-images/teleporter/source.png diff --git a/public/course-images/teleporter/teleporter-destination.png b/public/common-images/teleporter/teleporter-destination.png similarity index 100% rename from public/course-images/teleporter/teleporter-destination.png rename to public/common-images/teleporter/teleporter-destination.png diff --git a/public/course-images/teleporter/teleporter-registry-class-diagram.png b/public/common-images/teleporter/teleporter-registry-class-diagram.png similarity index 100% rename from public/course-images/teleporter/teleporter-registry-class-diagram.png rename to public/common-images/teleporter/teleporter-registry-class-diagram.png diff --git a/public/course-images/teleporter/teleporter-registry.png b/public/common-images/teleporter/teleporter-registry.png similarity index 100% rename from public/course-images/teleporter/teleporter-registry.png rename to public/common-images/teleporter/teleporter-registry.png diff --git a/public/course-images/teleporter/teleporter-source-destination.png b/public/common-images/teleporter/teleporter-source-destination.png similarity index 100% rename from public/course-images/teleporter/teleporter-source-destination.png rename to public/common-images/teleporter/teleporter-source-destination.png diff --git a/public/course-images/teleporter/teleporter-source.png b/public/common-images/teleporter/teleporter-source.png similarity index 100% rename from public/course-images/teleporter/teleporter-source.png rename to public/common-images/teleporter/teleporter-source.png diff --git a/public/course-images/teleporter/two-way-communication.png b/public/common-images/teleporter/two-way-communication.png similarity index 100% rename from public/course-images/teleporter/two-way-communication.png rename to public/common-images/teleporter/two-way-communication.png diff --git a/public/course-images/telepoter-chainlink-vrf/VRF provider and client.png b/public/common-images/telepoter-chainlink-vrf/VRF provider and client.png similarity index 100% rename from public/course-images/telepoter-chainlink-vrf/VRF provider and client.png rename to public/common-images/telepoter-chainlink-vrf/VRF provider and client.png diff --git a/public/course-banner/avalanche-fundamentals.jpg b/public/course-banner/avalanche-fundamentals.jpg index 2575186d..bea148af 100644 Binary files a/public/course-banner/avalanche-fundamentals.jpg and b/public/course-banner/avalanche-fundamentals.jpg differ diff --git a/public/course-banner/customizing-evm.jpg b/public/course-banner/customizing-evm.jpg new file mode 100644 index 00000000..91ae13c6 Binary files /dev/null and b/public/course-banner/customizing-evm.jpg differ diff --git a/public/course-banner/hypersdk.jpg b/public/course-banner/hypersdk.jpg index a9f9f635..098eda8f 100644 Binary files a/public/course-banner/hypersdk.jpg and b/public/course-banner/hypersdk.jpg differ diff --git a/public/course-banner/multi-chain-architecture.jpg b/public/course-banner/multi-chain-architecture.jpg index 26d21e70..66888a2e 100644 Binary files a/public/course-banner/multi-chain-architecture.jpg and b/public/course-banner/multi-chain-architecture.jpg differ diff --git a/public/course-banner/node-course.jpg b/public/course-banner/node-course.jpg new file mode 100644 index 00000000..eeb20dc9 Binary files /dev/null and b/public/course-banner/node-course.jpg differ diff --git a/public/course-banner/solidity-fundamentals.jpg b/public/course-banner/solidity-fundamentals.jpg new file mode 100644 index 00000000..52ef01e8 Binary files /dev/null and b/public/course-banner/solidity-fundamentals.jpg differ diff --git a/public/course-banner/teleporter.jpg b/public/course-banner/teleporter.jpg index 5845d63e..4f3efc5e 100644 Binary files a/public/course-banner/teleporter.jpg and b/public/course-banner/teleporter.jpg differ diff --git a/public/course/customizing-evm/29.png b/public/course/customizing-evm/29.png new file mode 100644 index 00000000..273fd4b6 Binary files /dev/null and b/public/course/customizing-evm/29.png differ diff --git a/public/showcases/briefkasten.png b/public/showcases/briefkasten.png deleted file mode 100644 index e715eeea..00000000 Binary files a/public/showcases/briefkasten.png and /dev/null differ diff --git a/public/showcases/frameground.png b/public/showcases/frameground.png deleted file mode 100644 index 631a0a83..00000000 Binary files a/public/showcases/frameground.png and /dev/null differ diff --git a/public/showcases/next-faq.png b/public/showcases/next-faq.png deleted file mode 100644 index 516810c5..00000000 Binary files a/public/showcases/next-faq.png and /dev/null differ diff --git a/public/showcases/nuqs.jpg b/public/showcases/nuqs.jpg deleted file mode 100644 index b741d2d3..00000000 Binary files a/public/showcases/nuqs.jpg and /dev/null differ diff --git a/public/showcases/xlog.png b/public/showcases/xlog.png deleted file mode 100644 index 60ffb972..00000000 Binary files a/public/showcases/xlog.png and /dev/null differ diff --git a/public/showcases/yeecord.png b/public/showcases/yeecord.png deleted file mode 100644 index 83d0654b..00000000 Binary files a/public/showcases/yeecord.png and /dev/null differ diff --git a/public/spot.png b/public/spot.png deleted file mode 100644 index cafc8756..00000000 Binary files a/public/spot.png and /dev/null differ diff --git a/scripts/generate-docs.mts b/scripts/generate-docs.mts deleted file mode 100644 index 6f91e014..00000000 --- a/scripts/generate-docs.mts +++ /dev/null @@ -1,33 +0,0 @@ -import * as OpenAPI from 'fumadocs-openapi'; -import * as Typescript from 'fumadocs-typescript'; -import * as path from 'node:path'; - -void OpenAPI.generateFiles({ - input: ['./*.yaml'], - output: './content/docs/ui', - per: 'tag', - render: (title, description) => { - return { - frontmatter: [ - '---', - `title: ${title}`, - `description: ${description}`, - 'toc: false', - '---', - ].join('\n'), - }; - }, -}); - -const demoRegex = /^---type-table-demo---\r?\n(?.+)\r?\n---end---$/gm; -void Typescript.generateFiles({ - input: ['./content/docs/**/*.model.mdx'], - transformOutput(_, content) { - return content.replace(demoRegex, '---type-table---\n$1\n---end---'); - }, - output: (file) => - path.resolve( - path.dirname(file), - `${path.basename(file).split('.')[0]}.mdx`, - ), -}); diff --git a/scripts/update-index.mts b/scripts/update-index.mts deleted file mode 100644 index 60cc042f..00000000 --- a/scripts/update-index.mts +++ /dev/null @@ -1,34 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { resolve } from 'node:path'; -import env from '@next/env'; -import algosearch from 'algoliasearch'; -import { sync } from 'fumadocs-core/search-algolia/server'; -import type { SearchIndex } from 'fumadocs-mdx'; - -env.loadEnvConfig(process.cwd()); - -const indexes = JSON.parse( - readFileSync( - resolve('./.next/server/chunks/fumadocs_search.json'), - ).toString(), -) as SearchIndex[]; - -const client = algosearch( - process.env.ALGOLIA_APP_ID || '', - process.env.ALGOLIA_API_KEY || '', -); - -void sync(client, { - document: process.env.NEXT_PUBLIC_ALGOLIA_INDEX, - documents: indexes.map((docs) => ({ - _id: docs.id, - title: docs.title, - url: docs.url, - structured: docs.structuredData, - extra_data: { - tag: docs.url.split('/')[2], - }, - })), -}).then(() => { - console.log('search updated'); -}); diff --git a/utils/modes.ts b/utils/modes.ts deleted file mode 100644 index e69de29b..00000000