Introduction
On February 24th, 2025, Aztec engaged zkSecurity to perform an audit of its smart contract related to the Token Generation Event (TGE). The specific code to review was shared via GitHub as a private repository (https://github.com/AztecProtocol/teegeeee at commit 8432c82584731813a2197dd3b715ba2db0dbe3f9). The audit lasted 3 workdays with 1 consultant.
The code was found to be clear, well documented, and accompanied with thorough tests.
One major finding and a few informational findings were reported to the Aztec team, which are detailed in the following sections.
Scope
The scope included the Solidity contract in the teegeeee repo. At a high level this included:
src/token, which is the AZTEC ERC20 token contract.src/atps, which is Aztec Token Positions (ATPs), including Milestone Aztec Token Position (MATP) and Linear Aztec Token Position (LATP).src/libraries, which contains the helper for the schedule lock.src/staker, which is the staker contract for AZTEC token. Currently, it’s just a “no-op” contract and is used for testing.src/ATPFactory.sol, which is the factory to create new ATPs.src/Registry.sol, which manages the global unlock schedule, staker implementations, and milestones.
Overview
The AZTEC token is an ERC20 token that is mintable by the contract owner. Aztec uses the Aztec Token Positions (ATPs) to distribute the AZTEC token to beneficiaries (e.g., individuals and companies). Each ATP is a standalone contract that holds the tokens to be distributed and specifies the unlock schedule. Typically, the unlock schedule is controlled by two schedules: the global unlock schedule (shared by every ATP) and the local unlock schedule specified by each ATP. At any given time the released token amount is the minimum of the two schedules. For example, if at the 12th month the local schedule releases 20% and the global schedule releases 10%, then the actual release is 10% (min(20%, 10%)). This means that all the ATPs are restricted by the global unlock schedule.
The global unlock schedule is a linear release curve with a cliff. The token is released linearly over time but only claimable after a specific cliff time. The curve is specified by a starting time, cliff time and end time. Below is an example schedule (taken from the docs of the repo) with a 24 month full duration and a 12 month cliff.

The local unlock schedule has two types: Milestone Aztec Token Position (MATP) and Linear Aztec Token Position (LATP).
The unlock schedule of MATP is specified by a milestone. The token is released only after the milestone (e.g., mainnet launch) is achieved. Before that, all the tokens are locked. Below is an example schedule of the MATP (taken from the docs of the repo). In the example, the tokens are not released after the global unlock cliff but after the milestone success.

The unlock schedule of LATP is a linear release curve with a cliff (just like the global unlock schedule). The resulting unlock amount at a time is the minimum of the global unlock schedule and the local unlock schedule. Below is an example schedule of the MATP (taken from the docs of the repo). In the example, the tokens are released only after the two unlock cliffs are reached.

Staking and Revoking
In the Aztec network, the locked AZTEC token in ATP can participate in staking (to the sequencer). This is achieved by allowing the ATP contract to approve the locked token to the staker contract. The staker can then transfer the token from ATP, perform staking, and transfer the token back when necessary. It is important that the staking operation cannot be used to bypass the unlock schedule. Thus, the ATP can only use the staker contract that is specified by the Aztec-labs. More specifically, each ATP has its own staker contract. The staker contract implementation is whitelisted by the Registry, which is managed by the Aztec-labs.
For some ATP, the tokens can be revoked by a revoker entity if they have not been accumulated.
For MATP, all the tokens can be revoked by the revoker when the milestone is in Pending status (i.e., not Failed or Succeeded). If the milestone is Succeeded, then it can’t be revoked and all the tokens will eventually go to the beneficiary (the schedule is still restricted by the global lock). If the milestone is marked as Failed, then it means the MATP is fully revoked.
For LATP, there are two cases. It can be set as non-revokable. In this case the local schedule lock is empty and the schedule just follows the global schedule. If it is set as revokable, then it will have a local schedule. The token that is not released according to the local schedule lock is revokable by the revoker. The contract ensures that the revokable portion cannot be transferred to the staker.
Registry
The registry holds the “source of truth” data of the protocol. It consists of three main parts:
- Global Schedule. This specifies the global schedule. All ATPs will refer to the registry to get the global schedule. The registry owner can decrease the start time of the global schedule.
- Staker Implementation. This contains the implementation address of the staker. When necessary, the registry owner can add new version staker implementations.
- Milestone. The registry manages all the milestones. The registry owner can add a new milestone and update the status of it.