Core Contract Overview
The main job of the core contract is to take in a Å-Node supplied bundle, decode, validate and execute it. In an effort to save gas the contract relaxes its trust assumptions in certain areas, relying on the economic security guarantees provided by the off-chain set of Angstrom nodes.
The below paragraphs give an overview of different important parts & concepts in Angstrom:
Node vs. Users
Trusted, staked nodes as described above are the ones responsible for calling execute
and
providing some of the parameters for orders. Assets, prices, amountFilled
for partial orders and
gasUsedAsset0
/ extraFeeAsset0
are all computed and supplied by the trusted node.
Users supply their orders/intents in the form of src/_reference/SignedTypes.sol
. The node performs
order matching determining which ToB & user orders to include as well as what amount to fill for
partial orders. The node is also responsible for computing and fairly splitting gas & referral fees
among users.
The execute(bytes)
Entry Point
The Angstrom::execute
method is the entry point for bundle execution, it calls into the Uniswap V4
position manager to establish a lock so that it can execute pool swaps & settle swap deltas.
The execute takes a PADE-encoded payload.
Bundle processing proceeds in the following stages:
- Load & validate list of assets
- Load & validate list of pairs for that bundle
- Take tokens from Uniswap (either to settle swap deltas or as flashloan for settling orders).
- For each pool: update
- Execute a swap against the underlying Uniswap pool
- Update pool reward accumulators to account for new rewards & tick crossings
- Validate & execute ToB (top of block) orders
- Validate & execute user orders
- Settle remaining deltas with Uniswap
- Emit collected fee summary event
Intra Bundle Accounting
To ensure the contract remains solvent the DeltaTracker
keeps
track of net balance changes for every asset and is checked at the end.
The solvency invariant it maintains for every asset is:
Here a visualization of how each action in a bundle is accounted for:
A component of general solvency that the core contract does not directly guarantee is amounts
related to limit order fees (fees accrued via feeInE6
& maxExtraF
).
Pool Rewards
Angstrom internally uses "growth outside" accumulators to track and account for LP rewards very similar to the way Uniswap V3+ fee accumulators function. The goal of this system is to be able to efficiently update & compute rewards to be distributed to ranged positions without needing to iterate across entire ranges.
The reward tracking consists of two accumulators:
- a global reward growth accumulator
- a per-tick "growth outside" accumulator
Global Reward Accumulator
Any accrued rewards get added to the global accumulator and represent the rewards accrued by active positions, position's whos tick range contains the current tick:
As the current tick moves around and leaves one range into another we want to keep track of the fact that the accrued rewards should not count towards the rewards for ranges that were previously active. This is where the "growth outside" accumulators come in:
Reward Growth Outside Accumulators
To ensure we don't double count rewards we update a growth outside accumulator whenever we cross a tick such that it represents the sum of rewards accrued in the direction away from the current tick:
Now we continue to update the global reward growth as usual whenever we distribute new rewards:
Generalizing the update of the growth outside value at tick boundaries we simply do growth_outside' := global_growth - growth_outside
:
Notice that as current tick moves and positions become inactive/active the amount of active liquidity will change. To ensure that the rewards tracking remains consistent despite this the accumulators do not track the absolute growth in rewards but instead the growth per unit of liquidity.
As rewards accumulate and the current tick moves back and forth you end up with growth outside accumulators looking like this:
The brackets representing the range for which the growth outside value is the sum relative to the current tick. Notice the discrepency between the meaning of the growth outside accumulators for ticks below or at the current tick and above the current tick. Below or at the curren tick the growth outside range is exclusive, above it is inclusive.
When rewarding multiple ranges in one go as in Angstrom one must consider this asymmetry when handling the above and below cases.
Gas Payment & Extra Fee
To ensure users trading on Angstrom don't have to think about ETH-denominated gas payments gas is charged in either the input/output asset of the respective trade.
On top of this users may be charged a referral fee if they opted into one via a referral tag
(ref_id
field). Validating the ref id, retrieving the fee rate and applying it correctly is the
responsibility of the nodes. They will use a registry in the periphery contracts as their source of
truth.
The total gas fee + referral fee is called the "extra fee" and is charged in the respective asset0
of the pair.