Aeva Info

An asset-native L1 for programmable ownership, financial rights, and settlement.

Aeva Logo

TrustNet Score

The TrustNet Score evaluates crypto projects based on audit results, security, KYC verification, and social media presence. This score offers a quick, transparent view of a project's credibility, helping users make informed decisions in the Web3 space.

72.16
Poor Excellent

Real-Time Threat Detection

Real-time threat detection, powered by Cyvers.io, is currently not activated for this project.

This advanced feature provides continuous monitoring and instant alerts to safeguard your assets from potential security threats. Real-time detection enhances your project's security by proactively identifying and mitigating risks. For more information, click here.

Security Assessments

Select the audit
Manual Review Static Analysis Dynamic Analysis
Contract address
N/A
Network N/A
License N/A
Compiler go1.26.10
Type Cosmos SDK chain modules (Go)
Language Go
Onboard date 2026/10/30
Revision date 2026/10/30

Summary and Final Words

No crucial issues found

The contract does not contain issues of high or medium criticality. This means that no known vulnerabilities were found in the source code.

Scope of Work

This audit encompasses the evaluation of the files listed below, each verified with a SHA-1 Hash. The team referenced above has provided the necessary files for assessment.

The auditing process consists of the following systematic steps:

  1. Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
  2. Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
  3. Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
  4. Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
  5. Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
  6. Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
  7. Actionable Recommendations: Provide detailed, specific, and actionable steps to secure and optimize the smart contracts.

A file with a different Hash has been intentionally or otherwise modified after the security review. A different Hash may indicate a changed condition or potential vulnerability that was not within the scope of this review.

Final Words

The following provides a concise summary of the audit report, accompanied by insightful comments from the auditor. This overview captures the key findings and observations, offering valuable context and clarity.


Smart Contract Analysis Statement

Contract Analysis

The Aeva staking area implements fixed-term lock periods over ordinary Cosmos delegations on aeva-1 as a Cosmos SDK module written in Go (x/stakelock). A delegator locks part of a delegation for 30, 90, 180 or 365 days at a weight of 1x to 3x and earns a lock reward from the staking pool's 30 percent share; the staking hooks refuse any undelegation or redelegation into a lock until it ends, and the ante handler refuses it early. The unbonding time on aeva-1 is 1 second, so lock periods are the only time commitment. This re-audit verified the remediation released in aevad v1.1.3 and v1.1.4, live on aeva-1 since heights 1 795 003 and 2 098 922 and before the first lock ended. The three critical issues and the high issue of the first review are fixed: a claim can no longer name a lock twice, moving a lock can no longer strip another lock on the same delegation, and lock addresses are stored in canonical form and compared as bytes, so a lock spelled in capitals is enforced. An ended lock now stops earning at its end, a message executed by governance can no longer cut a lock, the ante check no longer depends on the decoder, and the invariant check is about eight times cheaper per lock. The original proofs of concept no longer reproduce, a randomized test kept both lock invariants exact after every block, and all 28 locks read on aeva-1 at height 2 201 491 carry canonical addresses with a total weight equal to the sum of the lock weights. Double signs are deliberately not punished on aeva-1; the team has documented this decision and accepted the risk.

Ownership Privileges

The ownership of the lock settings has been given to on-chain governance. There is no owner key; in practice any two of the four team validators can pass an expedited proposal in about five minutes. Governance retains full privileges including:

  • Switching lock periods on or off and changing the tiers, the weights, the maximum lock reward rate, the minimum lock amount and the number of locks per account.
  • Changing the staking and slashing parameters, including the 1 second unbonding time.
  • Changing the recipients of the staking pool, which fund the lock reward.
  • Shipping a software upgrade, which can change any lock or balance; upgrade handlers and slashing are the only paths that may shrink a lock.
  • A message executed by governance cannot cut a lock: since v1.1.4 it is refused like the same message in a transaction.
  • Only the owner of a lock can move it, end it early or claim it, and a claim pays each lock once.
  • The lock reward cannot exceed the maximum lock rate per unit of weight, and a lock earns nothing after its end.
  • Double signs carry no penalty on aeva-1 by documented decision, so consensus safety rests on the team's validator keys rather than on slashing.

