#37867 [SC-Low] Contract upgrade failing due to SHA256 failing because of AVM byte width limits
Submitted on Dec 17th 2024 at 20:12:20 UTC by @uhudo for Audit Comp | Folks: Liquid Staking
Report ID: #37867
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/Folks-Finance/algo-liquid-staking-contracts/blob/8bd890fde7981335e9b042a99db432e327681e1a/contracts/xalgo/consensus_v2.py
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The code is not being transparently upgradable through scheduling an upgrade, as well as the upgrade potentially failing - without blocking the protocol.
Vulnerability Details
The code can be upgraded by admin via a two-step process, i.e. by calling schedule_update_sc
method to schedule an update and then calling the method update_sc
to deploy it. The scheduling makes commitments in the form of hashes of the smart contract code, i.e. the approval and clear programs, which are to be uploaded later. If the method update_sc
is called with programs that do not meet the hash commitments, the update will be rejected. The update can succeed only if the call is made after a predefined amount of time, which is set to 1 day.
The purpose of this two-step process is to give users the option to review the new contract changes and act by burning their xALGO before the new update goes live e.g. if they do not agree with the changes or find them malicious. The downside of the current implementation is that it still requires the admin to publish somewhere the code, which corresponds to the commitments made, for the users to review.
Moreover, the implementation includes an error. The maximum size of programs on Algorand is 8192 bytes. However, the maximum byte width of the Algorand Virtual Machine is 4096 bytes. This means that when verifying whether the correct programs are to be deployed after they have been scheduled, the calculation of hashes of programs larger than 4096 bytes will fail, i.e. at https://github.com/Folks-Finance/algo-liquid-staking-contracts/blob/8bd890fde7981335e9b042a99db432e327681e1a/contracts/xalgo/consensus_v2.py#L353. Because a pending upgrade can be overwritten by the admin calling again schedule_update_sc
method, the error will not result in a blocked contract.
Impact Details
The centralization fear is one of the main downsides of liquid staking protocols and a big reason why many users do not want to use them. Any effort that can be made to improve decentralization will drive additional users to the protocol.
To resolve both of these limitations simultaneously, the contract could take advantage of box storage to store the code for the new upgrade already in the update scheduling step.
References
This is one of the insights found during the Audit Competition. The full report on all bugs and insights found is accessible until 2025/01/16 at https://www.swisstransfer.com/d/4c5dff62-e56b-4c13-bc07-0bbba1e00e84. The download is password-protected. The password is NT4SCGJ7NTJENGSDWKKLZLZ2J (the first 25 letters of authors' Algorand address: NT4SCGJ7NTJENGSDWKKLZLZ2JNXFXM5Y6HLU224TPUJXNA2IU3DBBHDTMQ). The shared folder includes the full report (PDF file) and a .zip of the full test suite project (using AlgoKit), demonstrating all found issues.
Proof of Concept
Proof of Concept
The test showing issues with upgrades is implemented in schedule_and_update_sc_test.py
, found in https://www.swisstransfer.com/d/4c5dff62-e56b-4c13-bc07-0bbba1e00e84 (password is NT4SCGJ7NTJENGSDWKKLZLZ2J):
from hashlib import sha256
import pytest from algokit_utils import ( TransactionParameters, ) from algokit_utils.beta.account_manager import AddressAndSigner from algokit_utils.beta.algorand_client import AlgorandClient from algokit_utils.beta.composer import PayParams from algosdk.abi import ArrayStaticType, ByteType, TupleType, UintType from algosdk.abi.method import Method, Returns from algosdk.atomic_transaction_composer import ( AtomicTransactionComposer, TransactionWithSigner, ) from algosdk.error import AlgodHTTPError from algosdk.transaction import ApplicationCallTxn, OnComplete
from tests.consensus.conftest import BOX_UPDATE_PREFIX, MBR_UPDATE_BOX, Setup from tests.utils import ( advance_time, get_approval_and_clear_bytes, get_box, get_latest_timestamp, get_sp, )
def test_fails_when_update_too_large( algorand_client: AlgorandClient, dispenser: AddressAndSigner, setup: Setup, ) -> None: