Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8056c87
refactor: update withheld amount maintenance
kupermind Jun 23, 2025
80c47a4
chore: correcting number of optimizations in forge
kupermind Jun 23, 2025
b7ccf4d
test: adding test
kupermind Jun 23, 2025
4d543af
chore: linters
kupermind Jun 23, 2025
d5dfddc
doc: internal audit 7
Jun 23, 2025
da3d8e9
refactor: addressing internal audit
kupermind Jun 23, 2025
2c4d96d
test: correcting test
kupermind Jun 23, 2025
fd4aea0
doc: adding up-to-date diagram
kupermind Jun 23, 2025
cd5a0a9
doc: adding up-to-date diagram
kupermind Jun 23, 2025
e38d964
doc: adding up-to-date diagram
kupermind Jun 23, 2025
1555832
refactor: account for withheld amount update flag
kupermind Jun 23, 2025
f36648a
refactor: total amount correction
kupermind Jun 23, 2025
a71476d
chore: comment
kupermind Jun 23, 2025
87bac92
tes: test correction
kupermind Jun 23, 2025
c1ba5f6
tes: test correction
kupermind Jun 23, 2025
96621e5
Merge pull request #217 from valory-xyz/refactor_target_dispenser
kupermind Jun 23, 2025
ceb931c
doc: updating flowchart
kupermind Jun 23, 2025
6cbc6d2
Update README.md
mariapiamo Jun 23, 2025
d4de5d2
chore: syncing on mainnet name in globals
kupermind Jun 24, 2025
998b668
Merge pull request #215 from valory-xyz/v1.3.3-internal-audit
kupermind Jun 24, 2025
e885e71
Merge pull request #214 from valory-xyz/update_withheld_amount_tests
kupermind Jun 24, 2025
adc82db
doc: re-audit 1.3.3
Jun 25, 2025
368b741
Merge pull request #218 from valory-xyz/v1.3.3-internal-audit
kupermind Jun 25, 2025
dec034b
chore: Addressing internal audit 2
kupermind Jun 25, 2025
513a2fc
refactor: no revert
kupermind Jun 25, 2025
1e272d6
Merge pull request #219 from valory-xyz/address_internal_audit2
kupermind Jun 25, 2025
82b4e18
Merge pull request #216 from valory-xyz/address_internal_audit
kupermind Jun 25, 2025
cb47f9d
chore: static audit update
kupermind Jun 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
out
out_static_audit
node_modules
.env
coverage
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

This repository contains the tokenomics part of Autonolas onchain-protocol contracts.

A graphical overview is available here:
A graphical overview is available [here](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/docs/flowchart.md).

![architecture](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/docs/On-chain_architecture_v5.png)
For reference purposes only, an older version of the general Autonolas architecture is available [here](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/docs/On-chain_architecture_v5.png).

An overview of the Autonolas tokenomics model is provided [here](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/docs/Autonolas_tokenomics_audit.pdf). A description of the tokenomics contracts related to Olas staking is provided [here](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/docs/StakingSmartContracts.pdf).

Expand Down
1 change: 1 addition & 0 deletions audits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ An internal audit with a focus on PoAA Staking fixing after C4A is located in th

An internal audit with a focus on tokenimics inflation update is located in this folder: [internal audit 6](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/audits/internal6).

