A read-only reentrancy attack exploits the fact that when a contract calls another contract (like Curve's remove_liquidity), the receiving contract's receive() function executes before the original call finishes, allowing an attacker to read inflated or stale state values (such as virtual price) during the intermediate execution phase, which can be leveraged to manipulate reward calculations or other dependent logic in target contracts.
Read-Only Reentrancy Explained | Solidity Security (0.8)
Added:in a re-entrancy hack a contract will call a Target contract this Target contract will call back into the caller in this case the hack contract well the first call has now finished this hack contract will call back into the Target contract this is re-engine C hack a read-only re-entracy is a little bit more complicated first the hack contract will call into a contract we'll call this contract date contract day will call back into the caller in this case it will be the hack contract at this moment the call made in the first step to contract day has not yet finished while the call in the first step is not yet finished this hack contract will call into a Target contract the Target contract will read some state from contract day finish executing step 3 after the function call to the Target contract finishes then the execution to step 2 finishes and thus leader execution to step 1 finishes let's take a look at an example for this example I'll be using Foundry so first let's install it I'll install Foundry by copying and pasting this command into my terminal all of the command and the codes that I'll be typing I'll put it up on GitHub the next step of installing Foundry is to type Foundry up so I'll type Foundry up next I'll initialize The Foundry project inside this directory by typing Forge init okay once that command executed successfully if I check the current directory you can see some files that Foundry put in and we're now ready to write some code for the read-only reentrc example for this example you'll simulate a read-only reentrancy attack on the curb sde pool and we'll be executing this on the main Network so we won't be actually hacking the contract we will just be running a simulation first I'll open source folder and then remove this boilerplate code and then I'll create a new contract called hack dot so first I'll paste the solidity version next I'll import console.tool so that you'll be able to console log some values inside a transaction next I'll set the address for the curb sde pool and the token that you get when you deposit into this pool I'll name it LP for liquidity provider we're going to be hacking the curb contract so I'll paste the interface for the curb for this example the functions that we're going to be calling is get virtual price add liquidity and remove liquidity when you call this function get virtual price it Returns the value of the shares the higher the value of the shares the more tokens that you'll get that is locked inside the pool when you call the function withdrawal we can add the tokens to this curve pool by calling the function add liquidity and to remove the tokens from this pool we'll be calling the function remove liquidity we're also going to need the irc20 interface so I'll paste it here okay so next let's write the hack contract so I'll type contract hack first let's set some contact addresses I curve this will be private constant and I'll name it pool and this is equal to I curve this is the interface that we defined above and the address of the pool that we'll be calling is St E4 I also set the irc20 for the lp tokens so ierc 25A constant LP token and this will be irc20 for the lp this is the token that we'll receive when we call the function at liquidity on the purple next I'll write the function called Palm this will be external and payable since we will be sending some leave this will be the main function that we will be calling to initiate the attack so what we're going to be doing is to First add liquidity to the curve pool next we'll log the value of the shares by calling the function get virtual price log get virtual price and then we'll remove liquidity by calling this function we'll be able to trigger the read-only reagency attack when we call the function remove liquidity on the curb as the E4 and then if I scroll down you can see here that it sends the if back before the function finishes executing so this means that when we call the function remove liquidity the code will execute and then before it finishes executing all of the code it will send some leave so this is where we can do the reagency back inside my code editor and back inside our contract when we call the function remove liquidity at some point it would send the if back to this contract so inside this contract I'll Define a receive external payable and this is where we will write our rest of our code to execute the read-only reentrancy in this example to show you the get virtual price will be higher while we're still executing remove liquidity inside here we will log get virtual price again okay let's write our code the first thing is to add liquidity now if I scroll up to call add liquidity on the curvepool we'll need to prepare a array up to specifying the amount of tokens to add and the minimum amount of LP tokens to make so scroll down first we'll initialize the inventory of two unit two memory or call it amounts and this will be equal to the first value represents the amount of beef that we're going to be sending so you'll be sending message.value the second amount represents the amount of stf that we're sending we will be sending zero and then we'll add liquidity by calling pull dot add liquidity passing in the amounts and the minimum amount of LPS that will be minting we'll say one and when we call this function that liquidity since this is the if sde4 we'll have to also send the amount of beef that is specified over here so say value is message dot value when we call this function that liquidity it Returns the amount of LP tokens that were minted so I'll say uint LP equals to and that is ADD liquidity next we'll log the virtual price the value of one share of these LP tokens so I'll say console.log then I'll type before remove LP virtual price and then call the function pool dot get virtual price and then we'll remove liquidity let's scroll up to call remove liquidity we'll need to pass in the amount of LP tokens that we're going to be burning and the minimum amount of underlying token this will be if and sde that we expect to receive so scroll down we'll prepare a uint array of size 2 unit 2 memory Min amounts for this example I'll just say minimum amounts at 0 U and zero uint zero and then we'll call the function remove pool Dot remove liquidity passing in the amount of LP tokens that we're going to be burning we'll withdraw all of it so we'll pass in all of our LP tokens and pass in minimum amounts when we call this function remove liquidity at some point it will send us back the eve which will trigger the receive function so inside here we'll log the get virtual price so I'll copy this paste it here and then say during remove liquidity log the virtual price okay this completes the first step of our hack contract what we're trying to see here is that while we're calling remove liquidity we should see that get virtual price is higher and by confirming that get virtual price will be higher while this part of the function is executing we'll be able to write our exploit inside this receive function so let's first check the get virtual price is actually higher inside this part of the code so I'll open my terminal and then I'll try to compile the contract by typing Forge build now notice that the contract didn't compile because there was a contract in the test file that we deleted so I'll go fix that right now open the test and then remove the Imports and then remove all of the code inside the test and let's try compiling again okay our contract compiles let's now write the test a rename counter to hack.t.soul and also rename the test contract to hack test instead of importing a counter we'll import the hack contract and then inside our test contract I'll also import the console from Forge STD so copy this paste it here and then first we'll initialize the hack contract so I'll type hack public hack and then we'll write the function to set up the test function set up public then we'll initialize the hack contract pack is equal to new hack next we'll write a function to test our poem function function test all public and then we'll call the function hack dot poem when we call this function let's send hundred thousand if so I'll say value 100 000 times 18. okay that's execute the test so when we call the function Palm we should see that get virtual price will be higher inside the receive function compared to what we get when we call the function get virtual price inside the pawn so I'll open my terminal clear the logs and then we'll be running a test on the main Network I'll copy the address for the fork URL that I got from Alchemy API I'll paste this command sending the fork URL to the Alchemy API and then we'll execute the test by calling Forge test Dash I'll put in Four B's BBB and this will print out of other logs when we run the test then we'll say fork URL is Fork URL from what we set above and then hit enter okay our test finished executing and you can see that there is a lot of logs this is because if I scroll up I put in Four B's and this means that it would print a lot of logs for this execution what I am interested is in the virtual price before removing liquidity and the virtual price during removing liquidity let's take a look before removing liquidity it is this amount that you see over here and during when we're removing liquidity you can see that the virtual price has gone up by a little bit ecf5 over here and you see a 9 over here what this means is that if we target another contract that depends on Virtual price and then call that contract while we're removing liquidity we'll be able to exploit that contract that will be the next step okay I'm back in my hack contract and what we're going to do next is write an example contract to Target a contract to exploit inside the function received so first I'll write a Target contract that we'll be using for this example so I'll say contract Target and let's imagine that this Target contract you'll be able to State the lp tokens and you'll get some kind of rewards where the rewards is calculated based on the value returned by get virtual price so first I'll copy these to contract addresses and instead of LP token here I'll name it token I'll rename it to token so let's imagine that this contract has three functions a function to stake the tokens a function to unstake the lp tokens and some kind of function to get rewards based on the amount of tokens that we stick function get viewed okay let's fill in the details so stake it will take in the amount of LP tokens to State this will be external and when we call this function let's say that it transfers the token transfer from from message dot sender to this contract address this for the amount amount from the input and then let's say that it keeps track of the amount of tokens that is state so create a mapping from address to uint this will be public I'll name a balance up then when we stake it will update the balance of for message dot sender incremented by amount okay scrolling down when we on stake we'll be able to unstick the amount this will be external when we on state we'll first update the balance of message dot sender decremented by amount and then we'll transfer the token token dot transfer to message dot sender for the amount okay the last function we'll write is get rewards so this will be external returns let's say that it just Returns the amount of tokens amount of rewards tokens that we earn we'll set the amount of rewards token that we earn is we multiply the amount of state by the value of shares and the value of shares we get it by calling get virtual price on the curvepool so say unit reward is equal to balance of message dot sender times get virtual price pool dot get virtual price now both the lp tokens and get virtual price has 18 decimals so what we'll have to do is divide by 10 to the 18. if we multiply balance all which has 18 decimals and get virtual price which has 18 decimals this multiplication now has 36 decimals so to get it back down to 18 decimals again we'll have to divide by 10 to the 18. and then we'll have some code to transfer the reward go to transfer reward but for this example we'll just omit it escape code to transfer reward and for this example we'll just return the amount of reward that was calculated okay let's try compiling this contract clear the logs and then type Forge build look at the contract compiled successfully so let's move on to write some exploit inside the hack contract so scrolling down to the hack contract first I'll store the Target contract inside the hack contract so I'll say Target private immutable Target and then Define a Constructor Constructor we'll take in the address of the target and then we'll set the target target equal to Target address of Target now what we're going to do is before we call the function poem we'll stake some LP tokens into the Target contract and then afterwards we call the function poem this will eventually call remove liquidity which we'll call the receive function inside here we know that get virtual price is overpriced so inside here we'll call the function get reward On Target and we should get more rewards than what we should have if we didn't do the exploit okay so let's do that so first I'll create a function called set up this will be external payable and inside here we'll deposit we'll stake some tokens into the Target contract so first we'll need to add liquidity copy this paste it here and then we will transfer this LP token over to the Target contract so LP token Dot approve address of the target for the amount LP and then call Target Dot take LP next we'll execute the pawn function and this will execute remove liquidity which will execute receipt and inside here let's get the reward by calling uint reward is equal to Target dot get keyboard and then for this example we'll log the amount of rewards that we earned if we had called get reward inside the receive function so say console.log reward reward and then we'll also compare this amount of rewards that we would have got compared to how much reward we will get if we call it after the read-only reentrancy is done so if we did not execute our read-only reentrency how much reward will we get I'll copy these two code and then paste it here okay let's update our test so back inside the hack test contract I know that we'll need to deploy the Target contract so Target public Target and then we'll deploy the Target contract first Target is equal to new Target and now the hack contract takes in the address of the target so inside the Constructor I'll pass in the address of the Target and then before we call the function Palm you'll need to call the function setup to stake some of our LP tokens into the Target contract so here hack not setup and when we call this function setup we'll get some LP tokens from the pool from the curb SD E4 and then stake it so let's send some if so I'll say value we'll send 10 if 10 times 10 to the 18. okay let's try compounding contract I'll open the terminal clear the logs and then type Forge build and a contract compiles so let's now run the test again and our test was successful let's check the logs scroll up before we remove liquidity get virtual price return some amount during the lp is being removed so this will be the code that was executed inside the receive function get virtual price was slightly higher and when we call the function get reward we would have gotten this much amount whereas after remove liquidity is done executing and then we call get reward we get a slightly thus amount this means that if we were to call get reward while the liquidity was being removed then we would have gotten more rewards than calling get reward after recalled remove liquidity that was an example of read-only reentrancy where we wrote Our hack contract and then we called the curb SD info we called it by calling the function remove liquidity and then while the remove liquidity is executing it transferred some if and then inside here it executed the code inside the receive which allowed us to call the Target contract get keyboard get reward calculated the amount of rewards by calling get virtual price inside the curve pool at this point the virtual price is slightly higher than what it should be so by the time all of this finished executing we were able to get more rewards than what we would have if we did not execute the read-only re-engency
Up Next

Ethereum Smart Contract Auditing with Slither: Solidity Security Analysis
@fuzzinglabs
6.4K views•2022-06-16

Torrent File Format & Bencoding: A Technical Deep Dive
@AsliEngineering
12.5K views•2022-08-08

Arbitrage Algorithm for Uniswap V2 and V3 Pools
@smartcontractprogrammer
166 views•2026-02-18

Understanding Ethereum: A Comprehensive Beginner's Overview
@99Bitcoins
3.1M views•2018-06-26
Related Study Plans & Knowledge Roadmaps
Structured learning paths in Blockchain & Crypto




























![Fundamentals Lecture #12 [SP23]](https://i.ytimg.com/vi/5tCLOL2tuK4/maxresdefault.jpg)





