# Week 18-19 development update
I put together a beacon-chain accounting design validation document (pseudocode) and a python project, including pytests to prove the eODS beacon-chain accounting design.
# Beacon chain accounting - eODS feature
## 1. Validator balances
### 1.1 Validator records
We can add `delegated` boolean entry in the records:
```python
class Validator(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
effective_balance: Gwei # Balance at stake
slashed: boolean
# Status epochs
activation_eligibility_epoch: Epoch # When criteria for activation were met
activation_epoch: Epoch
exit_epoch: Epoch
withdrawable_epoch: Epoch # When validator can withdraw funds
delegated: boolean # new in eODS
```
### 1.2 Beacon chain records
Actual balance is stored in the `BeaconState` records, as a registry of validators and their balances.
We can add a fee `List` structure, mapping each Validator in the registry with its fee rate (`NULL` if `delegated` entry defined in `class Validator` is `False`):
```python
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
fees: List[Fee, VALIDATOR_REGISTRY_LIMIT]
```
## 2. Delegator balances
### 2.1 BeaconState records
We can add beacon chain `class Delegator`:
```python
class Delegator(Container):
pubkey: BLSPubkey
withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals
delegated_balance: Gwei # Balance at stake
# Status epochs
activation_epoch: Epoch
exit_epoch: Epoch
```
Delegator balances are to be stored in the `BeaconState` records, as a registry of delegators and their balances:
We can add Delegators registry inside `BeaconState`
```python
# Validators Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Delegators Registry - New in eODS
delegators: List[Delegator, DELEGATOR_REGISTRY_LIMIT]
balances: List[Gwei, DELEGATOR_REGISTRY_LIMIT]
```
## 3. Beacon-chain accounting design validation project - delegating to validators
### 3.1 classDelegator.py
For toy-project purposes, I wrote a toy `class Delegator` and imported it in the code file. The delegator IDs are stored as string, i.e. Validator_no1, Validator_no2, instead of delegator pubkeys
```python
class Delegator:
pubkey : str
balance : float
quota : float
```
### 3.2 Code.py
The code file stores delegators' data as a list of `Delegator` instances. If we dig deeper into the implementation, we can observe that the following functions perform balance sheet operations, as follows:
- `delegate` - Delegates capital to the `DelegatedValidator`, updates delegator's balance, and recalculates delegators' quotas.
- `withdraw` - Allows a delegator to withdraw up to their balance, updating their quota
- `adjust_DelegatedValidator_balance` - Adjusts the total DelegatedValidator value (profit/loss) without changing individual quotas.
- `_recalculate_quotas` - Recalculates and updates all delegator quotas to ensure the sum is 1.
Helper functions
- `get_delegator_quota` - Returns the delegator's quota as a percentage of the total balance of the delegated validator.
- `get_all_quotas` - Returns a list with all delegators' quotas.
- `get_delegator_index_by_pubkey` - Returns de index of a delegator with a given pubkey
```python
import math
from classDelegator import Delegator
class DelegatedValidator:
def __init__(self):
self.total_balance = 0 # Validator's initial effective balance
self.delegators = [] # Stores delegators' data as a list of delegator objects
def delegate(self, pubkey, amount):
"""delegates capital to the DelegatedValidator, updates delegator's balance, and recalculates quotas."""
if amount <= 0:
raise ValueError("delegate amount must be positive.")
# Update delegator's balance
index = self.get_delegator_index_by_pubkey(pubkey)
if type(index) == int:
self.delegators[index].balance += amount
else:
# append new delegator to list
delegator = Delegator()
delegator.pubkey = pubkey
delegator.balance = amount
delegator.quota = 0
self.delegators.append(delegator)
# Update total DelegatedValidator value and recalculate quotas
self.total_balance += amount
self._recalculate_quotas()
def withdraw(self, delegator_index, amount):
"""Allows a delegator to withdraw up to their balance, updating their quota."""
if delegator_index not in self.delegators:
raise ValueError("delegator not found.")
if amount <= 0:
raise ValueError("Withdrawal amount must be positive.")
# Ensure the delegator has sufficient balance
delegator_balance = self.delegators[delegator_index]['balance']
if amount > delegator_balance:
raise ValueError(f"Amount exceeds delegator's balance: {delegator_balance:.2f}")
# Adjust delegator's balance and total DelegatedValidator value
self.delegators[delegator_index]['balance'] -= amount
self.total_balance -= amount
# Remove delegator if balance reaches zero
if self.delegators[delegator_index]['balance'] == 0:
del self.delegators[delegator_index]
# Recalculate quotas to ensure they sum to 1
self._recalculate_quotas()
def adjust_DelegatedValidator_balance(self, change_in_value):
"""Adjusts the total DelegatedValidator value (profit/loss) without changing individual quotas."""
self.total_balance += change_in_value
if self.total_balance < 0:
raise ValueError("DelegatedValidator total value cannot be negative.")
for delegator in self.delegators:
delegator.balance += change_in_value * delegator.quota
# Quotas remain the same; balances are recalculated.
def _recalculate_quotas(self):
"""Recalculates and updates all delegator quotas to ensure the sum is 1."""
if self.total_balance == 0:
# If the total DelegatedValidator value is zero, reset all quotas to zero
for delegator in self.delegators:
delegator.quota = 0
else:
# Recalculate quotas as a fraction of the total DelegatedValidator value
for delegator in self.delegators:
delegator.quota = delegator.balance / self.total_balance
# Confirm quotas sum to 1
total_quota = sum(delegator.quota for delegator in self.delegators)
if abs(total_quota - 1) > 1e-6:
raise AssertionError("Sum of quotas should be 1 but is {:.6f}".format(total_quota))
def get_delegator_quota(self, delegator_index):
"""Returns the delegator's quota as a percentage of the total DelegatedValidator."""
if delegator_index not in self.delegators:
raise ValueError("delegator not found.")
return self.delegators[delegator_index]['quota']
def get_delegator_index_by_pubkey(self, pubkey):
"""Returns de index of a delegator with a given pubkey"""
for index, delegators in enumerate(self.delegators):
if delegators.pubkey == pubkey:
return index
def get_all_quotas(self):
"""Returns a dictionary with all ' quotas."""
return {delegator_index: data['quota'] for delegator_index, data in self.delegators.items()}
```
### 3.3 Tests.py
The following 4 tests are OK:
- `test_add_delegate` - Add a delegator and set its balance.
- `test_add_multiple_delegates` - Add multiple delegators and set their balance.
- `test_adjust_DelegatedValidator_value_quota` - Adjust the DelegatedValidator value and have the delegators balance updated.
- `test_adjust_DelegatedValidator_balance_quota` - Adjust the DelegatedValidator balance and have the delegators' quota unchanged.
```python
import unittest
from accounting import DelegatedValidator
class TestSum(unittest.TestCase):
def test_add_delegate(self):
"""
I should be able to add a delegator and set its balance
"""
delegatedvalidator = DelegatedValidator()
delegatedvalidator.delegate('Delegator_no1', 12)
value = delegatedvalidator.delegators[0].balance
self.assertEqual(value, 12)
def test_add_multiple_delegates(self):
"""
I should be able to add multiple delegators and set their balance
"""
delegatedvalidator = DelegatedValidator()
delegatedvalidator.delegate('Delegator_no1', 12)
delegatedvalidator.delegate('Delegator_no2', 55)
valueDelegator_no1 = delegatedvalidator.delegators[0].balance
valueDelegator_no2 = delegatedvalidator.delegators[1].balance
self.assertEqual(valueDelegator_no1, 12)
self.assertEqual(valueDelegator_no2, 55)
def test_adjust_DelegatedValidator_value(self):
"""
I should be able to adjust the DelegatedValidator balance and have the delegators balance updated
"""
delegatedvalidator = DelegatedValidator()
delegatedvalidator.delegate('Delegator_no1', 12)
delegatedvalidator.delegate('Delegator_no2', 12)
delegatedvalidator.adjust_DelegatedValidator_balance(100)
valueDelegator_no1 = delegatedvalidator.delegators[0].balance
self.assertEqual(valueDelegator_no1, 62)
def test_adjust_DelegatedValidator_balance_quota(self):
"""
I should be able to adjust the DelegatedValidator balance and have the delegators quota unchanged
"""
delegatedvalidator = DelegatedValidator()
delegatedvalidator.delegate('Delegator_no1', 10)
delegatedvalidator.delegate('Delegator_no2', 10)
quotaDelegator_no1 = delegatedvalidator.delegators[0].quota
self.assertEqual(quotaDelegator_no1, 0.5)
delegatedvalidator.adjust_DelegatedValidator_balance(100)
quotaDelegator_no1 = delegatedvalidator.delegators[0].quota
self.assertEqual(quotaDelegator_no1, 0.5)
if __name__ == '__main__':
unittest.main()
```

## 4. Week 20 planning
- For the first iteration of the beacon-chain accounting design validation project, I used a key,value pair structure to store delegtors' data. I then prefered to switch to a list sequence insetead, as it's less resources demanding. I refactored the code needed to pass the 4 tests, and I plan to fix the code in the remaining functions (marked as BROKEN), during next week.
## 5. Week 21 planning
Write final dev update & project presentation