Security Features

The contract implements several positive security features:

  • Locks are enforced by the staking hooks on every unstaking path, including authorizations and governance execution, and only slashing in BeginBlock and upgrade handlers may shrink a lock instead of failing.
  • Delegator and validator addresses are stored in canonical form and compared as decoded bytes, and genesis refuses locks with non-canonical addresses.
  • Rewards are settled by end cohorts, so a lock stops earning at its end whatever the retirement backlog, and retirements are capped per block.
  • Two hosted invariants halt the chain if the locks on a delegation exceed its shares or if the module owes more than it holds, and they are checked every 100 blocks.

Note - This Audit report consists of a security analysis of the Aeva Staking area (lock periods over delegations, their enforcement by the staking hooks and the ante handler, the lock reward, and the staking, slashing and governance behaviour they depend on) as released in aevad v1.1.4 and checked against live state. This analysis did not include economic analysis of the AEVA tokenomics. Moreover, we only audited the lock-period module and its use of the staking, slashing and governance modules for the Aeva team; the bridge, the rewards and the chain-level items are covered by their own reports, and other contracts associated with the project were not audited by our team. We recommend investors do their own research before investing.

Files and details

Findings and Audit result

critical Issues | 2 findings

Resolved

#1 critical Issue
A repeated id in MsgClaimLockReward pays the same lock reward again and halts the chain
chain/x/stakelock/keeper/locks.go
L279
Description

ClaimLockReward loads every lock named in lock_ids before it processes any of them, and nothing rejects an id that appears more than once. The message has no stateless check, the message server does not look for duplicates, and the ownership check does not track ids already seen. For an ended lock each copy adds the same accrued reward to the payout and closes the lock again, and the closing deletes are silent once the record is gone. The total is sent from the stakelock module account, which also holds the unpaid rewards of every other lock, so the transfer succeeds while that balance lasts and the attacker takes those rewards. The module then owes more than it holds, and at the next invariant check (every 100 blocks on aeva-1) the x/rights EndBlocker panics and every node stops. A single lock of 1 AEVA for 30 days is enough, and two copies of the id already halt the chain once the reward is above rounding dust. A variant with an ended but not yet retired lock also lowers TotalWeight several times. In tests a claim of the same id twice paid exactly twice the reward, and the same claim sent as a signed transaction made FinalizeBlock panic on the solvency check.

Resolved

#2 critical Issue
A lock recorded with an all-uppercase validator address is not enforced, and SL10 then halts the chain
chain/x/stakelock/keeper/locks.go
L93
Description

