<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/rss-style.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Satyam</title>
        <link>https://satyam.cv</link>
        <description>satyam's course of life</description>
        <lastBuildDate>Fri, 19 Jun 2026 19:14:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro Chiri Feed Generator</generator>
        <language>en-US</language>
        <copyright>Copyright © 2026 satyam</copyright>
        <atom:link href="https://satyam.cv/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Analysis of the Hedgey Finance Exploit]]></title>
            <link>https://satyam.cv/analysis_of_the_hedgey_finance_exploit</link>
            <guid isPermaLink="false">https://satyam.cv/analysis_of_the_hedgey_finance_exploit</guid>
            <pubDate>Sun, 07 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Resulting in a loss of $44.7 million worth of assets On April 19th of this year, Hedgey Finance fell victim to a standard flash loan attack by hackers who stole $2.1 million on the Ethereum Mainnet an...]]></description>
            <content:encoded><![CDATA[<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_0.0Ese7wN5_ZsazfN.webp" alt="Hedgey logo above an illustration of a hacker in a hoodie using a laptop with a skull on the screen" /></p>
<h2>Resulting in a loss of $44.7 million worth of assets</h2>
<p>On April 19th of this year, Hedgey Finance fell victim to a standard flash loan attack by hackers who stole $2.1 million on the Ethereum Mainnet and $42.6 million worth of assets on the Arbitrum network, resulting in approximately $44.7 million lost funds.</p>
<p>Cyvers first reported the exploit, and <a href="https://x.com/hedgeyfinance/status/1781257581488418862">Hedgey Finance confirmed</a> it a few hours later.</p>
<p>But what does Hedgey finance do?</p>
<h3>Background</h3>
<p>Hedgey Finance provides a free platform for creating and managing on-chain token vesting, lockups, claim portals, and associated functionalities. It claimed to be “the #1 token vesting and lockup tool,” to this day, it does the same even after the exploit. It’s so ironic. They will need a lot of work to regain that trust.</p>
<h2>Vulnerability Analysis &amp; Impact</h2>
<h3>On-Chain Details</h3>
<p><a href="https://etherscan.io/address/0xDed2b1a426E1b7d415A40Bcad44e98F47181dda2">Attacker Address</a>: <code>0xDed2b1a426E1b7d415A40Bcad44e98F47181dda2</code></p>
<p><a href="https://etherscan.io/address/0xC793113F1548B97E37c409f39244EE44241bF2b3">Attack Contract</a>: <code>0xC793113F1548B97E37c409f39244EE44241bF2b3</code></p>
<p><a href="https://etherscan.io/address/0xBc452fdC8F851d7c5B72e1Fe74DFB63bb793D511">Vulnerable Contract</a>: <code>0xBc452fdC8F851d7c5B72e1Fe74DFB63bb793D511</code></p>
<p><a href="https://etherscan.io/tx/0x2606d459a50ca4920722a111745c2eeced1d8a01ff25ee762e22d5d4b1595739">Main Attack Transactions</a>: <code>0x2606d459a50ca4920722a111745c2eeced1d8a01ff25ee762e22d5d4b1595739</code></p>
<h3>Root Cause</h3>
<p>The root cause of the hack was the lack of input validation on the user’s parameter within a critical function inside the token-locking contracts, which helped the attacker gain access to unauthorized tokens. This token approval vulnerability was created in the <code>ClaimCampaigns</code> Contract (in the <a href="https://github.com/hedgey-finance/Locked_VestingTokenPlans/blob/8c7679c7526c29980c6255127c0cd0ba85d000fa/contracts/Periphery/ClaimCampaigns.sol#L192"><em>line 192</em></a>).</p>
<p>::github{repo=“hedgey-finance/Locked_VestingTokenPlans”}</p>
<p>The attacker started by taking a flash loan and created a new campaign using the <code>createLockedCampaign()</code> Function. The creation of tokenLocker granted the exploiter’s contract approval to interact with the token vested under the Hedgey’s contract. In the same transaction, the <code>cancelCampaign()</code> function withdraws tokens from the tokenLocker, but it fails to revoke the granted token allowance, although it revoked the claim approval. With the approval still there, the attacker can transfer other unauthorized tokens.</p>
<h2><strong>The Attack</strong></h2>
<h3><strong>Transaction 1: Preparation</strong></h3>
<ol>
<li>The attacker receives <em>$1.3M USDC</em> in flash loan from <strong>Balancer</strong> for the exploit contract.</li>
<li>Attacker creates campaign with locking $1.3M USDC via the <code>createLockedCampaign()</code> function.</li>
<li>Attacker cancels campaign via the <code>cancelCampaign()</code> function, exploit contract receives the locked <em>$1.3M USDC</em> back and returns it to the Balancer.</li>
</ol>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_1.0WJ3Sk8i_77fC7.webp" alt="Diagram showing the flash loan flow: Balancer Vault transferring 1.3 million USDC to the Receiver contract, and back" /></p>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_2.zqX1gUzd_TXMRv.webp" alt="The Preparation Transaction Flow: 0xa17fdb804728f226fcd10e78eae5247abd984e0f03301312315b89cae25aa517" /></p>
<h3>Transaction 2: Draining Funds</h3>
<ol>
<li>The <code>cancelCampaign()</code> function does give back the funds but doesn’t revoke the allowance for using <code>transferFrom</code> on the USDC token contract.</li>
<li>The attacker used this door to drain <em>$1.3M USDC</em> from the <strong>ClaimCampaigns</strong> contract using <code>transferFrom</code> on the USDC token contract.</li>
<li>In addition to USDC, a large number of NOBL Tokens were stolen from the <strong>ClaimCampaigns</strong> contract valued at <em>$0.8M</em> at that time. Also, adding to it, <em>$20K</em> MASA tokens were stolen.</li>
</ol>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_3.DfqhTyHw_fqmls.webp" alt="Diagram showing Hedgey Finance Claims transferring 2.38 million NOBL Tokens to the Hedgey Finance Exploiter" /><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_4.SAoOpxOP_anGUV.webp" alt="The NOBL token Transaction Flow: 0x47da1ac72d488f746865891c9196c1632ae04f018b285b762b2b564ad1d3a9e5" /></p>
<ol>
<li>The attacker also utilized the <strong>Arbitrum network</strong> to exploit the same vulnerability to abscond with more than 77.74M BONUS tokens valued at around <em>$42M</em> at the time of the exploit. On Arbitrum,</li>
</ol>
<p><a href="https://arbiscan.io/address/0xc7241e27ee4b8d32b59a10e848b48530047a8c5b">Exploiter’s address</a>: <code>0xc7241e27ee4b8d32b59a10e848b48530047a8c5b</code></p>
<p><a href="https://arbiscan.io/address/0xbb52f1723ddf2c84ba2668f4e04712f572cbf780">Attack Contract</a>: <code>0xbb52f1723ddf2c84ba2668f4e04712f572cbf780</code></p>
<p><a href="https://arbiscan.io/address/0xbc452fdc8f851d7c5b72e1fe74dfb63bb793d511">Exploited Contract</a>: <code>0xbc452fdc8f851d7c5b72e1fe74dfb63bb793d511</code></p>
<h3>Total Loss</h3>
<p>Since the liquid backing of the token is essential to determine the actual value of the tokens stolen, the real loss is much less than the loss reported by multiple reports. The attacker’s wallet still has a <a href="https://arbiscan.io/tokenholdings?a=0xc7241e27ee4b8d32b59a10e848b48530047a8c5b">balance</a> of 76.8M BONUS tokens. A further 200k tokens were transferred to a Bybit account, and ~900k tokens remain in additional wallets. So, the total loss is around $600K on Arbitrum and $2.1M on Ethereum, totaling $2.7M.</p>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_5.D3PZea5n_1qnEbK.webp" alt="76.8M BONUS token on exploiter's address" /></p>
<h3>The flow of Funds</h3>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_6.pXWKbWeT_2eQBha.webp" alt="Complex diagram illustrating the complete flow of funds between Hedgey Finance, NobleBlocks, Uniswap V2 Router, and Wrapped Ether" /></p>
<p>Too complex to explain, I know, and that too in writing. Very difficult. Hopefully, you will understand what took place from the above flow diagram.</p>
<h2>Aftermath</h2>
<p>Following the hack, the Hedgey team went on X to inform its users about the hack and advise users to cancel their active claims to mitigate any additional impact. Hopefully, it mitigated some potential loss.</p>
<h3>Lessons Learned</h3>
<p>A reminder of the importance of robust security measures in decentralized finance (DeFi) protocols.</p>
<ul>
<li>Input validation and parameter verification are fundamental safeguards against exploitation.</li>
<li>Auditing is essential and helps remove vulnerabilities pre-hack.</li>
<li>This incident also highlights the limitations of audits and the importance of real-time transaction security measures. As mentioned before, the detection by Cyvers.</li>
<li>Business logic maintenance is crucial in any project’s development and design process.</li>
<li>The flaw was solved as quickly as adding and deleting a line of code.</li>
</ul>
<p><img src="https://satyam.cv/_astro/analysis_of_the_hedgey_finance_exploit_7.vmmjb5mu_Z28VH6F.webp" alt="Exploit Fixed on ClaimCampaigns.sol" /></p>
<p><em>These crypto exploits are a constant reminder of the importance of security in this field. This line of code cost Hedgey around $3M, which is expensive. The question remains: can Hedgey reclaim the trust it has lost?</em></p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What is RIP-7212?]]></title>
            <link>https://satyam.cv/what_is_rip_7212_</link>
            <guid isPermaLink="false">https://satyam.cv/what_is_rip_7212_</guid>
            <pubDate>Thu, 30 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A Precompile for secp256r1 Curve Support If you see the original proposal RIP-7212 (formerly EIP-7212), it says, This proposal creates a precompiled contract that performs signature verifications in t...]]></description>
            <content:encoded><![CDATA[<p><img src="https://satyam.cv/_astro/what_is_rip_7212__0.DHOnWztp_19vwcU.webp" alt="Note: you can’t obtain a private key from the public key because of the nature of the cryptographic method used, for example, the elliptic curve method." /></p>
<h2>A Precompile for secp256r1 Curve Support</h2>
<p>If you see the original proposal <a href="https://eips.ethereum.org/EIPS/eip-7212">RIP-7212 (formerly EIP-7212)</a>, it says,</p>
<blockquote>
<p>This proposal creates a precompiled contract that performs signature verifications in the “secp256r1” elliptic curve by given parameters of message hash, <code>r</code> and <code>s</code> components of the signature, <code>x</code> and <code>y</code> coordinates of the public key. So that, any EVM chain - principally Ethereum rollups - will be able to integrate this precompiled contract easily.</p>
</blockquote>
<p>after reading this, if you are like me, you would say, “What the heck is this?”. This needs a lot of foundation to understand the whole scope of problems it solves and the innovation that can be built on top of that.</p>
<p>First up is Externally owned accounts.</p>
<h3>Externally owned accounts</h3>
<p>Externally owned accounts are accounts controlled by anyone with private keys. The basic idea is to create a pair of cryptographic keypairs, one public, and one private key. It can be done using a regular Diffie Hellman algorithm (the discrete logarithm method), which seems much more straightforward than the Elliptic curve digital signature method. So why an elliptic curve?</p>
<h2>Elliptic Curves</h2>
<p>Elliptic curves are more computationally efficient and use shorter keys, which saves transaction fees compared to other algorithms at work. Let’s understand it a little.</p>
<p>This might look very complex on the surface level, but mathematically, it’s easy. If you want to explore this further, you can read this: <a href="https://www.math.brown.edu/johsilve/Presentations/WyomingEllipticCurve.pdf">https://www.math.brown.edu/johsilve/Presentations/WyomingEllipticCurve.pdf</a>, as I will not dive deep into this.</p>
<p>In simple terms, the curve below is used to get random points on the curve by creating and reflecting lines. Those points can be used to develop keypairs.</p>
<p><img src="https://satyam.cv/_astro/what_is_rip_7212__1.teX_9ZLp_ZXkOor.webp" alt="A graph of the secp256k1’s elliptic curve over real numbers (https://www.flavius.io/media/a-word-on-secp256k1-and-ecdsa)" /></p>
<p>The elliptic curve is defined by following mathematical equation,</p>
<p><img src="https://satyam.cv/_astro/what_is_rip_7212__2.BpsARC6y_NeEAM.webp" alt="The mathematical equation for an elliptic curve: y squared equals x cubed plus ax plus b" /></p>
<p>by varying a and b, we can have multiple equations and their pair curves.</p>
<h3>secp256k1 vs secp256r1</h3>
<p>In mathematics, the two most common elliptic curves defined in <a href="https://www.secg.org/sec2-v2.pdf">SEC 2</a> are <strong>secp256k1</strong> and <strong>secp256r1.</strong> Note the “r” in the penultimate position rather than a “k.” The “k” in secp256k1 stands for <strong>Koblitz,</strong> and the “r” in secp256r1 stands for <strong>random</strong>.</p>
<p>For secp256k1, we have</p>
<pre><code>a = 0
b = 7
</code></pre>
<p>and for secp256r1 (also called NIST P-256), we have</p>
<pre><code>a = FFFFFFFF 00000001 00000000 00000000 00000000 FFFFFFFF FFFFFFFF FFFFFFFC
b = 5AC635D8 AA3A93E7 B3EBBD55 769886BC 651D06B0 CC53B0F6 3BCE3C3E 27D2604B
</code></pre>
<p>Random seems more secure, but the fun fact is that Bitcoin started with secp256k1, aka the Koblitz curve. The reason comes from the conspiracy of the NSA controlling NIST, who might have created a backdoor using the random number generator in secp256r1 for a &amp; b. Given the fact that the curve r1 is pseudo-randomized, thus such weak curves could be used under the attack. As a result, Satoshi decided to use a pre-defined pure Koblitz curve for Bitcoin. Not to mention, the Koblitz curve is more efficient, comparatively.</p>
<p><img src="https://satyam.cv/_astro/what_is_rip_7212__3.DePHReKX_1THPVP.webp" alt="Flowchart showing the creation and verification of a digital signature using an elliptic curve, private key, message, and public key" /></p>
<p>Now, using the secp256k1 elliptic curve, a user can create a signature for a transaction using a private key, which anyone can use to verify if a particular transaction was signed by a public address or not. To understand this practically, there is this fantastic website that you can use: <a href="https://andersbrownworth.com/blockchain/public-private-keys/signatures.">https://andersbrownworth.com/blockchain/public-privatfantasticys/signatures.</a></p>
<h3>Why even care about secp256r1?</h3>
<p>Curious? Why is there so much effort in using secp256r1? Even when both curves have almost the exact cost of combined attacks and security conditions. One of the most essential functions is that this curve is widely used and supported in many modern devices, such as Apple’s Secure Enclave, Webauthn, and Android Keychain, which improves user adoption. It solves the challenge web3 currently faces, “cumbersome user experience (UX).”</p>
<p><img src="https://satyam.cv/_astro/what_is_rip_7212__4.BGRV9DmY_ZK8hfF.webp" alt="FaceID is one of the pioneering UX in the Apple ecosystem." /></p>
<p>For example, the <a href="https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/protecting_keys_with_the_secure_enclave">Apple secure enclave</a> only works with secp256r1 as it is a hardware-based key manager isolated from the main processor, meaning you can’t change how the signature is produced but can verify it. And if the protocol is just using secp256k1, we can’t have FaceID-integrated web3 mobile applications.</p>
<p>We need to know more about account abstraction to know what RIP-7212 brings to the table.</p>
<h2>EIP-4337: Account abstraction</h2>
<p>The idea was first tossed in 2017 with the proposal of EIP-86 by Vitalik Butrin. However, the notion of account abstraction existed only with the proposal of EIP-4337, as it avoided making changes to the consensus-layer protocol.</p>
<p>Instead of changing the bottom-layer transaction type and adding new protocol features, this proposal introduced a higher-layer pseudo-transaction object called a <code>UserOperation</code>. Users send <code>UserOperation</code> into a separate mempool using a bundler to set the objects into a transaction while making a <code>handleOps</code> call to a special contract, and that transaction then gets included in a block using an <code>entrypoint</code>.</p>
<p>The key goal achieved was to allow users to use smart contract wallets containing arbitrary verification logic instead of EOAs as their primary account.</p>
<p><img src="https://satyam.cv/_astro/what_is_rip_7212__5.ClnazXpx_Z2uNPdF.webp" alt="How account abstraction works (https://www.alchemy.com/learn/account-abstraction)" /></p>
<p>Users can now send <code>UserOperation</code>, which contains all the same parameters as the transaction, like “sender,” “to,” “calldata,” “maxFeePerGas,” “nonce,” “maxPriorityFee,” and “signature.” Apart from this, it also has the <code>Signatature</code> field is not defined by the protocol but by each account implementation.</p>
<p>This means you can use any signature algorithm adhering to your needs for the application from secp256r1 to Ed25519. Lost? Why is there a need for RIP-7212? What else must be done if EIP-4337 created the support to use secp256r1?</p>
<h3>Precompiled Contracts</h3>
<p>Higher gas cost. Unfortunately, using a non-native Ethereum curve generates a higher computational cost (gas). Compared to the native curve, it is around 50 times more. Because precompiled contracts support the native curve. What are precompiled contracts?</p>
<p>Precompiled contracts are special kinds of contracts bundled with the EVM at a fixed address and can be called with a determined gas cost, which is far lower than calling non-precompiled smart contracts.</p>
<p><a href="https://www.evm.codes/precompiled#0x01?fork=cancun">0x01</a> is the <code>ECRECOVER</code> precompiled address is a function to get the public address from a signature for the secp256k1 curve. RIP-7212 proposes a new precomplied contract for secp256r1, <code>P256VERIFY</code> , similar in function to the prior but for the secp256r1 curve.</p>
<p>In summary,</p>
<blockquote>
<p>RIP-7217 proposes to create a precompiled contract for the secp256r1 curve.</p>
</blockquote>
<p>Hopefully, you might get the picture that I wanted to paint in your mind.</p>
<p><em>Bonus. Still don’t know the reason for it being RIP, not EIP?</em> So, other willing rollups can use this to complete implementation around the standardization created with <a href="https://ethereum-magicians.org/t/eip-7212-precompiled-for-secp256r1-curve-support/14789/69">RIP</a>.</p>
<h3>Resources:</h3>
<p>::link{url=“<a href="https://eips.ethereum.org/EIPS/eip-7212">https://eips.ethereum.org/EIPS/eip-7212</a>”}
::link{url=“<a href="https://ethereum-magicians.org/t/eip-7212-precompiled-for-secp256r1-curve-support/14789">https://ethereum-magicians.org/t/eip-7212-precompiled-for-secp256r1-curve-support/14789</a>”}
::link{url=“<a href="https://www.johndcook.com/blog/2018/08/21/a-tale-of-two-elliptic-curves/">https://www.johndcook.com/blog/2018/08/21/a-tale-of-two-elliptic-curves/</a>”}
::link{url=“<a href="https://eips.ethereum.org/EIPS/eip-4337">https://eips.ethereum.org/EIPS/eip-4337</a>”}
::link{url=“<a href="https://eprint.iacr.org/2023/939.pdf">https://eprint.iacr.org/2023/939.pdf</a>”}
::link{url=“<a href="https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/protecting_keys_with_the_secure_enclave">https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/protecting_keys_with_the_secure_enclave</a>”}
::link{url=“<a href="https://www.odaily.news/en/post/5188615">https://www.odaily.news/en/post/5188615</a>”}
::link{url=“<a href="https://dappworks.com/why-did-satoshi-decide-to-use-secp256k1-instead-of-secp256r1/">https://dappworks.com/why-did-satoshi-decide-to-use-secp256k1-instead-of-secp256r1/</a>”}</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Move, exploring its way into Solana]]></title>
            <link>https://satyam.cv/move__exploring_its_way_into_solana</link>
            <guid isPermaLink="false">https://satyam.cv/move__exploring_its_way_into_solana</guid>
            <pubDate>Thu, 29 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[A Smart Contract Language on the rise “Move,” a language that has been rising recently with the development of emerging blockchains like Aptos and Sui. Now, we can watch the steady development in Sola...]]></description>
            <content:encoded><![CDATA[<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_0.sY6JHgCR_Z2kHfGe.webp" alt="Abstract logo of two intersecting blue and cyan rounded rectangles on a gradient background" /></p>
<h2>A Smart Contract Language on the rise</h2>
<p>“Move,” a language that has been rising recently with the development of emerging blockchains like Aptos and Sui. Now, we can watch the steady development in Solana.</p>
<blockquote>
<p>Move programming language is a safe and flexible language built for the blockchain giving you power for programable resources.</p>
</blockquote>
<p>Let’s begin by deep-diving into Move to understand it better,</p>
<h2>Why Move?</h2>
<p>It was originally built by Facebook’s Libra/Diem Blockchain — a payment network. The language came into existence with the native support for ownership/assets which other languages were lacking. The design of Move defines custom resource types forcing a resource can’t be copied or implicitly discarded, only moved between program storage locations. Basically, the safety is implemented by Move’s type system.</p>
<p>In layman’s terms, Move is statically typed and designed around modules giving it a fundamental advantage compared to other languages.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_1.BeybW26h_emiB3.webp" alt="An illustration of human evolution depicting the progression from apes to modern man, with labels Solidity, Rust, Anchor, and Move" /></p>
<p>Move was designed while keeping four key goals in mind: first-class resources(assets), flexibility, safety, and verifiability.</p>
<p><strong>First-class resources:</strong> Let users write programs that directly interact with the digital assets while using the type system guaranteeing safety.</p>
<p><strong>Flexibility:</strong> Move adds flexibility via transaction scripts. A transaction script is a single procedure that contains an arbitrary Move code, which allows customizable transactions.</p>
<p><strong>Safety:</strong> Move rejects any program that does not satisfy key properties, such as resource safety, type safety, and memory safety. It is ensured by the on-chain bytecode verifier and after verification, it is directly executed by the bytecode interpreter.</p>
<p><strong>Verifiability:</strong> Security and computational cost go hand in hand, we have to carefully weigh both. Move balances the two by using lightweight on-chain verification and advanced off-chain static verification tools.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_2.DGobHbf4_hQd6g.webp" alt="Diagram showing Move Blockchain State with global storage branching into Addresses, Resources, and Modules" /><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_3.B-Vi7Cn7_Z1LL1zo.webp" alt="Move and Solidity Blockchain State" /></p>
<p><em>The key difference is Solidity’s storage is address-based whereas Move’s storage is object-based.</em></p>
<h3>But how it is different from Solana?</h3>
<p>I assume you have familiarity with Solana’s Programming model or else you can give a read to <a href="https://humont.dev/notes/2021-09-01-solana-programming-overview">this</a>.</p>
<p>The biggest difference is that <strong>Move doesn’t require account checks</strong>. Which is very tricky to implement on Solana and also a reason for some biggest exploits (<a href="https://rekt.news/wormhole-rekt/">account substitution attacks</a>). Solana has to run checks such as account ownership, type, instance, and signature checks.</p>
<p>So how Move is handling this? In the Move runtime, some of the checks are transparently run and the rest of them are verified statically at the compile time. Even the type system design allows Move to skip some of the checks often required in Solana.</p>
<p>For example, due to checks and other declarations, the “Mint Authority” code in Solana and Move (Sui) has a distinct gap in size.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_4.B7eVaPF9_Z1p9X9d.webp" alt="Code snippet comparison between Solana and Sui showing the difference in account checks" /></p>
<p>Apart from the checks, Solana stores the addresses of other accounts in the form of pointers and doesn’t store the actual data. To access the accounts, they need to be passed every time and manually checked. On the other hand, in Move, we can directly access them using structs indefinitely without running any checks.</p>
<p>This is the reason, “<em>Move becomes a lot safer and faster option to code due to its global typed system and natural design.</em>”</p>
<p>An example of Move from <a href="https://move-language.github.io/move/">the Move book</a>:</p>
<pre><code>module 0x42::test {
    struct Example has copy, drop { i: u64 }

    use std::debug;
    friend 0x42::another_test;

    const ONE: u64 = 1;

    public fun print(x: u64) {
        let sum = x + ONE;
        let example = Example { i: sum };
        debug::print(&amp;sum)
    }
}
</code></pre>
<p>If you want to explore coding in Move you can check out <a href="https://github.com/MystenLabs/awesome-move">this curated list.</a> Also, <a href="https://github.com/move-language/move/blob/main/language/documentation/tutorial/README.md">this Move tutorial</a> is a good place to get started.</p>
<p>::github{repo=“MystenLabs/awesome-move”}
::github{repo=“move-language/move”}</p>
<p>All this was a technical overview of the “Why Move?”. But do we have a community of developers who are interested in this?</p>
<h3>Move community</h3>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_5.BThLBmXF_LkYsm.webp" alt="Developer Report by Electric Capital" /></p>
<p>Even Aptos and Sui (Move-based blockchains) are fairly new blockchains, they have a much greater number of developers compared to blockchains Algorand and Stacks who use their programming languages.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_6.BZHECupP_2wiAv2.webp" alt="Developer Report by Electric Capital" /></p>
<p>To make this read more interesting I figured why not get to know the behind-the-scenes of the project Move on Solana. It’s a good way to give your brain a headache, at least to mine, lol.</p>
<h2>Move on Solana</h2>
<p>Before getting into all the heavy stuff, knowing how Move works is convenient to understand.</p>
<h3>How Move Works?</h3>
<p>Move is built on Rust with ownership in mind and is strongly focused on simplicity, correctness, and verifiability. It doesn’t support dynamic dispatch which makes it faster, efficient, and allows early detection of type errors.</p>
<p>First, the Move code is compiled into Move bytecode, which runs in the Move VM for the Bytecode validation. The traditional architecture of the Move virtual machine:</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_7.Cd8L4hqQ_23b8oF.webp" alt="Diagram showing Move Bytecode flowing into a Deserializer, then Verifier, Code Cache, and finally Executor" /></p>
<p><strong>There are three ways we can get Move on Solana:</strong></p>
<ol>
<li><strong>Compile Move to SBF (like Solang):</strong> To implement this we might have to skip the Move Bytecode verification part, making it useless as we are not able to utilize all the advantages attached to Move.</li>
<li><strong>Add the Move VM as a native loader (alongside the SBF VM):</strong> This is a nice intuitive idea, but maintaining two different loaders in the runtime is not ideal for security (as it increases the attack surface by twice).</li>
<li><strong>Run the Move VM as a program:</strong> Out of all the options, this becomes inevitably the most reliable way to implement Move as it has already been carried out for running EVM on Solana (Neon).</li>
</ol>
<p>To approach the implementation of running the Move VM as a program, we would need to create a Move-LLVM compiler, this will become clear as we proceed forward in the article.</p>
<h3>LLVM Backend for Move</h3>
<p>So, LLVM? LLVM (Low-Level Virtual Machine) is a set of compilers and toolchains, used to create the front end of any programming language or backed for any instruction set architecture.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_8.Dwovl4cC_ome5o.webp" alt="LLVM’s Implementation of the Three-Phase Design" /></p>
<p>I know, LLVM can be intimidating to code. So why even try to do it? Because Solana runtime uses it.</p>
<p>First, The compiler breaks down the rust source code into lexemes, and then it verifies the correctness and validates the logic. In the second stage, the compiler generates the intermediate representation (IR) code. Now, the <em>LLVM optimizer</em> comes into play, which complies IR into optimized IR code (LLVM bytecode). In the end, the optimized IR code is translated into Solana Binary Format (SBF, a Solanaized eBPF).</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_9.BpRn0Bg2_Z2lJelR.webp" alt="Handwritten flow diagram showing Rust compiling to IR, then LLVM Optimizer to Optimized IR, and finally SBF" /></p>
<p>It makes sense to replace the Rust source code with the Move source code and generate IR code.</p>
<p><img src="https://satyam.cv/_astro/move__exploring_its_way_into_solana_10.Br6my2SX_ZiBgFb.webp" alt="Handwritten flow diagram showing Move compiling to IR, then LLVM Optimizer to Optimized IR, and finally SBF" /></p>
<p>In Rust, we have a few options to interface to LLVM:</p>
<ul>
<li><a href="https://docs.rs/llvm-sys/latest/llvm_sys/">llvm-sys</a> — raw unsafe bindings to the LLVM C API</li>
<li><a href="https://thedan64.github.io/inkwell/inkwell/index.html">inkwell</a> — idiomatic Rust bindings to <code>llvm-sys</code></li>
<li><a href="https://docs.rs/llvm-ir/latest/llvm_ir/">llvm-ir</a> — another high-level Rust binding</li>
</ul>
<p><strong>“llvm-sys”, llvm’s Rust C bindings stands out as the best option and it was used to develop the current</strong> <a href="https://github.com/anza-xyz/move/tree/llvm-sys/language/tools/move-mv-llvm-compiler"><strong>Move-LLVM backend</strong></a><strong>.</strong></p>
<p>::github{repo=“anza-xyz/move”}</p>
<h3>Challenges of Porting Move</h3>
<p>The Move language and the Move VM are tightly integrated. The Move bytecode must be validated on the Move VM before running as any other platform that doesn’t interpret the bytecode will lack the security and guarantee that comes with Move.</p>
<p>As all blockchains have a strong process boundary between any two programs, it’s very hard to see Move coming into the picture with SBF. This causes a barrier to run Move on Solana. Meaning that just converting the Move source code to IR is not enough. <em>Even after compiling it to LLVM, the Move code doesn’t have access to services provided by Move VM</em>.</p>
<p>The Move VM is a stack machine, and LLVM is a register machine, so how to translate one to the other is not immediately obvious. This could have become a technical challenge but we have, <em>Move Stackless Bytecode.</em> Move stackless bytecode is a transformation of the Move bytecode to a register machine model while utilizing the Move theorem prover.</p>
<p>Another technical problem that arises is how to deal with <strong>missing LLVM APIs</strong>. While LLVM C APIs have almost every feature, usually, developer needs to add their little library to expose the C++ APIs.</p>
<h2>Future?</h2>
<p>Now you might all agree that Move is an amazing piece of technology. And I believe having Move on Solana in the long run is worth it.</p>
<p>There will be more problems to be solved as the project moves forward. But when the project Move is concluded, I will be back with “Coding Move on Solana”.</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solana’s Versioned Transaction]]></title>
            <link>https://satyam.cv/solana_s_versioned_transaction</link>
            <guid isPermaLink="false">https://satyam.cv/solana_s_versioned_transaction</guid>
            <pubDate>Mon, 20 Nov 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Looking into the new lookup table Solana is fast; I mean, it allows hyper-parallelized processing of transactions, in turn enabling a high TPS. But there’s a catch. For even higher TPS, the transactio...]]></description>
            <content:encoded><![CDATA[<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_0.CfP7CxvX_lup6u.webp" alt="Solana logo on a dark, rippling sand-like background" /></p>
<h2>Looking into the new lookup table</h2>
<p>Solana is fast; I mean, it allows hyper-parallelized processing of transactions, in turn enabling a high TPS. But there’s a catch. For even higher TPS, the transaction size is limited to the IPv6 MTU standard, which is 1280 bytes, after accounting for headers, leaving 1232 bytes for serialized transactions.</p>
<p>One common workaround was to store the state temporarily on-chain and consume that state in later transactions. But it doesn’t work with more composition of on-chain programs where more account inputs are needed, each of which takes up 32 bytes. In summary, the problem is to increase the number of accounts that can be processed in a single transaction. The solution was proposed: why not just decrease the size of the addresses from 32 to one byte?</p>
<p>This is where the new <em>Versioned transaction (or transaction v0)</em> comes into the picture. The versioned transaction is an update to Solana runtime, introducing a new transaction format.</p>
<p>This article will explore exactly how everything works under the hood and how you can set up and use it on the go. In the first part of the article, we will describe how legacy transaction works to get the foundation right.</p>
<h3>Legacy Transaction</h3>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_1.dZgfJZGQ_Z23BgWg.webp" alt="Diagram showing a Legacy Transaction splitting into a Compact Array of Signatures and a Message, with the Message further splitting into Header, Account Addresses, Recent Blockhash, and Instructions" /></p>
<p>The legacy transaction is comprised of a compact array of signatures (64 bytes for each signature) and a message. The Legacy Message has four elements of data, which are,</p>
<ul>
<li>Header (3 bytes)</li>
<li><strong>A compact array of account addresses</strong> (32 bytes for each account)</li>
<li>Recent Blockhash (32 bytes)</li>
<li>Compact Array of Instructions</li>
</ul>
<p>If you don’t know what a compact array is, <em>“The compact array is an array serialized to have the number of items (special multi-byte encoding, compact-u16) and then all the items.”</em></p>
<p>The first component is the header, which has the number of required signatures, the number of read-only addresses that require signatures, and the number of read-only addresses that don’t require signatures. Each takes the space of a byte (u8), making it a total of three bytes.</p>
<p>Moving on to the compact array of instructions, each instruction has a program ID index (a byte, u8), a compact array of account address indexes, and opaque 8-bit data.</p>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_2.RxvqJuYS_ZabDRx.webp" alt="Diagram showing a Compact array of Instructions splitting into multiple individual Instructions containing Program ID Index, Account Addresses Indexes, and Opaque 8-bit Data" /></p>
<p>The next one is the compact array of account addresses, and here it contains the account address classified into requiring signature or not and further into read-only or read/write request. This is where the issues arise.</p>
<ol>
<li>The max. transaction size is 1232 bytes.</li>
<li>One account address is 32 bytes.</li>
<li><strong>If we take into account some headers, signatures, and other metadata, we are left with space only for a maximum of 35 accounts.</strong></li>
</ol>
<p>This doesn’t look good for a blockchain such as Solana, where you can only transact with 35 accounts simultaneously.</p>
<p>In fact, it is possible for an Ethereum account to send to multiple addresses within a single transaction. However, the block gas limit limits the number of addresses. The block gas limit that is currently in place is 12,500,000 gas. In a transaction that sends money to 100 addresses, almost 21,000 gas will be used. Consequently, it would mean that an Ethereum account can send to around 595 different addresses in one transaction.</p>
<p>The solution comes with the update to the legacy transaction in the versioned transaction (transaction v0). So, what’s comes with Transaction v0?</p>
<ul>
<li><strong>Introduces a new program that manages on-chain Address lookup tables (LUTs).</strong></li>
<li><strong>New Transaction format, which makes use of LUTs.</strong></li>
</ul>
<p>Now, let’s delve into the idea of Address Lookup tables,</p>
<h2>Address Lookup Tables (ALTs or LUTs)</h2>
<p>ALTs stores address in an on-chain array(table-like). After addresses are stored on-chain in an address lookup table account, they may be succinctly referenced in a transaction using a 1-byte u8 index rather than a full 32-byte address. As indices take u8 memory value, we can have a maximum of 256 accounts (2⁸).</p>
<p>So, if we need to accommodate 5 addresses in a legacy transaction, it takes 160 bytes, as in transaction v0, it will only take 37 bytes (after including the ALT’s address, which takes 32 bytes + 4 bytes for four accounts).</p>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_3.4dwWWJU0_15b9AT.webp" alt="Comparison for transaction memory for legacy and while using ALTs." /></p>
<p>ALTs need to be rent-free when initialized and after new addresses are appended each time. Addresses can added to the table using the on-chain buffer or by directly appending them to the table using the Extension instruction. (ALTs can also store associated metadata followed by the compact array of accounts).</p>
<p>Before versioning was introduced, all the transactions left an unused upper bit in the first byte of their message headers, which can now be used to declare our transactions’ version. Now, if the first bit is set, the other seven bits will encode a version number. In this case, “version 0” means you can use LUTs. If not, it will fall back to “legacy”.</p>
<pre><code>pub enum VersionedMessage {
   Legacy(Message),
   V0(v0::Message),
}
</code></pre>
<h2>MessageV0, What changed?</h2>
<ol>
<li><strong>Message Header:</strong> Unchanged</li>
<li><strong>Compact array of Accounts:</strong> Unchanged</li>
<li><strong>Recent Blockhash:</strong> Unchanged</li>
<li><strong>Compact array of instructions:</strong> Change from Legacy</li>
<li><strong>Compact array of ALTs:</strong> Introduced in V0</li>
</ol>
<p>The Compact array of ALTs is now compromised of the number of address lookup tables (in compact-u16 encoding) and an array of all address lookup tables.</p>
<p>Each Address lookup table in the array has three components →</p>
<ol>
<li><strong>Account Key</strong> (u8)</li>
<li><strong>Writable Indexes</strong>: Compact array of writable account address indexes</li>
<li><strong>Read-Only Indexes</strong>: Compact array of read-only account address indexes</li>
</ol>
<p>If we talk about changes in the compact array of instructions, the structure remains unchanged, but how it’s processed becomes a little different.</p>
<p>To remind the structure of a compact array of instructions, we have,</p>
<ol>
<li>Program ID index</li>
<li>A compact array of account address indexes</li>
<li>A compact array of opaque 8-bit data</li>
</ol>
<p>Only the compact array of accounts stored in the message is used in legacy transactions. <strong>But in v0, both the compact array of accounts stored in the message and indexes from the lookup table are used.</strong> Check <a href="https://docs.solana.com/proposals/versioned-transactions#new-transaction-format">here</a> for the new transaction format.</p>
<p>Enough theory behind the versioned transaction. From a developer’s point of view, we should look into what changes have been made in RPC and overall coding.</p>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_4.ChilNifK_Zq1cK5.webp" alt="Meme of a skeleton sitting on a bench with the caption 'ME WAITING FOR SOME CODE'" /></p>
<h2>RPC Changes</h2>
<p>Some methods that got updated are:</p>
<ol>
<li>getTransaction</li>
<li>getBlock</li>
</ol>
<p>The methods will require the following parameter to indicate which transaction structure needs to be followed for the deserialization:</p>
<pre><code>maxSupportedTransactionVersion: 0
</code></pre>
<p>If this is specified, it will account for it as a versioned transaction or fall back to a legacy transaction. Any block utilizing the versioned transaction will give an error in the case of the legacy transaction request.</p>
<p>It’s also important to note that versioned transactions allow users to create and use another set of account keys loaded from on-chain address lookup tables, so it’s recommended to use <em>“jsonParsed” encoding</em>.</p>
<pre><code>"encoding": "jsonParsed"
</code></pre>
<p>Example of a JSON request <strong>to the RPC:</strong></p>
<pre><code>curl &lt;http://localhost:8899&gt; -X POST -H "Content-Type: application/json" -d \\
'{"jsonrpc": "2.0", "id":1, "method": "getBlock", "params": [430, {
  "encoding":"jsonParsed",
  "maxSupportedTransactionVersion":0,
  "transactionDetails":"full",
  "rewards":false
}]}'
</code></pre>
<p>Now, let’s talk about how you can send a versioned transaction request,</p>
<h2>Sending a Versioned Transaction</h2>
<p>Initially, if you wanted to send the legacy transaction, the code would have looked like this,</p>
<pre><code>const {
  context: { slot: minContextSlot },
  value: { blockhash, lastValidBlockHeight },
} = await connection.getLatestBlockhashAndContext();
const transaction = new Transaction({
  feePayer: publickey,
  recentBlockhash: blockhash,
})
transaction.add(
  new TransactionInstruction ({
    data: Buffer.from( 'Hello, Solana!'),
    keys: [],
    programId: new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'),
  })
);
const signature = await sendTransaction(transaction, connection, { minContextSlot });
await connection.confirmTransaction({ blockhash, lastValidBlockHeight, signature });
</code></pre>
<p>Developers now have to use a new class, <code>VersionedTransaction</code> instead of <code>Transaction</code>. So, while building the versioned transaction, it’s similar to a legacy transaction except for the new <code>VersionedTransaction</code> Class.</p>
<pre><code>// create array of instructions (here, transfer instruction)
const instructions = [  SystemProgram.transfer({
    fromPubkey: publicKey,
    toPubkey: publicKey,
    lamports: 10,
  }),
];
// create v0 compatible message
const messageV0 = new TransactionMessage({
  payerKey: publicKey,
  recentBlockhash: blockhash,
  instructions,
}).compileToV0Message();
// make a versioned transaction
const transactionV0 = new VersionedTransaction(messageV0);
</code></pre>
<p>Once the instruction is set, the transaction can be signed and sent using the <code>signAndSendTransaction</code> method to the desired provider,</p>
<pre><code>const provider = getProvider();
const network = "&lt;NETWORK_URL&gt;";
const connection = new Connection(network);
const versionedTransaction = new VersionedTransaction();
const { signature } = await provider.signAndSendTransaction(versionedTransaction);
await connection.getSignatureStatus(signature);
</code></pre>
<h2>Forging an Address Lookup table</h2>
<p>We can use the <code>AddressLookupTableProgram</code> inside<code>@solana/web3.js</code> to construct the lookup table effectively. Use the <code>createLookupTable</code> function to build the table, then create a transaction, sign it, and send it to create a lookup table on-chain.</p>
<pre><code>// create an Address Lookup Table
const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({
  authority: publicKey,
  payer: publicKey,
  recentSlot: slot,
});
// to create the Address Lookup Table on chain
const lookupMessage = new TransactionMessage({
  payerKey: publicKey,
  recentBlockhash: blockhash,
  instructions: [lookupTableInst],
}).compileToV0Message();
const lookupTransaction = new VersionedTransaction(lookupMessage);
const lookupSignature = await signAndSendTransaction(provider, lookupTransaction);
</code></pre>
<p>We created a Lookup table on-chain and got the address, but we need to extend the table, i.e., appending some account addresses to it.</p>
<p>Easy, just use the <code>extendLookupTable</code> method. Here is how it goes…</p>
<pre><code>// add addresses to the Lookup Table Address via an `extend` instruction
const extendInstruction = AddressLookupTableProgram.extendLookupTable({
  payer: publicKey,
  authority: publicKey,
  lookupTable: lookupTableAddress,
  addresses: [    publicKey,
    SystemProgram.programId,
    // more `publicKey` addresses can be listed here
  ],
});
// send the transaction to update the data on-chain
const extensionMessageV0 = new TransactionMessage({
  payerKey: publicKey,
  recentBlockhash: blockhash,
  instructions: [extendInstruction],
}).compileToV0Message();
const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0);
const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0);
</code></pre>
<p>Awesome! We created the lookup table, but how about we use them now?</p>
<h2>Utilizing the Address Lookup table</h2>
<p>First, fetch the accounts of the Address lookup table we created and read all the addresses.</p>
<pre><code>// get the Lookup table
const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) =&gt; res.value);
console.log('Table address:', lookupTableAccount.key.toBase58());
// read all the address stored in the table
for (let i = 0; i &lt; lookupTableAccount.state.addresses.length; i++) {
  const address = lookupTableAccount.state.addresses[i];
  console.log(i, address.toBase58());
}
</code></pre>
<p>Next, use the addresses with any desired instruction.</p>
<pre><code>// create an your desired `instructions` (here, transfer instruction)
const instructions = [  SystemProgram.transfer({
    fromPubkey: publicKey,
    toPubkey: publicKey,
    lamports: minRent,
  }),
https://github.com/satyvm/solana-versioned];
// create v0 compatible message
const messageV0 = new TransactionMessage({
  payerKey: publicKey,
  recentBlockhash: blockhash,
  instructions,
}).compileToV0Message([lookupTableAccount]);
// make a versioned transaction
const transactionV0 = new VersionedTransaction(messageV0);
const signature = await signAndSendTransaction(provider, transactionV0);
</code></pre>
<p>But you might be thinking now, I want to try out, here you go…</p>
<h2>Get your hand dirty</h2>
<p>Let’s get on with this! Clone a repo using the following command,</p>
<p>::github{repo=“satyvm/solana-versioned”}</p>
<pre><code>git clone https://github.com/satyvm/solana-versioned
</code></pre>
<p>Then, install all the required libraries using</p>
<pre><code>yarn
</code></pre>
<p>Pop into the <strong>createTable.ts</strong> file and <strong>put your private key</strong> in the 9th line of code. <em>Also, remember to put some Solana Devnet Faucet to the account are using.</em> Afterward, run the following command to create a lookup table. (For example, mine was like <a href="https://explorer.solana.com/address/HZbtckxbYxdtkEkuEwnC6zSiwnVMHaMzySGDjmeKoiiY?cluster=devnet">this</a>)</p>
<pre><code>yarn ts-node createTable.ts
</code></pre>
<p>Get your Lookup table address from the terminal. It will look something like this (<a href="https://explorer.solana.com/tx/2zkdcKQmCWjG21ngta2gyHVixebMBwa4swca8C8mGqjYnGGi8kDKExQcTwn8XF9FWZcSePkxTpJfZtgHA3tgH7Fm?cluster=devnet">here</a> the lookup table address is 4iSg5upfgCrLAmcfBRaDxcqwKbvjTZti9bDjyqhmJrq6)</p>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_5.XS86qB15_JV86l.webp" alt="Screenshot of a terminal showing the successful execution of yarn ts-node createTable.ts" /></p>
<p>Open the <strong>app.ts</strong> file and put your private key and lookup table address in their places (lines 9 &amp; 15). Lastly, run the file using,</p>
<pre><code>yarn ts-node app.ts
</code></pre>
<p>Your terminal will look something like this,</p>
<p><img src="https://satyam.cv/_astro/solana_s_versioned_transaction_6.Dc7Gpj-__XyrbT.webp" alt="Screenshot of a terminal showing the execution of yarn ts-node app.ts, displaying lookup table entries and transaction confirmation details" /></p>
<p>Congratulations! 🎉 You’ve successfully run the code. If you observe, you will see the transaction size is hugely reduced. That’s how much the lookup table is effective.</p>
<p>This was a very deep dive into the inner workings of the new versioned transaction Solana introduced.</p>
]]></content:encoded>
        </item>
    </channel>
</rss>