Skip to content

Commit

Permalink
Merge pull request #148 from CirclesUBI/20240503-events-discountcost
Browse files Browse the repository at this point in the history
(circles): introduce event DiscountCost
  • Loading branch information
jaensen authored May 10, 2024
2 parents 4c0d5a5 + f5a3099 commit dea17c1
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/circles/Circles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ contract Circles is ERC1155, ICirclesErrors {
_calculateDiscountedBalance(totalSupplyBalance.balance, today - totalSupplyBalance.lastUpdatedDay) + _value;
if (newTotalSupply > MAX_VALUE) {
// DiscountedBalances: balance exceeds maximum value
revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, newTotalSupply, 2);
revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, newTotalSupply, 2);
}
totalSupplyBalance.balance = uint192(newTotalSupply);
totalSupplyBalance.lastUpdatedDay = today;
Expand Down
6 changes: 5 additions & 1 deletion src/circles/Demurrage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity >=0.8.24;
import "../errors/Errors.sol";
import "../lib/Math64x64.sol";

contract Demurrage is ICirclesERC1155Errors {
contract Demurrage is ICirclesDemurrageErrors {
// Type declarations

/**
Expand Down Expand Up @@ -142,6 +142,10 @@ contract Demurrage is ICirclesERC1155Errors {
*/
int128[15] internal R;

// Events

event DiscountCost(address indexed account, uint256 indexed id, uint256 discountCost);

// Constructor

constructor() {
Expand Down
52 changes: 43 additions & 9 deletions src/circles/DiscountedBalances.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,30 @@ contract DiscountedBalances is Demurrage {
* @param _account Address of the account to calculate the balance of
* @param _id Circles identifier for which to calculate the balance
* @param _day Day since inflation_day_zero to calculate the balance for
* @return balanceOnDay_ The discounted balance of the account for the Circles identifier on specified day
* @return discountCost_ The discount cost of the demurrage of the balance since the last update
*/
function balanceOfOnDay(address _account, uint256 _id, uint64 _day) public view returns (uint256) {
function balanceOfOnDay(address _account, uint256 _id, uint64 _day)
public
view
returns (uint256 balanceOnDay_, uint256 discountCost_)
{
DiscountedBalance memory discountedBalance = discountedBalances[_id][_account];
return _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay);
if (_day < discountedBalance.lastUpdatedDay) {
// DiscountedBalances: day is before last updated day
revert CirclesDemurrageDayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 0);
}
uint256 dayDifference;
unchecked {
dayDifference = _day - discountedBalance.lastUpdatedDay;
}
// Calculate the discounted balance
balanceOnDay_ = _calculateDiscountedBalance(discountedBalance.balance, dayDifference);
// Calculate the discount cost; this can be unchecked as cost is strict positive
unchecked {
discountCost_ = discountedBalance.balance - balanceOnDay_;
}
return (balanceOnDay_, discountCost_);
}

/**
Expand All @@ -63,7 +83,7 @@ contract DiscountedBalances is Demurrage {
function _updateBalance(address _account, uint256 _id, uint256 _balance, uint64 _day) internal {
if (_balance > MAX_VALUE) {
// DiscountedBalances: balance exceeds maximum value
revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, _balance, 0);
revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, _balance, 0);
}
DiscountedBalance storage discountedBalance = discountedBalances[_id][_account];
discountedBalance.balance = uint192(_balance);
Expand All @@ -79,14 +99,28 @@ contract DiscountedBalances is Demurrage {
*/
function _discountAndAddToBalance(address _account, uint256 _id, uint256 _value, uint64 _day) internal {
DiscountedBalance storage discountedBalance = discountedBalances[_id][_account];
uint256 discountedBalanceValue =
_calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay);
uint256 newBalance = discountedBalanceValue + _value;
if (newBalance > MAX_VALUE) {
if (_day < discountedBalance.lastUpdatedDay) {
// DiscountedBalances: day is before last updated day
revert CirclesDemurrageDayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 1);
}
uint256 dayDifference;
unchecked {
dayDifference = _day - discountedBalance.lastUpdatedDay;
}
uint256 discountedBalanceOnDay = _calculateDiscountedBalance(discountedBalance.balance, dayDifference);
// Calculate the discount cost; this can be unchecked as cost is strict positive
unchecked {
uint256 discountCost = discountedBalance.balance - discountedBalanceOnDay;
if (discountCost > 0) {
emit DiscountCost(_account, _id, discountCost);
}
}
uint256 updatedBalance = discountedBalanceOnDay + _value;
if (updatedBalance > MAX_VALUE) {
// DiscountedBalances: balance exceeds maximum value
revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, newBalance, 1);
revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, updatedBalance, 1);
}
discountedBalance.balance = uint192(newBalance);
discountedBalance.balance = uint192(updatedBalance);
discountedBalance.lastUpdatedDay = _day;
}
}
14 changes: 7 additions & 7 deletions src/circles/ERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC
// Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
string private _uri;

// Events

event ConvertInflation(uint256 inflationValue, uint256 demurrageValue, uint64 day);

// Constructor

