Skip to content

Commit

Permalink
Implement Claims accounting as minimal balance (Uniswap#379)
Browse files Browse the repository at this point in the history
* Add MinimalBalance

* Initial commmit

* Router custodies Claims, has access to priviledged burnFrom anbd tests

* updategas

* remove 6909 lib

* yarn snapshots

* Add gas snaps for swapping from claims balance

* fix gas snaps by removing aux logic in router

* gas

* remove lib

* Add transfer to minimalBalance, update tests

* nit: rename

* add back custom errors

* move addition out of unchecked

* Add transfer overflow check

* Rename impl test

* nit comments

* comment#

* Remove unused inheritance

* remove comment

* Remove poolClaimTest

* fix interfaces

* Feedback

* Add address(0) and address(this) check for transfer

* remove address(0) check

* Remove batchBurn

* Move mock claims to diff file

* Add gas snaps for collect protocol fees

* Add balance checks, make balances mapping private

* Fix imports

* fix fs perms

* Remove uint256 in mapping and use Currency

* feedback

* Add gas snaps
  • Loading branch information
zhongeric authored and hyunchel committed Feb 21, 2024
1 parent 5a7085a commit 5c2dc73
Show file tree
Hide file tree
Showing 29 changed files with 368 additions and 203 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
197590
197447
2 changes: 1 addition & 1 deletion .forge-snapshots/cached dynamic fee, no hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
184225
184082
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 1 token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
95961
95906
2 changes: 1 addition & 1 deletion .forge-snapshots/donate gas with 2 tokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
153292
153271
1 change: 1 addition & 0 deletions .forge-snapshots/erc20 collect protocol fees.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
25011
2 changes: 1 addition & 1 deletion .forge-snapshots/gas overhead of no-op lock.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
58562
58439
2 changes: 1 addition & 1 deletion .forge-snapshots/initialize.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
38123
38079
2 changes: 1 addition & 1 deletion .forge-snapshots/mint with empty hook.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
307130
307014
2 changes: 1 addition & 1 deletion .forge-snapshots/mint with native token.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
275102
275044
2 changes: 1 addition & 1 deletion .forge-snapshots/mint.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
293738
293680
1 change: 1 addition & 0 deletions .forge-snapshots/native collect protocol fees.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
36677
2 changes: 1 addition & 1 deletion .forge-snapshots/poolManager bytecode size.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
29853
24366
2 changes: 1 addition & 1 deletion .forge-snapshots/simple swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
151412
151298
Original file line number Diff line number Diff line change
@@ -1 +1 @@
125053
124939
2 changes: 1 addition & 1 deletion .forge-snapshots/swap against liquidity.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
109741
109627
1 change: 1 addition & 0 deletions .forge-snapshots/swap burn claim for input.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
126356
1 change: 1 addition & 0 deletions .forge-snapshots/swap mint output as claim.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
166380
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with dynamic fee.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
196825
196682
2 changes: 1 addition & 1 deletion .forge-snapshots/swap with hooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
109719
109605
2 changes: 1 addition & 1 deletion .forge-snapshots/update dynamic fee in before swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
203442
203277
199 changes: 103 additions & 96 deletions .gas-snapshot

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/Claims.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;

import {Currency, CurrencyLibrary} from "./types/Currency.sol";
import {IClaims} from "./interfaces/IClaims.sol";

/// An intentionally barebones balance mapping only supporting mint/burn/transfer
contract Claims is IClaims {
using CurrencyLibrary for Currency;

// Mapping from Currency to account balances
mapping(Currency currency => mapping(address account => uint256)) private balances;

/// @inheritdoc IClaims
function balanceOf(address account, Currency currency) public view returns (uint256) {
return balances[currency][account];
}

/// @inheritdoc IClaims
function transfer(address to, Currency currency, uint256 amount) public {
if (to == address(this)) revert InvalidAddress();

if (amount > balances[currency][msg.sender]) revert InsufficientBalance();
unchecked {
balances[currency][msg.sender] -= amount;
}
balances[currency][to] += amount;
emit Transfer(msg.sender, to, currency, amount);
}

/// @notice Mint `amount` of currency to address
/// @param to The address to mint to
/// @param currency The currency to mint
/// @param amount The amount to mint
function _mint(address to, Currency currency, uint256 amount) internal {
balances[currency][to] += amount;
emit Mint(to, currency, amount);
}

/// @notice Burn `amount` of currency from msg.sender
/// @param currency The currency to mint
/// @param amount The amount to burn
/// @dev Will revert if the sender does not have enough balance
function _burn(Currency currency, uint256 amount) internal {
if (amount > balances[currency][msg.sender]) revert InsufficientBalance();
unchecked {
balances[currency][msg.sender] -= amount;
}
emit Burn(msg.sender, currency, amount);
}
}
39 changes: 11 additions & 28 deletions src/PoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ import {IHookFeeManager} from "./interfaces/IHookFeeManager.sol";
import {IPoolManager} from "./interfaces/IPoolManager.sol";
import {ILockCallback} from "./interfaces/callback/ILockCallback.sol";
import {Fees} from "./Fees.sol";
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import {Claims} from "./Claims.sol";
import {PoolId, PoolIdLibrary} from "./types/PoolId.sol";
import {BalanceDelta} from "./types/BalanceDelta.sol";

/// @notice Holds the state for all pools
contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Receiver {
contract PoolManager is IPoolManager, Fees, NoDelegateCall, Claims {
using PoolIdLibrary for PoolKey;
using SafeCast for *;
using Pool for *;
Expand Down Expand Up @@ -51,7 +50,7 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec

mapping(PoolId id => Pool.State) public pools;

constructor(uint256 controllerGasLimit) Fees(controllerGasLimit) ERC1155("") {}
constructor(uint256 controllerGasLimit) Fees(controllerGasLimit) {}

function _getPool(PoolKey memory key) private view returns (Pool.State storage) {
return pools[key.toId()];
Expand Down Expand Up @@ -323,12 +322,6 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
currency.transfer(to, amount);
}

/// @inheritdoc IPoolManager
function mint(Currency currency, address to, uint256 amount) external override noDelegateCall onlyByLocker {
_accountDelta(currency, amount.toInt128());
_mint(to, currency.toId(), amount, "");
}

/// @inheritdoc IPoolManager
function settle(Currency currency) external payable override noDelegateCall onlyByLocker returns (uint256 paid) {
uint256 reservesBefore = reservesOf[currency];
Expand All @@ -338,26 +331,16 @@ contract PoolManager is IPoolManager, Fees, NoDelegateCall, ERC1155, IERC1155Rec
_accountDelta(currency, -(paid.toInt128()));
}

function _burnAndAccount(Currency currency, uint256 amount) internal {
_burn(address(this), currency.toId(), amount);
_accountDelta(currency, -(amount.toInt128()));
}

function onERC1155Received(address, address, uint256 id, uint256 value, bytes calldata) external returns (bytes4) {
if (msg.sender != address(this)) revert NotPoolManagerToken();
_burnAndAccount(CurrencyLibrary.fromId(id), value);
return IERC1155Receiver.onERC1155Received.selector;
/// @inheritdoc IPoolManager
function mint(Currency currency, address to, uint256 amount) external noDelegateCall onlyByLocker {
_accountDelta(currency, amount.toInt128());
_mint(to, currency, amount);
}

function onERC1155BatchReceived(address, address, uint256[] calldata ids, uint256[] calldata values, bytes calldata)
external
returns (bytes4)
{
if (msg.sender != address(this)) revert NotPoolManagerToken();
for (uint256 i; i < ids.length; i++) {
_burnAndAccount(CurrencyLibrary.fromId(ids[i]), values[i]);
}
return IERC1155Receiver.onERC1155BatchReceived.selector;
/// @inheritdoc IPoolManager
function burn(Currency currency, uint256 amount) external noDelegateCall onlyByLocker {
_accountDelta(currency, -(amount.toInt128()));
_burn(currency, amount);
}

function setProtocolFees(PoolKey memory key) external {
Expand Down
31 changes: 31 additions & 0 deletions src/interfaces/IClaims.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Currency} from "../types/Currency.sol";

interface IClaims {
/// @notice Thrown when user has insufficient Claims balance
error InsufficientBalance();

/// @notice Thrown when transferring Claims to this address
error InvalidAddress();

/// @notice Get the balance of `account` for `currency`
/// @param account The account to get the balance of
/// @param currency The currency to get the balance of
function balanceOf(address account, Currency currency) external returns (uint256);

/// @notice Transfer `amount` of `currency` from sender to `to`
/// @param to The address to transfer to
/// @param currency The currency to transfer
/// @param amount The amount to transfer
/// @dev Will revert if the sender does not have enough balance
function transfer(address to, Currency currency, uint256 amount) external;

/// @notice Emitted when minting `amount` of currency Claims to address
event Mint(address indexed to, Currency indexed currency, uint256 amount);
/// @notice Emitted when burning `amount` of currency Claims from address
event Burn(address indexed from, Currency indexed currency, uint256 amount);
/// @notice Emitted when transferring `amount` of currency Claims
event Transfer(address indexed from, address indexed to, Currency indexed currency, uint256 amount);
}
9 changes: 6 additions & 3 deletions src/interfaces/IPoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ pragma solidity ^0.8.20;
import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {Pool} from "../libraries/Pool.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IHooks} from "./IHooks.sol";
import {IFees} from "./IFees.sol";
import {IClaims} from "./IClaims.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {Position} from "../libraries/Position.sol";

interface IPoolManager is IFees, IERC1155 {
interface IPoolManager is IFees, IClaims {
/// @notice Thrown when currencies touched has exceeded max of 256
error MaxCurrenciesTouched();

Expand Down Expand Up @@ -176,9 +176,12 @@ interface IPoolManager is IFees, IERC1155 {
/// @dev Can also be used as a mechanism for _free_ flash loans
function take(Currency currency, address to, uint256 amount) external;

/// @notice Called by the user to move value into ERC1155 balance
/// @notice Called by the user to move value into Claims balance
function mint(Currency token, address to, uint256 amount) external;

/// @notice Called by the user to redeem their Claims balance
function burn(Currency token, uint256 amount) external;

/// @notice Called by the user to pay what is owed
function settle(Currency token) external payable returns (uint256 paid);

Expand Down
19 changes: 19 additions & 0 deletions src/test/MockClaims.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {Claims} from "../Claims.sol";
import {IClaims} from "../interfaces/IClaims.sol";
import {CurrencyLibrary, Currency} from "../types/Currency.sol";

contract MockClaims is Claims {
using CurrencyLibrary for Currency;

function mint(address to, Currency currency, uint256 amount) public {
_mint(to, currency, amount);
}

function burn(Currency currency, uint256 amount) public {
_burn(currency, amount);
}
}
23 changes: 4 additions & 19 deletions src/test/PoolSwapTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity ^0.8.20;

import {CurrencyLibrary, Currency} from "../types/Currency.sol";
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";

import {ILockCallback} from "../interfaces/callback/ILockCallback.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
Expand Down Expand Up @@ -69,21 +68,14 @@ contract PoolSwapTest is ILockCallback {
manager.settle(data.key.currency0);
}
} else {
// the received hook on this transfer will burn the tokens
manager.safeTransferFrom(
data.sender,
address(manager),
uint256(uint160(Currency.unwrap(data.key.currency0))),
uint128(delta.amount0()),
""
);
manager.burn(data.key.currency0, uint128(delta.amount0()));
}
}
if (delta.amount1() < 0) {
if (data.testSettings.withdrawTokens) {
manager.take(data.key.currency1, data.sender, uint128(-delta.amount1()));
} else {
manager.mint(data.key.currency1, data.sender, uint128(-delta.amount1()));
manager.mint(data.key.currency1, address(this), uint128(-delta.amount1()));
}
}
} else {
Expand All @@ -98,21 +90,14 @@ contract PoolSwapTest is ILockCallback {
manager.settle(data.key.currency1);
}
} else {
// the received hook on this transfer will burn the tokens
manager.safeTransferFrom(
data.sender,
address(manager),
uint256(uint160(Currency.unwrap(data.key.currency1))),
uint128(delta.amount1()),
""
);
manager.burn(data.key.currency1, uint128(delta.amount1()));
}
}
if (delta.amount0() < 0) {
if (data.testSettings.withdrawTokens) {
manager.take(data.key.currency0, data.sender, uint128(-delta.amount0()));
} else {
manager.mint(data.key.currency0, data.sender, uint128(-delta.amount0()));
manager.mint(data.key.currency0, address(this), uint128(-delta.amount0()));
}
}
}
Expand Down
Loading

0 comments on commit 5c2dc73

Please sign in to comment.