diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index 1efb159..45d2c63 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -75,67 +75,6 @@ contract Circles is ERC1155, ICirclesErrors { DiscountedBalances(_inflation_day_zero) {} - // Public functions - - /** - * Inflationary balance of an account for a Circles identifier. Careful, - * calculating the inflationary balance can introduce numerical errors - * in the least significant digits (order of few attoCircles). - * @param _account Address for which the balance is queried. - * @param _id Circles identifier for which the balance is queried. - */ - function inflationaryBalanceOf(address _account, uint256 _id) public view returns (uint256) { - return _inflationaryBalanceOf(_account, _id); - } - - /** - * @notice safeInflationaryTransferFrom transfers Circles from one address to another by specifying inflationary units. - * @param _from Address from which the Circles are transferred. - * @param _to Address to which the Circles are transferred. - * @param _id Circles indentifier for which the Circles are transferred. - * @param _inflationaryValue Inflationary value of the Circles transferred. - * @param _data Data to pass to the receiver. - */ - function safeInflationaryTransferFrom( - address _from, - address _to, - uint256 _id, - uint256 _inflationaryValue, - bytes memory _data - ) public { - address sender = _msgSender(); - if (_from != sender && !isApprovedForAll(_from, sender)) { - revert ERC1155MissingApprovalForAll(sender, _from); - } - // convert inflationary value to todays demurrage value - uint256 value = convertInflationaryToDemurrageValue(_inflationaryValue, day(block.timestamp)); - _safeTransferFrom(_from, _to, _id, value, _data); - } - - /** - * @notice safeInflationaryBatchTransferFrom transfers Circles from one address to another by specifying inflationary units. - * @param _from Address from which the Circles are transferred. - * @param _to Address to which the Circles are transferred. - * @param _ids Batch of Circles identifiers for which the Circles are transferred. - * @param _inflationaryValues Batch of inflationary values of the Circles transferred. - * @param _data Data to pass to the receiver. - */ - function safeInflationaryBatchTransferFrom( - address _from, - address _to, - uint256[] memory _ids, - uint256[] memory _inflationaryValues, - bytes memory _data - ) public { - address sender = _msgSender(); - if (_from != sender && !isApprovedForAll(_from, sender)) { - revert ERC1155MissingApprovalForAll(sender, _from); - } - uint64 today = day(block.timestamp); - uint256[] memory values = convertBatchInflationaryToDemurrageValues(_inflationaryValues, today); - _safeBatchTransferFrom(_from, _to, _ids, values, _data); - } - // Internal functions /** diff --git a/src/circles/InflationaryOperator.sol b/src/circles/InflationaryOperator.sol new file mode 100644 index 0000000..7097679 --- /dev/null +++ b/src/circles/InflationaryOperator.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.24; + +import "../hub/IHub.sol"; +import "./Demurrage.sol"; + +contract InflationaryCirclesOperator is Demurrage { + // Storage + + IHubV2 public hub; + + // Errors + + error InflationaryCirclesOperatorOnlyActOnBalancesOfSender(address sender, address from); + + // Modifier + + modifier OnlyActOnBalancesOfSender(address _from) { + if (_from != msg.sender) { + // only accept requests that act on the balances of msg.sender + revert InflationaryCirclesOperatorOnlyActOnBalancesOfSender(msg.sender, _from); + } + _; + } + + // Constructor + + constructor(IHubV2 _hub) { + hub = _hub; + } + + // Public functions + + /** + * Inflationary balance of an account for a Circles identifier. Careful, + * calculating the inflationary balance can introduce numerical errors + * in the least significant digits (order of few attoCircles). + * @param _account Address for which the balance is queried. + * @param _id Circles identifier for which the balance is queried. + */ + function inflationaryBalanceOf(address _account, uint256 _id) public view returns (uint256) { + return _inflationaryBalanceOf(_account, _id); + } + + /** + * @notice safeInflationaryTransferFrom transfers Circles from one address to another by specifying inflationary units. + * @param _from Address from which the Circles are transferred. + * @param _to Address to which the Circles are transferred. + * @param _id Circles indentifier for which the Circles are transferred. + * @param _inflationaryValue Inflationary value of the Circles transferred. + * @param _data Data to pass to the receiver. + */ + function safeInflationaryTransferFrom( + address _from, + address _to, + uint256 _id, + uint256 _inflationaryValue, + bytes memory _data + ) public OnlyActOnBalancesOfSender(_from) { + // convert inflationary value to todays demurrage value + uint256 value = convertInflationaryToDemurrageValue(_inflationaryValue, day(block.timestamp)); + // if from has this operator authorized, it can call ERC1155:safeTransferFrom + hub.safeTransferFrom(_from, _to, _id, value, _data); + } + + /** + * @notice safeInflationaryBatchTransferFrom transfers Circles from one address to another by specifying inflationary units. + * @param _from Address from which the Circles are transferred. + * @param _to Address to which the Circles are transferred. + * @param _ids Batch of Circles identifiers for which the Circles are transferred. + * @param _inflationaryValues Batch of inflationary values of the Circles transferred. + * @param _data Data to pass to the receiver. + */ + function safeInflationaryBatchTransferFrom( + address _from, + address _to, + uint256[] memory _ids, + uint256[] memory _inflationaryValues, + bytes memory _data + ) public OnlyActOnBalancesOfSender(_from) { + uint256[] memory values = convertBatchInflationaryToDemurrageValues(_inflationaryValues, day(block.timestamp)); + hub.safeBatchTransferFrom(_from, _to, _ids, values, _data); + } + + // Internal functions + + /** + * @dev Calculate the inflationary balance of a discounted balance + * @param _account Address of the account to calculate the balance of + * @param _id Circles identifier for which to calculate the balance + */ + function _inflationaryBalanceOf(address _account, uint256 _id) internal view returns (uint256) { + // retrieve the balance in demurrage units (of today) + uint256 balance = hub.balanceOf(_account, _id); + return _calculateInflationaryBalance(balance, day(block.timestamp)); + } +} diff --git a/src/hub/Hub.sol b/src/hub/Hub.sol index fad09de..6aa3846 100644 --- a/src/hub/Hub.sol +++ b/src/hub/Hub.sol @@ -437,15 +437,14 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { // Only human can call stop. revert CirclesHubMustBeHuman(msg.sender, 2); } - uint96 lastMintTime = mintTimes[msg.sender].lastMintTime; + MintTime storage mintTime = mintTimes[msg.sender]; // check if already stopped - if (lastMintTime == INDEFINITE_FUTURE) { + if (mintTime.lastMintTime == INDEFINITE_FUTURE) { return; } // stop future mints of personal Circles // by setting the last mint time to indefinite future. - lastMintTime = INDEFINITE_FUTURE; - mintTimes[msg.sender].lastMintTime = lastMintTime; + mintTime.lastMintTime = INDEFINITE_FUTURE; emit Stopped(msg.sender); } @@ -459,8 +458,8 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { // Only personal Circles can have a status of boolean stopped. revert CirclesHubMustBeHuman(_human, 3); } - uint96 lastMintTime = mintTimes[msg.sender].lastMintTime; - return (lastMintTime == INDEFINITE_FUTURE); + MintTime storage mintTime = mintTimes[msg.sender]; + return (mintTime.lastMintTime == INDEFINITE_FUTURE); } /** @@ -613,7 +612,6 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { } /** - * @notice Returns true if the flow to the receiver is permitted. * The receiver must trust the Circles being sent, and the Circles avatar associated with * the Circles must trust the receiver. @@ -945,10 +943,9 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { // set the last mint time to the current timestamp for invited human // and register the v1 Circles contract status v1CirclesStatus = _avatarV1CirclesStatus(_human); - MintTime memory mintTime = mintTimes[_human]; + MintTime storage mintTime = mintTimes[_human]; mintTime.mintV1Status = v1CirclesStatus; mintTime.lastMintTime = uint96(block.timestamp); - mintTimes[_human] = mintTime; // trust self indefinitely, cannot be altered later _trust(_human, _human, INDEFINITE_FUTURE); @@ -1066,7 +1063,7 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { * @param _mintV1Status Mint status of the v1 Circles contract. */ function _updateMintV1Status(address _human, address _mintV1Status) internal { - MintTime memory mintTime = mintTimes[_human]; + MintTime storage mintTime = mintTimes[_human]; // precautionary check to ensure that the last mint time is already set // as this marks whether an avatar is registered as human or not if (mintTime.lastMintTime == 0) { @@ -1078,7 +1075,6 @@ contract Hub is Circles, TypeDefinitions, IHubErrors { if (mintTime.mintV1Status != _mintV1Status) { mintTime.mintV1Status = _mintV1Status; mintTime.lastMintTime = uint96(block.timestamp); - mintTimes[_human] = mintTime; } }