An internal audit with a focus on WithheldAmount fixing in PoAA Staking is located in this folder: [internal audit 7](https://github.com/valory-xyz/autonolas-tokenomics/blob/main/audits/internal7).

### External audit
Final audit reports are listed in their historical order:
Expand Down
56 changes: 56 additions & 0 deletions audits/internal7/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Internal audit of autonolas-tokenomics
The review has been performed based on the contract code in the following repository:<br>
`https://github.com/valory-xyz/autonolas-tokenomics` <br>
commit: `4d543af4efefea0380a1c5b1d79f5e574b4c26f0` or `tag: v1.3.3-pre-internal-audit`<br>

## Objectives
The audit focused on WithheldAmount fix in TargetDispenserL2 contract

## Issue
### Medium. Logical issue
```
This is not a bug in the current code.
However, the essence of the fix is ​​in conflict with this:
function syncWithheldAmount(bytes memory bridgePayload) external payable {}
In the current code, anyone can execute it.
It zeroid withheldAmount = amount - normalizedAmount;
They need to be given equal rights (OwnerOnly).
To discussion!
```
[x] Fixed

## Re-audit 25.06.25
The review has been performed based on the contract code in the following repository:<br>
`https://github.com/valory-xyz/autonolas-tokenomics` <br>
commit: `d4de5d273bc9ce9ff652f1734c8822c3954e6090` or `tag: v1.3.3-pre-internal-audit`<br>

## Issue
### Notes/Question
```
processDataMaintenance(bytes memory data, bool updateWithheldAmount)
let updateWithheldAmount == true
->
_processData(data);
->
uint256 localWithheldAmount = 0;
if (limitAmount == 0) {
// Withhold OLAS for further usage
localWithheldAmount += amount;
..
localWithheldAmount += targetWithheldAmount;
// Adjust withheld amount, if at least one target has not passed the validity check
if (localWithheldAmount > 0) {
withheldAmount += localWithheldAmount;
}
// Update total to-be-deposited amount
totalAmount += amount;
-> back to processDataMaintenance
// Update withheld amount
localWithheldAmount -= totalAmount;
withheldAmount = localWithheldAmount;
Is there a source of some discrepancy here? It turns out that we are erasing (minus) what adjust at `_processData`
Please, double check logic.
```
[x] Fixed


83 changes: 71 additions & 12 deletions contracts/staking/DefaultTargetDispenserL2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {
event AmountWithheld(address indexed target, uint256 amount);
event StakingRequestQueued(bytes32 indexed queueHash, address indexed target, uint256 amount,
bytes32 indexed batchHash, uint256 olasBalance, uint256 paused);
event StakingMaintenanceDataProcessed(bytes data);
event MessagePosted(uint256 indexed sequence, address indexed messageSender, uint256 amount,
bytes32 indexed batchHash);
event MessageReceived(address indexed sender, uint256 chainId, bytes data);
event WithheldAmountUpdated(uint256 amount);
event Drain(address indexed owner, uint256 amount);
event TargetDispenserPaused();
event TargetDispenserUnpaused();
Expand Down Expand Up @@ -138,7 +140,8 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {

/// @dev Processes the data received from L1.
/// @param data Bytes message data sent from L1.
function _processData(bytes memory data) internal {
/// @param updateWithheldAmount True, if withheld amount update is required.
function _processData(bytes memory data, bool updateWithheldAmount) internal returns (uint256 totalAmount) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
Expand Down Expand Up @@ -179,23 +182,36 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {

// If the limit amount is zero, withhold OLAS amount and continue
if (limitAmount == 0) {
// Withhold OLAS for further usage
localWithheldAmount += amount;
emit AmountWithheld(target, amount);
// Since the updateWithheldAmount is requested, no additional withheldAmount must be accounted for
// to avoid double accounting as there was no funds deposited.
if (!updateWithheldAmount) {
// Withhold OLAS for further usage
localWithheldAmount += amount;
emit AmountWithheld(target, amount);
}

// Proceed to the next target
continue;
}

// Check the amount limit and adjust, if necessary
if (amount > limitAmount) {
uint256 targetWithheldAmount = amount - limitAmount;
localWithheldAmount += targetWithheldAmount;
amount = limitAmount;
// Since the updateWithheldAmount is requested, no additional withheldAmount must be accounted for
// to avoid double accounting as there was no funds deposited.
if (!updateWithheldAmount) {
// Withhold OLAS for further usage
uint256 targetWithheldAmount = amount - limitAmount;
localWithheldAmount += targetWithheldAmount;

emit AmountWithheld(target, targetWithheldAmount);
}

emit AmountWithheld(target, targetWithheldAmount);
amount = limitAmount;
}

// Update total to-be-deposited amount
totalAmount += amount;

uint256 olasBalance = IToken(olas).balanceOf(address(this));
// Check the OLAS balance and the contract being unpaused
if (olasBalance >= amount && localPaused == 1) {
Expand Down Expand Up @@ -256,7 +272,7 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {
emit MessageReceived(l1DepositProcessor, l1SourceChainId, data);

// Process the data
_processData(data);
_processData(data, false);
}

/// @dev Changes the owner address.
Expand Down Expand Up @@ -327,14 +343,33 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {
/// - Token transfer succeeds, message fails: call this function;
/// - Token transfer fails, message succeeds: re-send OLAS to the contract (separate vote).
/// @param data Bytes message data that was not delivered from L1.
function processDataMaintenance(bytes memory data) external {
/// @param updateWithheldAmount True, if withheld amount update is required (must be used if there is no bridge issue).
function processDataMaintenance(bytes memory data, bool updateWithheldAmount) external {
// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

// Process the data
_processData(data);
// Process the data and calculate deposited amounts
uint256 totalAmount = _processData(data, updateWithheldAmount);

// Update withheld amount
if (updateWithheldAmount) {
uint256 localWithheldAmount = withheldAmount;

// Check for overflow
if (totalAmount > localWithheldAmount) {
revert Overflow(totalAmount, localWithheldAmount);
}

// Update withheld amount
localWithheldAmount -= totalAmount;
withheldAmount = localWithheldAmount;

emit WithheldAmountUpdated(localWithheldAmount);
}

emit StakingMaintenanceDataProcessed(data);
}

/// @dev Syncs withheld token amount with L1.
Expand All @@ -346,6 +381,11 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {
}
_locked = 2;

// Check for the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

// Pause check
if (paused == 2) {
revert Paused();
Expand Down Expand Up @@ -397,6 +437,25 @@ abstract contract DefaultTargetDispenserL2 is IBridgeErrors {
_locked = 1;
}

/// @dev Updates withheld amount manually by the DAO in order to:
/// [1] Account for not recorded `processDataMaintenance()` amounts;
/// [2] Withheld amount update after balance migration to a new contract.
/// @notice The amount here must correspond to:
/// [1] The exact withheldAmount minus the accumulation of all the previous
/// unique amounts deposited via `processDataMaintenance()` function execution;
/// [2] Final OLAS balance of this contract address.
/// @param amount Updated withheld amount.
function updateWithheldAmountMaintenance(uint256 amount) external {
// Check the contract ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

withheldAmount = amount;

emit WithheldAmountUpdated(amount);
}

/// @dev Pause the contract.
function pause() external {
// Check for the contract ownership
Expand Down
60 changes: 60 additions & 0 deletions docs/flowchart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Tokenomics Flowchart

```mermaid
graph TD
%% Tokenomics
subgraph tokenomics [Tokenomics]
Treasury[Treasury]
Dispenser[Dispenser]
DonatorBlacklist[DonatorBlacklist]
Tokenomics[Tokenomics]
Depository[Depository]
GenericBondCalculator[Generic Bond Calculator]
DepositProcessorL1[DepositProcessorL1]
TargetDispenserL2[TargetDispenserL2]
end

subgraph governance [Governance]
OLAS_Token[OLAS Token]
Timelock@{ shape: div-rect, label: "Timelock" }
veOLAS[veOLAS]
end

subgraph registries [Registries]
AgentRegistry[Agent and Component Registry]
ServiceRegistry[Service Registry]
StakingProxy[StakingProxy]
end

LP_Token[LP Token]
Owner([OLAS or LP Token owner])
OwnerAgent[[Component or Agent Owner]]
AnyWallet([Any Wallet or Contract])

AnyWallet-->|depositServiceDonationETH|Treasury
DepositProcessorL1==>|bridge: tokens, message|TargetDispenserL2
Depository-->|calculatePayoutOLAS|GenericBondCalculator
Depository-->|reserveAmountForBondProgram, refundFromBondProgram|Tokenomics
Depository-->|depositTokenForOLAS|Treasury
Depository-->|transfer|OLAS_Token
Dispenser-->|claimOwnerIncentives, claimStakingIncentives|Tokenomics
Dispenser-->|sendMessage|DepositProcessorL1
Dispenser-->|withdrawToAccount|Treasury
GenericBondCalculator-->|getLastIDF|Tokenomics
Owner-->|deposit, redeem|Depository
OwnerAgent-->|claimOwnerRewards|Dispenser
TargetDispenserL2-->|deposit|StakingProxy
Timelock-->|changeOwner|Dispenser
Timelock-->|changeOwner|Tokenomics
Timelock-->|changeOwner, create, close|Depository
Timelock-->|changeOwner, withdraw, enableToken, disableToken|Treasury
Treasury<-->|trackServiceDonation, rebalanceTreasury|Tokenomics
Tokenomics-->|ownerOf, totalSupply|AgentRegistry
Tokenomics-->|getComponentIdsOfServiceId, getAgentIdsOfServiceId|ServiceRegistry
Tokenomics-->|inflationRemainder, totalSupply|OLAS_Token
Tokenomics-->|getVotes|veOLAS
Tokenomics-->|isDonatorBlacklisted|DonatorBlacklist
Treasury-->|drain|ServiceRegistry
Treasury-->|transferFrom|LP_Token
Treasury-->|mint, transfer|OLAS_Token
```
17 changes: 17 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[profile.default]
#solc = "0.8.30"
auto_detect_solc = true
src = "contracts"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 100
evm_version = "prague"

remappings = [
"canonical-weth=node_modules/canonical-weth",
"@gnosis.pm=node_modules/@gnosis.pm",
"@prb=node_modules/@prb",
"@uniswap=node_modules/@uniswap",
"wormhole-solidity-sdk=node_modules/wormhole-solidity-sdk",
]
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"eslint": "^8.57.0",
"solhint": "^5.0.3",
"ethers": "^5.7.2",
"hardhat": "^2.24.0",
"hardhat": "^2.25.0",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-deploy": "^0.11.43",
"hardhat-deploy-ethers": "^0.3.0-beta.13",
Expand All @@ -58,7 +58,7 @@
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"canonical-weth": "^1.4.0",
"eth-permit": "^0.2.3",
"ethereum-sources-downloader": "^0.1.21",
"ethereum-sources-downloader": "^0.2.1",
"wormhole-solidity-sdk": "^0.0.4"
}
}
32 changes: 19 additions & 13 deletions scripts/audit_chains/audit_contracts_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,21 +711,27 @@ async function main() {
const dataFromJSON = fs.readFileSync(configFile, "utf8");
const configs = JSON.parse(dataFromJSON);

const numChains = configs.length;

// ################################# VERIFY CONTRACTS WITH REPO #################################
console.log("\nVerifying deployed contracts vs the repo... If no error is output, then the contracts are correct.");

// Currently the verification is fo mainnet only
const network = "etherscan";
const contracts = configs[0]["contracts"];

// Verify contracts
for (let i = 0; i < contracts.length; i++) {
console.log("Checking " + contracts[i]["name"]);
const execSync = require("child_process").execSync;
try {
execSync("scripts/audit_chains/audit_repo_contract.sh " + network + " " + contracts[i]["name"] + " " + contracts[i]["address"]);
} catch (error) {
continue;
// Traverse all chains
for (let i = 0; i < numChains; i++) {
console.log("\n\nNetwork:", configs[i]["name"]);
const contracts = configs[i]["contracts"];
const chainId = configs[i]["chainId"];
console.log("chainId", chainId);

// Verify contracts
for (let j = 0; j < contracts.length; j++) {
console.log("Checking " + contracts[j]["name"]);
const execSync = require("child_process").execSync;
try {
execSync("scripts/audit_chains/audit_repo_contract.sh " + chainId + " " + contracts[j]["name"] + " " + contracts[j]["address"]);
} catch (err) {
err.stderr.toString();
}
}
}
// ################################# /VERIFY CONTRACTS WITH REPO #################################
Expand All @@ -735,7 +741,7 @@ async function main() {
"mainnet": "scripts/deployment/globals_mainnet.json",
"polygon": "scripts/deployment/staking/polygon/globals_polygon_mainnet.json",
"gnosis": "scripts/deployment/staking/gnosis/globals_gnosis_mainnet.json",
"arbitrumOne": "scripts/deployment/staking/arbitrum/globals_arbitrum_one.json",
"arbitrumOne": "scripts/deployment/staking/arbitrum/globals_arbitrum_mainnet.json",
"optimistic": "scripts/deployment/staking/optimistic/globals_optimistic_mainnet.json",
"base": "scripts/deployment/staking/base/globals_base_mainnet.json",
"celo": "scripts/deployment/staking/celo/globals_celo_mainnet.json",
Expand Down
Loading
Loading