Cointime

Download App
iOS & Android

Runtime Environment and Smart Contract Security Modeling

Validated Project

The design and runtime of different blockchains alter the security of smart contracts built on top of them. There are a number of reasons for this: developers must use a new domain-specific language; transaction executions may involve asynchronous function finality; and tooling is not always equal for different blockchain environments.

In this blog post, we will explore how security changes based on different runtime models. In particular we will compare the runtime assumptions for EVM smart contracts and a similar set of assumptions for NEAR smart contracts. We will then look into how this alters the design of a secure smart contract.

We will also highlight an attack vector found while auditing NEAR contracts which we will name an insolvency attack. While this attack is known to the some in the NEAR community, it is still not a well known attack. Further, it can effect any blockchain that requires additional fees for creating new storage.

Abstract Liquid Staking Contracts

We will begin by defining an abstract liquid staking contract. We will use this contract to understand how the runtime of a blockchain effects the security of a smart contract.

Our staking contract will have some common functionalities found in an ERC20-type contract. The common functionalities will be to track the token balance of a given user and allow for transfers of tokens between users. There are three functions that change state: transfer; transferFrom; and approve. Between two user’s Bob and Alice they are able to generally change the state as follows:

  • transfer

a. Call - Bob calls transfer with input amount x and to address Alice.

b. Effect - The contract updates Alice’s balance with x tokens and removes x tokens from Bob’s balance.

  • approve

a. Call - Bob calls approve with input allowance x and to address Alice.

b. Effect - The contract updates Alice’s allowance x with respective to Bob’s balance to allow Alice to call transferfrom on Bob's tokens.

  • transferFrom

Call - Alice calls transferFrom with input amount x input, from address Bobs account, and to address an Sally's address

Effect - The contract updates Sallys’s balance with x tokens and removes x tokens from Bob’s balance and removes Alice’s allowance x with respective to Bob’s balance.

The remaining functions of our liquid staking will be the following.

  • depositAndStake

a. Call - Alice calls depositAndStake with x input amount

b. Effect - The contract checks whether there is sufficient attached GAS as input x. If there is then the contract mints Alice stGAS tokens and deposits the GAS into a node. It then calls depositandStakeCallback

  • depositAndStakeCallback

a. Call - The contract can only call this function internally with no inputs.

b. Effect - It checks whether the Gas deposit is successful. If successful it returns true and otherwise it reverts the state updates and sends the user their GAS tokens back.

  • withdraw

a. Call - Alice calls withdraw with input amount x

b. Effect - The contract unstakes Alice’s GAS tokens from the node and calls transferFrom on an equivalent amount of stGAS tokens that Alice owns. If Alice does not hold a sufficent amount of stGAS then the transaction reverts.

EVM Runtime Model and Asynchronous Function Calls

The runtime model for the EVM is well known. A general set of axioms assumed by Solidity developers are seen below:

  • If there is sufficient amount of gas then transactions may contain numerous and complex function calls;
  • If a transaction is executed then function calls are synchronous;
  • There is no difference in gas cost of a transaction if a function call increases the storage usage of an existing contract.

This model lead to an interesting family of attack surfaces called reentrancy attacks. While solutions for reentrancy attacks are now well known, there are still many different examples of reentrancy that are more difficult to detect such as multifunction reentrancy and read only reentrancy.

Using our liquid staking contract, let us review how a simple reentrancy attack would work on a EVM-style smart contract using our psuedo-code below.

    function withdraw(uint amount) {
        require(balances[msg.sender] >= amount);
        msg.sender.transfer(amount);       
        balances[msg.sender] -= amount;   
        ...
        ...
    }

Reentrancy attack flow for synchronous function calls:

  • Suppose Bob is a smart contract and our liquid staking contract controls 100 GAS tokens.
  • If Bob holds at least 1 stGAS token then Bob can call withdraw and re-enter via the fallback.
  • The final withdraw will then complete the function call.

The check-effect-interact pattern is one solution to this type of reentrancy. In the example this is done by switching the line responsible for transferring with the line that updates balances. However, is this solution still secure if function calls are no longer synchronous? No! A concrete example can be found in Near smart contracts.

Lets look at the following psuedocode for a liquid staking contract. This example, we will assume that all external function calls are assumed to be finalized in the next block. Further note that the follow is based off the documentation from NEAR and is only meant to model a re-entrancy attack.

   fn depositAndStake(){
     let user = msg.sender();
     let amount = msg.value();
     balance[user]= balance[user] + amount;

     #External contract call  - Takes 1 block to finalize

     validator.deposit_NEAR_and_stake()
     .attachNEAR(amount).thenCallback(depositAndStakeCallback)

   }

   fn withdraw(amount: u32){
     if user has sufficent balance:
       balance[user] = balance[user] - amount
       msg.sender.transfer(amount); 
       ...
       ...
   }

   function depositAndStakeCallback{
   if deposit Failed:
       # Also assume not reverts on underflows 
       balance[user] = balance[user] - amount
       msg.sender.transfer(amount);
   }
   ...
   ...

} 

Notice that in the above contract, the withdraw function follows check-effect-interact pattern. However it is still vulnerable to reentrancy as follows:

  • Suppose Bob is a smart contract on Near and suppose our liquid staking contract controls 100 Near tokens.
  • Bob calls depositAndStake with 49 Near attached at block zero.

Bob calls the withdraw function at block zero then Bob will receive 49 Near.

However, the callback function depositAndStakeCallback is executed at block one and Bob will receive another 49 Near.

Note that Bob is able to call depositAndStake and withdraw within the same transaction. This is because the NEAR blockchain allows for batched function calls. The key here is that the withdraw function is finalized before the depositAndStake has completed the external function call.

Reentrancy attacks are still permissible when function calls are asynchronous however in a slightly different form. One solution to protect against such an attack one is to follow a Check-Interact-Effect in combination with a local Mutex for certain situations. See below for how the Check-Interact-Effect would work here. Further to see the full example, please check the excellent explanation found in the NEAR documentation.

    fn depositAndStake(){
      let user = msg.sender();
      let amount = msg.value();

      #External contract call  - Takes 1 block to finalize

      validator.deposit_NEAR_and_stake()
      .attachNEAR(amount).thenCallback(depositAndStakeCallback)

    }

    fn withdraw(amount: u32){
      if user has sufficent balance:
        balance[user] = balance[user] - amount
        msg.sender.transfer(amount); 
        ...
        ...
    }

    function depositAndStakeCallback{

    if deposit Successful:
    balance[user]= balance[user] + amount;

    if deposit Failed:
        # Also assume not reverts on underflows 
        msg.sender.transfer(amount);
    }
    ...
    ...

} 

Near Runtime Model and Storage Staking

In the previous section, we saw how the security model of our smart contract can change if function calls are no longer synchronous. In this section, lets see how security will change if gas increases when the state of a smart contract increases.

On the NEAR blockchain, the storage of on-chain data requires the deposit of NEAR tokens into the account that contains such data. This mechanism is called Storage Staking. Any piece of data is subject to the deposit including: account metadata; smart contract bytecode; data generated by function calls on smart contracts. The amount of Near needed for Storage Staking is defined to be the stored data length multiplied by the cost in byte.

As such we can update the general axioms for Near developers as follows:

  • If there is sufficient amount of gas then transactions may contain numerous and complex function calls;
  • If a transaction is executed then external function calls are asynchronous;
  • The gas cost of a transaction is defined to be the sum of the gas cost of the function call and a storage staking fee. If there is not enough storage staking fee then all function calls will revert and all other funds deposited in the contract are locked.

The introduces a new problem not found when writing EVM smart contracts. If a generic user can call a function that stores new on-chain data then the smart contract logic must verify there is sufficient funds for the Storage Staking. Otherwise a generic user can cause a denial of service attack on the smart contract. This attack is referred to as the Million Small Deposits attack.

Evgeny Kuzyakov from the Near Social team has provided a solution for storage staking found in NEP-145. However, even with this solution if storage staking is not properly calculated between contracts this opens up a new attack vector which we will call an insolvency attack.

Insolvency Attack on Liquid Staking Contract

To minimize complexity let us suppose that our Liquid staking contract does not enforce a storage staking fee in the function depositAndStake. However the withdraw function refunds a storage staking fee.

    fn depositAndStake(){

      let user = msg.sender();
      let amt = msg.value();
      # increases contract size if user is calling for the first time.
      balance[user]= balance[user] + amt;

      #External contract call   

      validator.deposit_NEAR_and_stake()
      .attachNEAR(amount).thenCallback(depositAndStakeCallback)

    }

    fn withdraw(amount: u32){
      if user has sufficent balance:
        balance[user] = balance[user] - amount
        msg.sender.transfer(amount);
        refund storage fee  
        ...
        ...
    }

Insolvency attack flow:

  • Suppose our liquid staking contract contains 100 Near such that 50 Near is the necessary storage fee for 200 accounts. The remaining 50 is rewards obtained by staking.
  • Further suppose that a new account requires 0.5 Near for storage staking to call depositAndStake.
  • Bob calls depositAndStake with the input amount 0.01.
  • Bob then batch calls the function withdraw with input amount 0.0001.
  • Bob is able to drain 50 NEAR from the Liquid staking and the contract will revert function calls that increase state.

While the above example attack is trivial to see, in practice it is much more difficult to detect for a large system of smart contracts. Further, this scenario does not appear to be logical. Why would a contract refund NEAR when calling withdraw if the balance[user] is not even 0?

This scenario has appeared while auditing NEAR smart contracts. In particular, a bridge can have escrow contracts on either side such that one side of the bridge will collect storage fees and the other side of the bridge will release storage fees. However if both sides do not have the correct storage accounting then a user can abuse the storage fee release. That was an issue with Calimero bridge which we will explore in more detail in the next post. 

Conclusion

Smart contract security is influenced by the underlying blockchain's runtime and the virtual machines that define the programming language. Different models of security are needed for different blockchains.

While there is already a large number of well known security models for different blockchains, the number of models will continue to increase as new blockchains are launched.

Further, we have seen that smart contract security is not composable between different models. In this blog, we provided a number of examples illustrating how NEAR and EVM smart contracts can differ.

One aspect that remains important however not explored in this post is how different smart contract security models interact with each other. These intersections remain critically important as they are found in bridges and other inter-blockchain communication systems. This topic will be explored in another blog post.

Read more: https://www.certik.com/resources/blog/3eSV9gLNNCcIwTt8Qe78Kb-runtime-environment-and-smart-contract-security-modeling

Get the latest news here: Cointime channel — https://t.me/cointime_en

Comments

All Comments

