#37768 [SC-Insight] Missing Event Emission when proposer are added prevents safe retrieval of index
Submitted on Dec 15th 2024 at 11:28:16 UTC by @danvinci_20 for Audit Comp | Folks: Liquid Staking
Report ID: #37768
Report Type: Smart Contract
Report severity: Insight
Target: https://github.com/Folks-Finance/algo-liquid-staking-contracts/blob/8bd890fde7981335e9b042a99db432e327681e1a/contracts/xalgo/consensus_v2.py
Impacts:
Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)
Design Flaw
Description
Brief/Intro
It's a common design pattern to emit event/log when crucial operation are carried out onchain to be used by offchain systems, adding a proposer to the to the proposers box is an important onchain action that events/log are to be emitted when a proposer is successfully added, but this was not implemented in the current version of the consensus_v2.py
Vulnerability Details
The add_proposer
method does not return or emit event when a proposer is added this can lead to lack of transparency or easy retrievability of the proposer index for subsequent operations. This will lead to lack of usability and introduce vulnerability in dependent workflow like
Adding of proposer admin
Offline and Online Registration
Subscribe xgov and unsubscribe xgov This is the current implementation of the add_proposer function
@router.method(no_op=CallConfig.CALL)
def add_proposer(proposer: abi.Account) -> Expr:
proposer_rekeyed_to = proposer.params().auth_address()
num_proposers = ScratchVar(TealType.uint64)
return Seq(
rekey_and_close_to_check(),
# ensure initialised
Assert(App.globalGet(initialised_key)),
# verify caller is register admin
check_register_admin_call(),
# verify proposer has been rekeyed to the app
proposer_rekeyed_to,
Assert(proposer_rekeyed_to.hasValue()),
Assert(proposer_rekeyed_to.value() == Global.current_application_address()),
# check num proposers won't exceed max
num_proposers.store(App.globalGet(num_proposers_key)),
Assert(num_proposers.load() < ProposersBox.MAX_NUM_PROPOSERS),
# add proposer, verifying it hasn't already been added
Assert(BoxCreate(Concat(AddedProposerBox.NAME, proposer.address()), Int(0))),
BoxReplace(ProposersBox.NAME, num_proposers.load() * ProposersBox.ADDRESS_SIZE, proposer.address()),
App.globalPut(num_proposers_key, num_proposers.load() + Int(1)),
)
Impact Details
The impact caused by this are
It leads to disruption of workflow, The absence of an event or explicit return value providing the proposer's index makes it challenging for users to correctly interact with functions that require the index as a parameter.
Increased computational complexity and taking of unnecessary risk, for user must guess or calculate the proposer's index manually by iterating through the stored data (ProposersBox), which is computationally expensive.
Proof of Concept
Proof of Concept
This issue can be resolved by:
The system should implement mechanism to retrieve the proposer index given a particular address
class ProposerManager:
def __init__(self):
self.address_list = [] # List to store proposer addresses
self.address_to_index = {} # Mapping to store poposer address to index
def add_address(self, address):
if address in self.address_to_index:
print(f"Address {address} is already added at index {self.address_to_index[address]}.")
return
# Add the address to the list
self.address_list.append(address)
# Map the address to its index
self.address_to_index[address] = len(self.address_list) - 1
print(f"Address {address} added at index {self.address_to_index[address]}.")
def get_index(self, address):
#retrieve proposer index given the address
return self.address_to_index.get(address, None)
def get_address(self, index):
if 0 <= index < len(self.address_list):
return self.address_list[index]
return None
# Example usage
manager = ProposerManager()
# Adding addresses
manager.add_address("0x1234567890abcdef1234567890abcdef12345678")
manager.add_address("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd")
manager.add_address("0x9876543210fedcba9876543210fedcba98765432")
# Trying to add a duplicate address
manager.add_address("0x1234567890abcdef1234567890abcdef12345678")
# Retrieving index by address
index = manager.get_index("0xabcdefabcdefabcdefabcdefabcdefabcdefabcd")
print(f"Index of the address: {index}")
# Retrieving address by index
address = manager.get_address(1)
print(f"Address at index 1: {address}")
# Attempting to get an index that doesn't exist
print(manager.get_index("0xnonexistentaddress"))
The protocol should emit an event when proposer is added
# Log addition of proposer with index and address
Log(Concat(MethodSignature("AddProposer(uint64,address)"), Itob(index), proposer.address()))
Last updated
Was this helpful?