A bech32 address has two accepted spellings, all lowercase and all uppercase, and the codecs x/stakelock uses decode both to the same bytes and always encode in lowercase. MsgLock has no stateless check, and Lock decodes the delegator and validator only to validate them and then stores the text exactly as submitted. MoveLock does the same with the destination validator, and genesis import keeps whatever the file holds. Lock enforcement compares text: binding encodes the validator in lowercase and skips every lock whose stored validator differs, and the staking hooks, the unstake ante check and the free-stake check in MsgLock all take their locks from binding. A lock that names its validator in uppercase is therefore enforced by none of them, while the rest of the module decodes the text, so the lock still counts as active, earns weight and reward, and is counted by the SL10 invariant, which sums locks by the raw text and compares the sums with the real delegations. As soon as the stake under such a lock leaves with an ordinary undelegation, which nothing refuses, or a second such lock is added to the same stake, which the free-stake check does not see, SL10 fails and the x/rights EndBlocker panics at its next check, every 100 blocks on aeva-1, so every node stops. Any account can do this with the 1 AEVA minimum lock, in a single transaction if it likes, and gets the stake back after the one-second unbonding. No lock has to end first, and a wallet that copies an uppercase address from a QR code can trigger it by accident. Replaying the block panics again and the lock binds for at least 30 days, so recovery needs a coordinated patched binary with a state repair. MsgMoveLock to an uppercase destination is a second way in for existing lock holders. Because the delegator text is also stored as submitted and is part of the invariant's key, the spelling variants let one delegation carry three full locks with the invariants green, which gives three times the lock weight and the lock reward on the same stake and dilutes the other lockers. Tests show the escape and the halt at height 100 with the live check period, the halt in the same block at the code default, the move path, the two-lock halt without any undelegation, and one account earning exactly three times the lock reward of another on equal stake. The opposite direction is safe. At height 263 700 every stored lock used canonical addresses, and at height 600 465 on 2026-10-06 all 25 stored locks still used canonical lowercase delegator and validator addresses, each identical to the re-encoding of its own decoded bytes, so the defect had not been triggered. At that height aeva-1 ran v1.1.1, whose source was not available, so whether the running binary still has this defect is unknown. This extends AUDIT-SCOPE section 9 note 16.

high Issues | 1 findings

Resolved

#1 high Issue
MsgMoveLock of an older lock strips a newer lock on the same delegation and leaves phantom weight
chain/x/stakelock/keeper/locks.go
L203
Description

MoveLock redelegates the moved lock's shares with a bypass marker, so the staking hook on the source delegation runs in shrink mode while the moved lock is still stored on the source. The hook sums every binding lock there, the moved one included, finds the delegation short and shrinks the locks oldest first. When the moved lock is older than another lock on the same delegation, the moved lock is kept whole and the newer lock loses its shares, all of them when there is no free stake. The newer lock then has zero shares but still counts as binding until its end, so it protects nothing: its stake can be undelegated and is liquid one second later, nothing is forfeited, and its reward is still paid at its end date. MoveLock then writes back the reward state it read before the redelegation, which undoes the hook's reduction of TotalWeight and leaves the stripped weight counted for good. That dilutes every other locker, strands the share credited to the phantom weight in the module, and makes an exported genesis fail validation. Any account with two locks on one delegation can do this, and any delegator can create that pair. On aeva-1 the precondition already exists: at height 263 700 there were three locks on three different delegations, but at height 600 465 on 2026-10-06 four delegations, held by three accounts, carried two or more locks each (lock ids 13, 14 and 15; 24 and 27; 26, 31 and 32; 28 and 29). It has not been used yet: total_weight still equalled the sum of the 25 stored lock weights, so there is no phantom weight. In a test, a 365-day lock was left after five days with its reward kept and 300 AEVA-weight left in TotalWeight with no lock behind it.

medium Issues | 1 findings

Acknowledged

#1 medium Issue
Double signs are never punished on aeva-1 because x/evidence is not wired
chain/app/app_config.go
L160
Description

CometBFT detects a validator that signs two different votes at the same height and round, commits the evidence in a block and hands it to the application in FinalizeBlock. In Cosmos SDK v0.54.4 only the x/evidence BeginBlocker acts on that evidence by slashing, jailing for good and tombstoning. The aeva-1 app does not include x/evidence: the module config and the BeginBlocker list have no evidence module, and the live evidence query answers Not Implemented. x/slashing only counts missed signatures, and no other module in the binary reads the evidence. An equivocating validator therefore keeps its stake and its place in the set, slash_fraction_double_sign of 0.05 is never read during block processing, and locked stake carries no equivocation exposure, contrary to ADR-021, ADR-025 and the known-issues list. The lock tests that cover double signs call the slashing keeper directly and so simulate a path the chain never runs. A test that feeds duplicate-vote evidence through FinalizeBlock with mainnet parameters shows no slash, jail or tombstone and no change in supply. Exploiting this against consensus safety needs more than one third of the voting power, which today only the team's validators hold.

