Skip to content

Commit 3208bfc

Browse files
committed
split out GDAv1StorageLayout
1 parent efb29ee commit 3208bfc

File tree

4 files changed

+316
-275
lines changed

4 files changed

+316
-275
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// SPDX-License-Identifier: AGPLv3
2+
pragma solidity ^0.8.23;
3+
// open-zeppelin
4+
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
5+
// semantic-money
6+
import {
7+
BasicParticle,
8+
Value,
9+
Time,
10+
FlowRate
11+
} from "@superfluid-finance/solidity-semantic-money/src/SemanticMoney.sol";
12+
// superfluid
13+
import { ISuperfluidToken } from "../../interfaces/superfluid/ISuperfluidToken.sol";
14+
import {
15+
IGeneralDistributionAgreementV1
16+
} from "../../interfaces/agreements/gdav1/IGeneralDistributionAgreementV1.sol";
17+
import { ISuperfluidPool } from "../../interfaces/agreements/gdav1/ISuperfluidPool.sol";
18+
19+
20+
/* @title Storage layout reader for the GDAv1.
21+
* @author Superfluid
22+
* @notice
23+
*
24+
* Storage Layout Notes
25+
* ## Agreement State
26+
*
27+
* Universal Index Data
28+
* slotId = _universalIndexStateSlotId() or 0
29+
* msg.sender = address of GDAv1
30+
* account = context.msgSender
31+
* Universal Index Data stores a Basic Particle for an account as well as the total buffer and
32+
* whether the account is a pool or not.
33+
*
34+
* SlotsBitmap Data
35+
* slotId = _POOL_SUBS_BITMAP_STATE_SLOT_ID or 1
36+
* msg.sender = address of GDAv1
37+
* account = context.msgSender
38+
* Slots Bitmap Data Slot stores a bitmap of the slots that are "enabled" for a pool member.
39+
*
40+
* Pool Connections Data Slot Id Start
41+
* slotId (start) = _POOL_CONNECTIONS_DATA_STATE_SLOT_ID_START or 1 << 128 or 340282366920938463463374607431768211456
42+
* msg.sender = address of GDAv1
43+
* account = context.msgSender
44+
* Pool Connections Data Slot Id Start indicates the starting slot for where we begin to store the pools that a
45+
* pool member is a part of.
46+
*
47+
* ## Agreement Data
48+
* NOTE The Agreement Data slot is calculated with the following function:
49+
* keccak256(abi.encode("AgreementData", agreementClass, agreementId))
50+
* agreementClass = address of GDAv1
51+
* agreementId = DistributionFlowId | PoolMemberId
52+
*
53+
* DistributionFlowId =
54+
* keccak256(abi.encode(block.chainid, "distributionFlow", from, pool))
55+
* DistributionFlowId stores FlowDistributionData between a sender (from) and pool.
56+
*
57+
* PoolMemberId =
58+
* keccak256(abi.encode(block.chainid, "poolMember", member, pool))
59+
* PoolMemberId stores PoolMemberData for a member at a pool.
60+
*/
61+
library GDAv1StorageReader {
62+
uint256 internal constant ACCOUNT_DATA_STATE_SLOT_ID = 0;
63+
64+
// # Account Data Operations
65+
//
66+
// Account data includes:
67+
// - Semantic universal index (a basic particle)
68+
// - buffer amount
69+
// - isPool flag
70+
// store buffer (96) and one bit to specify is pool in free
71+
// --------+------------------+------------------+------------------+------------------+
72+
// WORD 1: | flowRate | settledAt | totalBuffer | isPool |
73+
// --------+------------------+------------------+------------------+------------------+
74+
// | 96b | 32b | 96b | 32b |
75+
// --------+------------------+------------------+------------------+------------------+
76+
// WORD 2: | settledValue |
77+
// -------- ------------------+------------------+------------------+------------------+
78+
// | 256b |
79+
// --------+------------------+------------------+------------------+------------------+
80+
81+
struct AccountData {
82+
int96 flowRate;
83+
uint32 settledAt;
84+
uint256 totalBuffer;
85+
bool isPool;
86+
int256 settledValue;
87+
}
88+
89+
/// @dev Update the universal index of the account data.
90+
function encodeUpdatedUniversalIndex(AccountData memory accountData, BasicParticle memory uIndex)
91+
internal
92+
pure
93+
returns (bytes32[] memory data)
94+
{
95+
data = new bytes32[](2);
96+
data[0] = bytes32(
97+
(uint256(int256(SafeCast.toInt96(FlowRate.unwrap(uIndex.flow_rate())))) << 160) |
98+
(uint256(SafeCast.toUint32(Time.unwrap(uIndex.settled_at()))) << 128) |
99+
(uint256(SafeCast.toUint96(accountData.totalBuffer)) << 32) |
100+
(accountData.isPool ? 1 : 0)
101+
);
102+
data[1] = bytes32(uint256(Value.unwrap(uIndex._settled_value)));
103+
}
104+
105+
/// @dev Update the total buffer of the account data.
106+
function encodeUpdatedTotalBuffer(AccountData memory accountData, uint256 totalBuffer)
107+
internal
108+
pure
109+
returns (bytes32[] memory data)
110+
{
111+
data = new bytes32[](1);
112+
data[0] = bytes32(
113+
(uint256(int256(accountData.flowRate)) << 160) |
114+
(uint256(accountData.settledAt) << 128) |
115+
(uint256(SafeCast.toUint96(totalBuffer)) << 32) |
116+
(accountData.isPool ? 1 : 0)
117+
);
118+
}
119+
120+
function decodeAccountData(bytes32[] memory data)
121+
internal
122+
pure
123+
returns (AccountData memory accountData)
124+
{
125+
uint256 a = uint256(data[0]);
126+
127+
if (a > 0) {
128+
accountData.flowRate = int96(int256(a >> 160) & int256(uint256(type(uint96).max)));
129+
accountData.settledAt = uint32(uint256(a >> 128) & uint256(type(uint32).max));
130+
accountData.totalBuffer = uint256(a >> 32) & uint256(type(uint96).max);
131+
accountData.isPool = a & 1 == 1;
132+
}
133+
134+
// encodeUpdatedTotalBuffer only encodes the first word
135+
if (data.length >= 2) {
136+
uint256 b = uint256(data[1]);
137+
accountData.settledValue = int256(b);
138+
}
139+
}
140+
141+
function getAccountData(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address owner)
142+
internal
143+
view
144+
returns (AccountData memory accountData)
145+
{
146+
return decodeAccountData(token.getAgreementStateSlot(address(gda), owner, ACCOUNT_DATA_STATE_SLOT_ID, 2));
147+
}
148+
149+
/// @dev returns true if the account is a pool
150+
function isPool(ISuperfluidToken token, IGeneralDistributionAgreementV1 gda, address account)
151+
internal
152+
view
153+
returns (bool)
154+
{
155+
uint256 a = uint256(token.getAgreementStateSlot(address(gda), account, ACCOUNT_DATA_STATE_SLOT_ID, 1)[0]);
156+
return a & 1 == 1;
157+
}
158+
159+
function getUniversalIndexFromAccountData(AccountData memory accountData)
160+
internal
161+
pure
162+
returns (BasicParticle memory uIndex)
163+
{
164+
uIndex._flow_rate = FlowRate.wrap(accountData.flowRate);
165+
uIndex._settled_at = Time.wrap(accountData.settledAt);
166+
uIndex._settled_value = Value.wrap(accountData.settledValue);
167+
}
168+
}
169+
170+
171+
/* @title Storage layout writer for the GDAv1.
172+
* @author Superfluid
173+
* @dev Due to how agreement framework works, `address(this)` must be the GeneralDistributionAgreementV1 itself.
174+
*/
175+
library GDAv1StorageWriter {
176+
function setUniversalIndex(ISuperfluidToken token,
177+
address owner,
178+
BasicParticle memory uIndex) internal
179+
{
180+
GDAv1StorageReader.AccountData memory accountData =
181+
GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner);
182+
183+
token.updateAgreementStateSlot(
184+
owner,
185+
GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID,
186+
GDAv1StorageReader.encodeUpdatedUniversalIndex(accountData, uIndex)
187+
);
188+
}
189+
190+
function setPool(ISuperfluidToken token, ISuperfluidPool pool)
191+
internal
192+
{
193+
bytes32[] memory data = new bytes32[](1);
194+
data[0] = bytes32(uint256(1));
195+
token.updateAgreementStateSlot(address(pool), GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID, data);
196+
}
197+
198+
function setTotalBuffer(ISuperfluidToken token,
199+
address owner,
200+
uint256 totalBuffer) internal
201+
{
202+
GDAv1StorageReader.AccountData memory accountData =
203+
GDAv1StorageReader.getAccountData(token, IGeneralDistributionAgreementV1(address(this)), owner);
204+
205+
token.updateAgreementStateSlot(
206+
owner,
207+
GDAv1StorageReader.ACCOUNT_DATA_STATE_SLOT_ID,
208+
GDAv1StorageReader.encodeUpdatedTotalBuffer(accountData, totalBuffer)
209+
);
210+
}
211+
}

0 commit comments

Comments
 (0)