111 lines
4.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import "../Tokens.sol";
import "../Timestamps.sol";
/// Used to identify an account. The first 20 bytes consist of the address of
/// the account holder, and the last 12 bytes consist of a discriminator value.
type AccountId is bytes32;
/// Records the token balance and the incoming and outgoing token flows
struct Account {
Balance balance;
Flow flow;
}
/// The account balance. Fits in 32 bytes to minimize storage costs.
/// A uint128 is used to record the amount of tokens, which should be more than
/// enough. Given a standard 18 decimal places for the ERC20 token, this still
/// allows for 10^20 whole coins.
struct Balance {
/// Available tokens can be transfered
uint128 available;
/// Designated tokens can no longer be transfered
uint128 designated;
}
/// The incoming and outgoing flows of an account. Fits in 32 bytes to minimize
/// storage costs.
struct Flow {
/// Rate of outgoing tokens
TokensPerSecond outgoing;
/// Rate of incoming tokens
TokensPerSecond incoming;
/// Last time that the flow was updated
Timestamp updated;
}
library Accounts {
using Accounts for Account;
using Tokens for TokensPerSecond;
using Timestamps for Timestamp;
/// Creates an account id from the account holder address and a discriminator.
/// The discriminiator can be used to create different accounts that belong to
/// the same account holder.
function encodeId(
address holder,
bytes12 discriminator
) internal pure returns (AccountId) {
bytes32 left = bytes32(bytes20(holder));
bytes32 right = bytes32(uint256(uint96(discriminator)));
return AccountId.wrap(left | right);
}
/// Extracts the account holder and the discriminator from the the account id
function decodeId(AccountId id) internal pure returns (address, bytes12) {
bytes32 unwrapped = AccountId.unwrap(id);
address holder = address(bytes20(unwrapped));
bytes12 discriminator = bytes12(uint96(uint256(unwrapped)));
return (holder, discriminator);
}
/// Calculates whether the available balance is sufficient to sustain the
/// outgoing flow of tokens until the specified timestamp
function isSolventAt(
Account memory account,
Timestamp timestamp
) internal pure returns (bool) {
Duration duration = account.flow.updated.until(timestamp);
uint128 outgoing = account.flow.outgoing.accumulate(duration);
return outgoing <= account.balance.available;
}
/// Updates the available and designated balances by accumulating the
/// outgoing and incoming flows up until the specified timestamp. Outgoing
/// tokens are deducted from the available balance. Incoming tokens are added
/// to the designated tokens.
function accumulateFlows(
Account memory account,
Timestamp timestamp
) internal pure {
Duration duration = account.flow.updated.until(timestamp);
account.balance.available -= account.flow.outgoing.accumulate(duration);
account.balance.designated += account.flow.incoming.accumulate(duration);
account.flow.updated = timestamp;
}
/// Starts an incoming flow of tokens at the specified rate. If there already
/// is a flow of incoming tokens, then its rate is increased accordingly.
function flowIn(Account memory account, TokensPerSecond rate) internal view {
account.accumulateFlows(Timestamps.currentTime());
account.flow.incoming = account.flow.incoming + rate;
}
/// Starts an outgoing flow of tokens at the specified rate. If there is
/// already a flow of incoming tokens, then these are used to pay for the
/// outgoing flow. If there are insuffient incoming tokens, then the outgoing
/// rate is increased.
function flowOut(Account memory account, TokensPerSecond rate) internal view {
account.accumulateFlows(Timestamps.currentTime());
if (rate <= account.flow.incoming) {
account.flow.incoming = account.flow.incoming - rate;
} else {
account.flow.outgoing = account.flow.outgoing + rate;
account.flow.outgoing = account.flow.outgoing - account.flow.incoming;
account.flow.incoming = TokensPerSecond.wrap(0);
}
}
}