low Issues | 1 findings

Resolved

#1 low Issue
An ended lock keeps earning until it is retired, after its stake has left
chain/x/stakelock/keeper/rewards.go
L56
Description

Once a lock's end passes it no longer binds, so its owner may undelegate or redelegate the stake and lock it again elsewhere. The lock itself is retired only by the EndBlocker, oldest end first and at most max_retirements_per_block (100 on aeva-1) per block, or by the owner's claim. Until then settle keeps crediting it, because its weight is still in TotalWeight, and at retirement reweigh values the stored shares at the validator's rate, so the weight is not lowered even though the delegation is gone. During a retirement backlog the lock reward is therefore paid on stake that no longer exists, or on the same AEVA twice once it is locked again, which dilutes the other lockers while the lock share is the limit. A backlog needs more than 100 locks due in the same block. In a test with the limit set to 1, sixteen ended locks of 100 AEVA each were credited a further 0.000206 AEVA after all 1 600 AEVA had been undelegated. The solvency check and the lock coverage check both still hold.

informational Issues | 3 findings

Resolved

#1 informational Issue
A governance proposal can erase a lock through the protocol-path shrink, given an authz grant to the gov account
chain/x/stakelock/keeper/locks.go
L24
Description

The lock hooks refuse unstaking only when the context carries transaction bytes, and treat every other context as a protocol path in which they shrink the locks instead. The governance EndBlocker executes passed proposals in such a context. A proposal that contains an authz MsgExec, with the gov account as grantee, wrapping a MsgUndelegate of a delegator who granted that permission to the gov account is accepted and executed, and the delegator's lock is left empty and without weight instead of the undelegation being refused. Its accrued reward is settled, not forfeited. Without such a grant the proposal fails. Governance can already do more through a software upgrade, so the trust assumption barely widens, but the documentation says the hooks are the rule and does not mention this path, and it would apply to any future module that executes messages in a blocker.

Resolved

#2 informational Issue
The unstake ante decorator relies on the decoder for a non-nil amount, and its MsgExec depth differs from the documentation
chain/x/stakelock/keeper/ante.go
L72
Description

The decorator passes the message amount to the staking keeper's ValidateUnbondAmount without checking it, and it runs before signature verification, so it sees unsigned messages shaped by anyone. A nil amount would panic there. Today it cannot arrive from transaction bytes, because the interface registry re-encodes every decoded message and the integer encoder turns a nil value into zero, so a missing or empty amount reaches the decorator as zero and the transaction is refused on its signature. The safety therefore rests on a side effect of the decoder, while a panic at this point would not just fail the transaction: cosmos/evm runs the Cosmos ante chain for CheckTx on its insert queue goroutine without a recover, so it would stop the node process. Tests show the node answers CheckTx with an error and stays alive for both encodings. Separately, the decorator follows MsgExec while the depth is at most one, so it inspects two levels, while the comment, ADR-025 and the scope notes say one. A third level is still refused by the staking hook.

Resolved

#3 informational Issue
The lock solvency and coverage checks walk every stored lock every invariant check period
chain/x/stakelock/keeper/invariants.go
L21
Description

The hosted solvency and lock coverage checks walk every stored lock, including retired locks that nobody has claimed yet. For each lock they re-read the params, the reward state, the module balance, the validator and its signing info, and then read one delegation per locked pair. x/rights runs them every invariant_check_period, which is 100 blocks on aeva-1, and the invariant status query runs them on every request. A test measured about 5 664 gas of store reads and about 5 microseconds per lock, so the cost grows linearly with the number of locks, and locks only disappear when they are claimed. With one million locks, which 1 000 000 AEVA can create within a month, one check would read about 5.7 billion gas worth of state and take several seconds every 100 blocks. This is a block time risk rather than a direct halt, and it contradicts the design note that no block ever walks every lock.