/**
Expand Down Expand Up @@ -73,7 +69,8 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC
* @dev See {IERC1155-balanceOf}.
*/
function balanceOf(address _account, uint256 _id) public view returns (uint256) {
return balanceOfOnDay(_account, _id, day(block.timestamp));
(uint256 balance,) = balanceOfOnDay(_account, _id, day(block.timestamp));
return balance;
}

/**
Expand All @@ -93,7 +90,7 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC
uint256[] memory batchBalances = new uint256[](_accounts.length);

for (uint256 i = 0; i < _accounts.length; ++i) {
batchBalances[i] = balanceOfOnDay(_accounts.unsafeMemoryAccess(i), _ids.unsafeMemoryAccess(i), today);
(batchBalances[i],) = balanceOfOnDay(_accounts.unsafeMemoryAccess(i), _ids.unsafeMemoryAccess(i), today);
}

return batchBalances;
Expand Down Expand Up @@ -171,10 +168,13 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC
uint256 value = values.unsafeMemoryAccess(i);

if (from != address(0)) {
uint256 fromBalance = balanceOfOnDay(from, id, today);
(uint256 fromBalance, uint256 discountCost) = balanceOfOnDay(from, id, today);
if (fromBalance < value) {
revert ERC1155InsufficientBalance(from, fromBalance, value, id);
}
if (discountCost > 0) {
emit DiscountCost(from, id, discountCost);
}
unchecked {
// Overflow not possible: value <= fromBalance
_updateBalance(from, id, fromBalance - value, today);
Expand Down
12 changes: 8 additions & 4 deletions src/errors/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ interface IHubErrors {
error CirclesHubNettedFlowMismatch(uint16 vertexPosition, int256 matrixNettedFlow, int256 streamNettedFlow);
}

interface ICirclesERC1155Errors {
interface ICirclesDemurrageErrors {
error CirclesERC1155MintBlocked(address human, address mintV1Status);

error CirclesERC1155AmountExceedsMaxUint190(address account, uint256 circlesId, uint256 amount, uint8 code);
error CirclesDemurrageAmountExceedsMaxUint190(address account, uint256 circlesId, uint256 amount, uint8 code);

error CirclesDemurrageDayBeforeLastUpdatedDay(
address account, uint256 circlesId, uint64 day, uint64 lastUpdatedDay, uint8 code
);

error CirclesERC1155CannotReceiveBatch(uint8 code);
}

interface ICirclesErrors {
Expand All @@ -54,8 +60,6 @@ interface ICirclesErrors {

error CirclesInvalidParameter(uint256 parameter, uint8 code);

error CirclesERC1155CannotReceiveBatch(uint8 code);

error CirclesAmountOverflow(uint256 amount, uint8 code);

error CirclesArraysLengthMismatch(uint256 lengthArray1, uint256 lengthArray2, uint8 code);
Expand Down
10 changes: 4 additions & 6 deletions src/lift/DemurrageCircles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E

INameRegistry public nameRegistry;

address public avatar;

// Events

event Deposit(address indexed account, uint256 amount, uint256 inflationaryAmount);
event DepositDemurraged(address indexed account, uint256 amount, uint256 inflationaryAmount);

event Withdraw(address indexed account, uint256 amount, uint256 inflationaryAmount);
event WithdrawDemurraged(address indexed account, uint256 amount, uint256 inflationaryAmount);

// Modifiers

Expand Down Expand Up @@ -74,7 +72,7 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E

uint256 inflationaryAmount = _calculateInflationaryBalance(_amount, day(block.timestamp));

emit Withdraw(msg.sender, _amount, inflationaryAmount);
emit WithdrawDemurraged(msg.sender, _amount, inflationaryAmount);
}

function totalSupply() external view override returns (uint256) {
Expand Down Expand Up @@ -107,7 +105,7 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E

uint256 inflationaryAmount = _calculateInflationaryBalance(_amount, day(block.timestamp));

emit Deposit(_from, _amount, inflationaryAmount);
emit DepositDemurraged(_from, _amount, inflationaryAmount);

return this.onERC1155Received.selector;
}
Expand Down
67 changes: 55 additions & 12 deletions src/lift/ERC20DiscountedBalances.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {

// State variables

address public avatar;

/**
* @dev The mapping of addresses to the discounted balances.
*/
Expand Down Expand Up @@ -55,7 +57,8 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {
}

function balanceOf(address _account) external view returns (uint256) {
return balanceOfOnDay(_account, day(block.timestamp));
(uint256 balance,) = balanceOfOnDay(_account, day(block.timestamp));
return balance;
}

