Skip to content

Commit 468e5dc

Browse files
TepNiksakulstra
andauthored
refactor: simplify and better document changes in ATokenWithDelegation
Co-authored-by: Lukas <lukasstrassel@googlemail.com>
1 parent a964a09 commit 468e5dc

7 files changed

Lines changed: 490 additions & 266 deletions

File tree

docs/3.4/Aave-v3.4-features.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,6 @@ Currently, the treasury on each aToken is stored in storage, although it is the
9393
This causes unnecessary storage reads on liquidations and mintToTreasury.
9494
Therefore, in Aave 3.4 the treasury was moved to an immutable, which reduces gas cost on these methods.
9595

96-
### Changed implementation of aToken for UNI token in Mainnet Core pool
97-
98-
In the previous version the (implementation)[https://etherscan.io/address/0x21714092d90c7265f52fdfdae068ec11a23c6248] of (UNI aToken)[https://etherscan.io/address/0xF6D2224916DDFbbab6e6bd0D1B7034f4Ae0CaB18] has a function `delegateUnderlyingTo` for the Pool admin that allows to delegate voting power of aToken suppliers to some delegatee. In Aave 3.4 the implementation will be changed to a default one and the function will be removed.
99-
10096
### Errors
10197

10298
Aave has historically used `error codes` opposed to `Error` signatures, because there was a preference for `require(cond, error)` which did not support signatures.
@@ -109,6 +105,26 @@ While this change could be **breaking** for anyone relying on exact error codes,
109105
- Every single Aave release since v3.0 had breaking changes in regards to error emission (either due to new Errors or the order of Errors)
110106
- We checked all the major integrations and did not find a single example of people relying on exact error codes
111107

108+
### Token Storage & implementation alignment
109+
110+
Currently there are 3 different versions of the aToken deployed:
111+
112+
- the main one you can find on this repository
113+
- a [custom version](https://etherscan.io/address/0xF6D2224916DDFbbab6e6bd0D1B7034f4Ae0CaB18) for UNI token voting delegation
114+
- a custom one for aAAVE that can be found [here](https://github.com/bgd-labs/aave-a-token-with-delegation)
115+
116+
In the previous version the (implementation)[https://etherscan.io/address/0x21714092d90c7265f52fdfdae068ec11a23c6248] of (UNI aToken)[https://etherscan.io/address/0xF6D2224916DDFbbab6e6bd0D1B7034f4Ae0CaB18] has a function `delegateUnderlyingTo` for the Pool admin that allows to delegate voting power of aToken suppliers to some delegatee. Delegating the suppliers UNI token to the AAVE DAO or similar is debatable and the feature has never been used. Therefore in Aave 3.4 the implementation will be changed to a default one and the function will be removed.
117+
118+
In order to remove code complexity between aAAVE and other aTokens, the storage layout between the versions was aligned.
119+
In practice this means that on ScaledBalanceTokenBase the `.balance` storage was changed from 128 to 120 bits. For Aave this storage change is perfectly fine given the following rational:
120+
121+
- the protocol works with uin256 and has no assumptions about the token storage **already**
122+
- the uint120 storage was audited for the case of AAVE, but the same artificial limitation can be applied for all tokens given that 2^120 ~= 10^36, still accepts values that exceed what could ever be required
123+
- the "freed" 8 bits are at the end of the current balance and always 0 (except for aAAve where the occupy delegation related storage)
124+
- the storage is not directly exposed on the token (no interface change)
125+
126+
The implementation for aAAVE was upgraded in line with the other tokens: [ATokenWithDelegation diff](./appendix/ATokenWithDelegation.diff), [BaseDelegation diff](./appendix/BaseDelegation.diff).
127+
112128
### Misc improvements
113129

114130
- Gas usage of `executeUseReserveAsCollateral` was greatly reduced by optimizing storage access
@@ -119,7 +135,6 @@ While this change could be **breaking** for anyone relying on exact error codes,
119135
- Self-liquidation is now forbidden. While this is a breaking change, it's unlikely to affect anyone, as there are essentially no onchain traces of people relying on this functionality.
120136
- SafeCast was upgraded from openzeppelin v4 to v5. The main difference is the usage of error signatures, reducing the codesize of various contracts.
121137
- VersionedInitializable now bricks the initializer on the implementation, so implementations no longer have to be initialized in order to prevent malicious initialization.
122-
- ScaledBalanceTokenBase changed the .balance storage from 128 to 120 bits. This was done in order to align storage across all tokens (currently on aAave on ethereum mainnet uses 120 bits storage)
123138
- Now all fees from flash-loans are sent to the `RESERVE_TREASURY_ADDRESS` in the form of the underlying token. Also, the function `FLASHLOAN_PREMIUM_TO_PROTOCOL` in the `Pool` contract now always returns `100_00` value.
124139
- Improved the accuracy and gas consumption of the `calculateCompoundedInterest` function without changing the formula. Inside calculations of the `second_term` and `third_term` variables now at first the function performs multiplications by `exp` and then divides by `SECONDS_PER_YEAR`. Previously it was the other way around, first there was division, then multiplication.
125140

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
diff --git a/docs/3.4/diffs/ATokenWithDelegation.sol b/src/contracts/protocol/tokenization/ATokenWithDelegation.sol
2+
index b3967209..717e91a9 100644
3+
--- a/docs/3.4/diffs/ATokenWithDelegation.sol
4+
+++ b/src/contracts/protocol/tokenization/ATokenWithDelegation.sol
5+
@@ -1,17 +1,23 @@
6+
-// SPDX-License-Identifier: MIT
7+
-pragma solidity ^0.8.0;
8+
+// SPDX-License-Identifier: BUSL-1.1
9+
+pragma solidity ^0.8.10;
10+
+
11+
+import {WadRayMath} from '../libraries/math/WadRayMath.sol';
12+
+import {IPool} from '../../interfaces/IPool.sol';
13+
14+
-import {IPool} from 'aave-v3-core/contracts/interfaces/IPool.sol';
15+
-import {BaseDelegation} from 'aave-token-v3/BaseDelegation.sol';
16+
import {AToken} from './AToken.sol';
17+
18+
+import {DelegationMode} from './base/DelegationMode.sol';
19+
+import {BaseDelegation} from './delegation/BaseDelegation.sol';
20+
+
21+
/**
22+
* @author BGD Labs
23+
* @notice contract that gives a tokens the delegation functionality. For now should only be used for AAVE aToken
24+
* @dev uint sizes are used taken into account that is tailored for AAVE token. In this AToken child we only update
25+
delegation balances. Balances amount is taken care of by AToken contract
26+
*/
27+
-contract ATokenWithDelegation is AToken, BaseDelegation {
28+
+abstract contract ATokenWithDelegation is AToken, BaseDelegation {
29+
+ using WadRayMath for uint256;
30+
+
31+
struct ATokenDelegationState {
32+
uint72 delegatedPropositionBalance;
33+
uint72 delegatedVotingBalance;
34+
@@ -19,7 +25,17 @@ contract ATokenWithDelegation is AToken, BaseDelegation {
35+
36+
mapping(address => ATokenDelegationState) internal _delegatedState;
37+
38+
- constructor(IPool pool) AToken(pool) {}
39+
+ /**
40+
+ * @dev Constructor.
41+
+ * @param pool The address of the Pool contract
42+
+ * @param rewardsController The address of the rewards controller contract
43+
+ * @param treasury The address of the treasury. This is where accrued interest is sent.
44+
+ */
45+
+ constructor(
46+
+ IPool pool,
47+
+ address rewardsController,
48+
+ address treasury
49+
+ ) AToken(pool, rewardsController, treasury) {}
50+
51+
function _getDomainSeparator() internal view override returns (bytes32) {
52+
return DOMAIN_SEPARATOR();
53+
@@ -57,20 +73,36 @@ contract ATokenWithDelegation is AToken, BaseDelegation {
54+
}
55+
56+
/**
57+
- * @notice Overrides the parent _transfer to force validated transfer() and delegation balance transfers
58+
- * @param from The source address
59+
- * @param to The destination address
60+
- * @param amount The amount getting transferred
61+
+ * @notice Transfers tokens and updates delegation balances. This function overrides the parent `_transfer`
62+
+ * to include delegation logic. It first updates the delegation balances based on the transfer
63+
+ * and then calls the parent's `_transfer` function to perform the actual token transfer.
64+
+ * @dev The amount is divided by the index inside this function to perform the scaling.
65+
+ * @param from The sender's address.
66+
+ * @param to The recipient's address.
67+
+ * @param amount The amount of tokens to transfer (non-scaled).
68+
+ * @param index The current liquidity index of the reserve.
69+
*/
70+
- function _transfer(address from, address to, uint256 amount, bool validate) internal override {
71+
- _delegationChangeOnTransfer(from, to, _getBalance(from), _getBalance(to), amount);
72+
- super._transfer(from, to, amount, validate);
73+
+ function _transfer(
74+
+ address from,
75+
+ address to,
76+
+ uint256 amount,
77+
+ uint256 index
78+
+ ) internal virtual override {
79+
+ _delegationChangeOnTransfer({
80+
+ from: from,
81+
+ to: to,
82+
+ fromBalanceBefore: _userState[from].balance,
83+
+ toBalanceBefore: _userState[to].balance,
84+
+ amount: uint256(amount).rayDiv(index)
85+
+ });
86+
+
87+
+ super._transfer(from, to, amount, index);
88+
}
89+
90+
/**
91+
* @notice Overrides the parent _mint to force delegation balance transfers
92+
* @param account The address receiving tokens
93+
- * @param amount The amount of tokens to mint
94+
+ * @param amount The amount of tokens to mint (scaled)
95+
*/
96+
function _mint(address account, uint120 amount) internal override {
97+
_delegationChangeOnTransfer(address(0), account, 0, _getBalance(account), amount);
98+
@@ -80,7 +112,7 @@ contract ATokenWithDelegation is AToken, BaseDelegation {
99+
/**
100+
* @notice Overrides the parent _burn to force delegation balance transfers
101+
* @param account The account whose tokens are burnt
102+
- * @param amount The amount of tokens to burn
103+
+ * @param amount The amount of tokens to burn (scaled)
104+
*/
105+
function _burn(address account, uint120 amount) internal override {
106+
_delegationChangeOnTransfer(account, address(0), _getBalance(account), 0, amount);
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
diff --git a/docs/3.4/diffs/BaseDelegation.sol b/src/contracts/protocol/tokenization/delegation/BaseDelegation.sol
2+
index c1a95d08..f15ccb69 100644
3+
--- a/docs/3.4/diffs/BaseDelegation.sol
4+
+++ b/src/contracts/protocol/tokenization/delegation/BaseDelegation.sol
5+
@@ -1,11 +1,14 @@
6+
// SPDX-License-Identifier: MIT
7+
pragma solidity ^0.8.0;
8+
9+
-import {ECDSA} from 'openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol';
10+
+import {MessageHashUtils} from 'openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol';
11+
+import {SafeCast} from 'openzeppelin-contracts/contracts/utils/math/SafeCast.sol';
12+
13+
-import {SafeCast72} from './utils/SafeCast72.sol';
14+
-import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol';
15+
-import {DelegationMode} from './DelegationAwareBalance.sol';
16+
+import {WadRayMath} from '../../libraries/math/WadRayMath.sol';
17+
+import {Errors} from '../../libraries/helpers/Errors.sol';
18+
+
19+
+import {IBaseDelegation} from './interfaces/IBaseDelegation.sol';
20+
+import {DelegationMode} from '../base/DelegationMode.sol';
21+
22+
/**
23+
* @notice The contract implements generic delegation functionality for the upcoming governance v3
24+
@@ -18,7 +21,7 @@ import {DelegationMode} from './DelegationAwareBalance.sol';
25+
* otherwise at least POWER_SCALE_FACTOR should be adjusted !!!
26+
* *************************************************************
27+
*/
28+
-abstract contract BaseDelegation is IGovernancePowerDelegationToken {
29+
+abstract contract BaseDelegation is IBaseDelegation {
30+
struct DelegationState {
31+
uint72 delegatedPropositionBalance;
32+
uint72 delegatedVotingBalance;
33+
@@ -81,7 +84,7 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
34+
DelegationState memory delegationState
35+
) internal virtual;
36+
37+
- /// @inheritdoc IGovernancePowerDelegationToken
38+
+ /// @inheritdoc IBaseDelegation
39+
function delegateByType(
40+
address delegatee,
41+
GovernancePowerType delegationType
42+
@@ -89,13 +92,13 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
43+
_delegateByType(msg.sender, delegatee, delegationType);
44+
}
45+
46+
- /// @inheritdoc IGovernancePowerDelegationToken
47+
+ /// @inheritdoc IBaseDelegation
48+
function delegate(address delegatee) external override {
49+
_delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING);
50+
_delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION);
51+
}
52+
53+
- /// @inheritdoc IGovernancePowerDelegationToken
54+
+ /// @inheritdoc IBaseDelegation
55+
function getDelegateeByType(
56+
address delegator,
57+
GovernancePowerType delegationType
58+
@@ -103,7 +106,7 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
59+
return _getDelegateeByType(delegator, _getDelegationState(delegator), delegationType);
60+
}
61+
62+
- /// @inheritdoc IGovernancePowerDelegationToken
63+
+ /// @inheritdoc IBaseDelegation
64+
function getDelegates(address delegator) external view override returns (address, address) {
65+
DelegationState memory delegatorBalance = _getDelegationState(delegator);
66+
return (
67+
@@ -112,7 +115,7 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
68+
);
69+
}
70+
71+
- /// @inheritdoc IGovernancePowerDelegationToken
72+
+ /// @inheritdoc IBaseDelegation
73+
function getPowerCurrent(
74+
address user,
75+
GovernancePowerType delegationType
76+
@@ -122,10 +125,12 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
77+
? _getBalance(user)
78+
: 0;
79+
uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType);
80+
+
81+
+ // The power returned is the scaled power, assuming an index of 1e27. The voting strategy is based on the same assumption.
82+
return userOwnPower + userDelegatedPower;
83+
}
84+
85+
- /// @inheritdoc IGovernancePowerDelegationToken
86+
+ /// @inheritdoc IBaseDelegation
87+
function getPowersCurrent(address user) external view override returns (uint256, uint256) {
88+
return (
89+
getPowerCurrent(user, GovernancePowerType.VOTING),
90+
@@ -133,7 +138,7 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
91+
);
92+
}
93+
94+
- /// @inheritdoc IGovernancePowerDelegationToken
95+
+ /// @inheritdoc IBaseDelegation
96+
function metaDelegateByType(
97+
address delegator,
98+
address delegatee,
99+
@@ -143,10 +148,11 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
100+
bytes32 r,
101+
bytes32 s
102+
) external override {
103+
- require(delegator != address(0), 'INVALID_OWNER');
104+
+ require(delegator != address(0), Errors.ZeroAddressNotValid());
105+
//solium-disable-next-line
106+
- require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
107+
- bytes32 digest = ECDSA.toTypedDataHash(
108+
+ require(block.timestamp <= deadline, Errors.InvalidExpiration());
109+
+
110+
+ bytes32 digest = MessageHashUtils.toTypedDataHash(
111+
_getDomainSeparator(),
112+
keccak256(
113+
abi.encode(
114+
@@ -160,11 +166,12 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
115+
)
116+
);
117+
118+
- require(delegator == ECDSA.recover(digest, v, r, s), 'INVALID_SIGNATURE');
119+
+ require(delegator == ecrecover(digest, v, r, s), Errors.InvalidSignature());
120+
+
121+
_delegateByType(delegator, delegatee, delegationType);
122+
}
123+
124+
- /// @inheritdoc IGovernancePowerDelegationToken
125+
+ /// @inheritdoc IBaseDelegation
126+
function metaDelegate(
127+
address delegator,
128+
address delegatee,
129+
@@ -173,17 +180,19 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
130+
bytes32 r,
131+
bytes32 s
132+
) external override {
133+
- require(delegator != address(0), 'INVALID_OWNER');
134+
+ require(delegator != address(0), Errors.ZeroAddressNotValid());
135+
//solium-disable-next-line
136+
- require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
137+
- bytes32 digest = ECDSA.toTypedDataHash(
138+
+ require(block.timestamp <= deadline, Errors.InvalidExpiration());
139+
+
140+
+ bytes32 digest = MessageHashUtils.toTypedDataHash(
141+
_getDomainSeparator(),
142+
keccak256(
143+
abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, _incrementNonces(delegator), deadline)
144+
)
145+
);
146+
147+
- require(delegator == ECDSA.recover(digest, v, r, s), 'INVALID_SIGNATURE');
148+
+ require(delegator == ecrecover(digest, v, r, s), Errors.InvalidSignature());
149+
+
150+
_delegateByType(delegator, delegatee, GovernancePowerType.VOTING);
151+
_delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION);
152+
}
153+
@@ -213,10 +222,10 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken {
154+
155+
// we use uint72, because this is the most optimal for AaveTokenV3
156+
// To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR
157+
- uint72 impactOnDelegationBefore72 = SafeCast72.toUint72(
158+
+ uint72 impactOnDelegationBefore72 = SafeCast.toUint72(
159+
impactOnDelegationBefore / POWER_SCALE_FACTOR
160+
);
161+
- uint72 impactOnDelegationAfter72 = SafeCast72.toUint72(
162+
+ uint72 impactOnDelegationAfter72 = SafeCast.toUint72(
163+
impactOnDelegationAfter / POWER_SCALE_FACTOR
164+
);
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"full amount; sender: with delegations, ->disableCollateral; receiver: without delegations, ->enableCollateral": "150593",
3-
"full amount; sender: with delegations, delegatee, ->disableCollateral; receiver: with delegations, delegatee, ->enableCollateral": "142719",
4-
"full amount; sender: with delegations, not delegatee, ->disableCollateral; receiver: with delegations, not delegatee, ->enableCollateral": "142719",
5-
"full amount; sender: without delegations, ->disableCollateral; receiver: with delegations, ->enableCollateral": "133492",
6-
"full amount; sender: without delegations, delegatee, ->disableCollateral; receiver: without delegations, delegatee, ->enableCollateral": "141366",
7-
"full amount; sender: without delegations, not delegatee, ->disableCollateral; receiver: without delegations, not delegatee, ->enableCollateral": "141366"
2+
"full amount; sender: with delegations, ->disableCollateral; receiver: without delegations, ->enableCollateral": "159429",
3+
"full amount; sender: with delegations, delegatee, ->disableCollateral; receiver: with delegations, delegatee, ->enableCollateral": "155367",
4+
"full amount; sender: with delegations, not delegatee, ->disableCollateral; receiver: with delegations, not delegatee, ->enableCollateral": "155367",
5+
"full amount; sender: without delegations, ->disableCollateral; receiver: with delegations, ->enableCollateral": "142329",
6+
"full amount; sender: without delegations, delegatee, ->disableCollateral; receiver: without delegations, delegatee, ->enableCollateral": "146392",
7+
"full amount; sender: without delegations, not delegatee, ->disableCollateral; receiver: without delegations, not delegatee, ->enableCollateral": "146392"
88
}

0 commit comments

Comments
 (0)