Recommended for you

  • El Salvador Accumulates 7 BTC in the Last 7 Days, Total Holdings Reach 7,751.37 BTC

    On August 23, according to data from the El Salvador Ministry of Finance, El Salvador has accumulated 7 bitcoins in the last 7 days, bringing its total bitcoin holdings to 7,751.37 BTC, valued at approximately $599 million.

  • El Salvador Increases Bitcoin Holdings by 7 BTC in the Last 7 Days

    On August 23, according to data from the Ministry of Finance of El Salvador, the country has accumulated an additional 7 bitcoins in the last 7 days, bringing its total bitcoin holdings to 7,751.37 BTC, valued at approximately $599 million.

  • Global Bond Market Faces Sell-off, Panda Bond Issuance Reaches Record High

    According to CCTV Finance, the long-term government bond yields of major global economies have continued to rise recently, intensifying sell-off pressure in the bond market. However, China's bond market and exchange rate have remained relatively stable, with the issuance of Panda bonds reaching a historical high for the same period. Data shows that as of August 21, the cumulative issuance of 2026 Panda bonds has reached 209.975 billion yuan, an increase of over 73% year-on-year. Against the backdrop of significant fluctuations in the global bond market, the increased interest from international institutions in domestic RMB financing has drawn attention. Industry insiders noted, 'We are in completely different economic and monetary cycles compared to overseas. Foreign capital accounts for only about 5%-8% of our bond market, while domestic capital holds absolute pricing power. Coupled with our monetary policy that prioritizes domestic considerations, overseas shocks cannot reverse the overall trend of the domestic bond market.' Looking ahead, industry experts believe that overseas bond yields are likely to remain highly volatile, highlighting the value of RMB bond allocations, and foreign capital may continue to increase its allocation in the medium to long term. However, it is also important to note that rising U.S. bond yields have raised the return threshold for global allocation funds, which may disrupt the willingness of foreign institutions to increase their holdings of RMB bonds. Additionally, the rapid rise in bond yields in developed countries overseas may also constrain the valuation of domestic risk assets.

  • Global Bond Market Faces Sell-Off, Panda Bond Issuance Reaches Record High

    According to CCTV Finance, recent long-term government bond yields in major global economies have been rising, increasing sell-off pressure in the bond market. In contrast, China's bond market and exchange rate have remained relatively stable, with Panda bond issuance reaching a historical high for the same period. Data shows that as of August 21, the cumulative issuance of 2026 Panda bonds has reached 209.975 billion yuan, a year-on-year increase of over 73%. Amidst significant fluctuations in the global bond market, international institutions are increasingly focusing on domestic RMB financing. Industry insiders noted, 'We are in completely different economic and monetary cycles compared to overseas. Foreign capital accounts for only about 5%-8% of our bond market, while domestic capital holds absolute pricing power. Coupled with our monetary policy that prioritizes domestic conditions, overseas shocks cannot reverse the overall trend of the domestic bond market.' Looking ahead, industry experts believe that overseas bond yields are likely to remain highly volatile, highlighting the value of RMB bond allocation, which may attract continued foreign investment in the medium to long term. However, it is also important to note that rising U.S. bond yields have raised the return threshold for global allocation funds, which may disturb the willingness of foreign institutions to increase their holdings of RMB bonds. Additionally, the rapid rise in yields of bonds from developed countries overseas may also constrain the valuation of domestic risk assets.

  • Whale Transfers 1,727 Bitcoins Worth $133 Million to Binance

    According to monitoring by Bitcoin News, a whale has transferred 1,727 bitcoins, valued at $133 million, to Binance.

  • A Whale Transfers 1,727 Bitcoins Worth $133 Million to Binance

    According to monitoring by Bitcoin News, a whale has transferred 1,727 bitcoins to Binance, valued at $133 million.

  • Canadian Prime Minister: Tariff Measures Against the U.S. to Take Effect on September 8

    On August 22, Canadian Prime Minister Carney announced that Canada's tariff measures against the United States will take effect on September 8. (Jin Ten)

  • Canadian Prime Minister: Tariff Measures Against the U.S. to Take Effect on September 8

    On August 22, Canadian Prime Minister Carney announced that Canada’s tariff measures against the United States will take effect on September 8. (Jin Shi)

  • Over $1.244 Billion Liquidated in 24 Hours, Majority in Long Positions

    On August 22, according to CoinGlass data, the total liquidation amount in the cryptocurrency market reached $1.244 billion in the past 24 hours, with long positions accounting for $744 million and short positions for $500 million. A total of 246,203 individuals were liquidated. Among them, the liquidation amount for BTC was approximately $194 million, for ETH about $277 million, for SOL around $104 million, for XRP about $123 million, for ZEC approximately $66.77 million, and other assets totaled about $479 million in liquidations. The largest single liquidation in the past 24 hours occurred on the BTC-USD trading pair on Hyperliquid, valued at approximately $24.96 million.

  • Over $1.244 Billion Liquidated in the Last 24 Hours, Primarily Long Positions

    On August 22, according to CoinGlass data, the total liquidation amount in the cryptocurrency market over the past 24 hours reached $1.244 billion, with long positions accounting for $744 million and short positions for $500 million. A total of 246,203 individuals were liquidated. Among them, the liquidation amounts were approximately $194 million for BTC, $277 million for ETH, $104 million for SOL, $123 million for XRP, and about $66.77 million for ZEC, with other assets totaling approximately $479 million in liquidations. The largest single liquidation occurred on the Hyperliquid BTC-USD trading pair, valued at approximately $24.96 million.