function allowance(address _owner, address _spender) external view returns (uint256) {
Expand All @@ -68,9 +71,28 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {

// Public functions

function balanceOfOnDay(address _account, uint64 _day) public view returns (uint256) {
function balanceOfOnDay(address _account, uint64 _day)
public
view
returns (uint256 balanceOnDay_, uint256 discountCost_)
{
DiscountedBalance memory discountedBalance = discountedBalances[_account];
return _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay);
if (_day < discountedBalance.lastUpdatedDay) {
// ERC20 DiscountedBalances: day is before last updated day
revert CirclesDemurrageDayBeforeLastUpdatedDay(
_account, toTokenId(avatar), _day, discountedBalance.lastUpdatedDay, 0
);
}
uint256 dayDifference;
unchecked {
dayDifference = _day - discountedBalance.lastUpdatedDay;
}
balanceOnDay_ = _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay);
// Calculate the discount cost; this can be unchecked as cost is strict positive
unchecked {
discountCost_ = discountedBalance.balance - balanceOnDay_;
}
return (balanceOnDay_, discountCost_);
}

// Internal functions
Expand All @@ -83,7 +105,7 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {
function _updateBalance(address _account, uint256 _balance, uint64 _day) internal {
if (_balance > MAX_VALUE) {
// Balance exceeds maximum value.
revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, _balance, 0);
revert CirclesDemurrageAmountExceedsMaxUint190(_account, toTokenId(avatar), _balance, 0);
}
DiscountedBalance storage discountedBalance = discountedBalances[_account];
discountedBalance.balance = uint192(_balance);
Expand All @@ -92,23 +114,41 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {

function _discountAndAddToBalance(address _account, uint256 _value, uint64 _day) internal {
DiscountedBalance storage discountedBalance = discountedBalances[_account];
uint256 newBalance = _calculateDiscountedBalanceAndCache(
discountedBalance.balance, _day - discountedBalance.lastUpdatedDay
) + _value;
if (newBalance > MAX_VALUE) {
if (_day < discountedBalance.lastUpdatedDay) {
// ERC20 DiscountedBalances: day is before last updated day
revert CirclesDemurrageDayBeforeLastUpdatedDay(
_account, toTokenId(avatar), _day, discountedBalance.lastUpdatedDay, 1
);
}
uint256 dayDifference;
unchecked {
dayDifference = _day - discountedBalance.lastUpdatedDay;
}
uint256 discountedBalanceOnDay = _calculateDiscountedBalanceAndCache(discountedBalance.balance, dayDifference);
unchecked {
uint256 discountCost = discountedBalance.balance - discountedBalanceOnDay;
if (discountCost > 0) {
emit DiscountCost(_account, toTokenId(avatar), discountCost);
}
}
uint256 updatedBalance = discountedBalanceOnDay + _value;
if (updatedBalance > MAX_VALUE) {
// Balance exceeds maximum value.
revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, newBalance, 1);
revert CirclesDemurrageAmountExceedsMaxUint190(_account, toTokenId(avatar), updatedBalance, 1);
}
discountedBalance.balance = uint192(newBalance);
discountedBalance.balance = uint192(updatedBalance);
discountedBalance.lastUpdatedDay = _day;
}

function _transfer(address _from, address _to, uint256 _amount) internal {
uint64 day = day(block.timestamp);
uint256 fromBalance = balanceOfOnDay(_from, day);
(uint256 fromBalance, uint256 discountCost) = balanceOfOnDay(_from, day);
if (fromBalance < _amount) {
revert ERC20InsufficientBalance(_from, fromBalance, _amount);
}
if (discountCost > 0) {
emit DiscountCost(_from, toTokenId(avatar), discountCost);
}
unchecked {
_updateBalance(_from, fromBalance - _amount, day);
}
Expand All @@ -124,10 +164,13 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {

function _burn(address _owner, uint256 _amount) internal {
uint64 day = day(block.timestamp);
uint256 ownerBalance = balanceOfOnDay(_owner, day);
(uint256 ownerBalance, uint256 discountCost) = balanceOfOnDay(_owner, day);
if (ownerBalance < _amount) {
revert ERC20InsufficientBalance(_owner, ownerBalance, _amount);
}
if (discountCost > 0) {
emit DiscountCost(_owner, toTokenId(avatar), discountCost);
}
unchecked {
_updateBalance(_owner, ownerBalance - _amount, day);
}
Expand Down
8 changes: 4 additions & 4 deletions src/lift/InflationaryCircles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc

// Events

event Deposit(address indexed account, uint256 amount, uint256 demurragedAmount);
event DepositInflationary(address indexed account, uint256 amount, uint256 demurragedAmount);

event Withdraw(address indexed account, uint256 amount, uint256 demurragedAmount);
event WithdrawInflationary(address indexed account, uint256 amount, uint256 demurragedAmount);

// Modifiers

Expand Down Expand Up @@ -80,7 +80,7 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc

hub.safeTransferFrom(address(this), msg.sender, toTokenId(avatar), demurragedAmount, "");

emit Withdraw(msg.sender, _amount, demurragedAmount);
emit WithdrawInflationary(msg.sender, _amount, demurragedAmount);
}

function name() external view returns (string memory) {
Expand Down Expand Up @@ -108,7 +108,7 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc
// calculate inflationary amount to mint to sender
uint256 inflationaryAmount = _mintFromDemurragedAmount(_from, _amount);

emit Deposit(_from, inflationaryAmount, _amount);
emit DepositInflationary(_from, inflationaryAmount, _amount);

return this.onERC1155Received.selector;
}
Expand Down
Loading

0 comments on commit dea17c1

Please sign in to comment.