-
Notifications
You must be signed in to change notification settings - Fork 507
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* i might just be insane * additional assertion * fix pseudorandom seeding * do not bound seed * pr feedback
- Loading branch information
1 parent
598b02e
commit fff8413
Showing
3 changed files
with
195 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.21; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
import {BalanceDelta, toBalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; | ||
|
||
/// @title Currency Deltas | ||
/// @notice Fetch two currency deltas in a single call | ||
library CurrencyDeltas { | ||
using SafeCast for int256; | ||
|
||
/// @notice Get the current delta for a caller in the two given currencies | ||
/// @param _caller The address of the caller | ||
/// @param currency0 The currency to lookup the delta | ||
/// @param currency1 The other currency to lookup the delta | ||
/// @return BalanceDelta The delta of the two currencies packed | ||
/// amount0 corresponding to currency0 and amount1 corresponding to currency1 | ||
function currencyDeltas(IPoolManager manager, address _caller, Currency currency0, Currency currency1) | ||
internal | ||
view | ||
returns (BalanceDelta) | ||
{ | ||
bytes32 tloadSlot0; | ||
bytes32 tloadSlot1; | ||
assembly { | ||
mstore(0, _caller) | ||
mstore(32, currency0) | ||
tloadSlot0 := keccak256(0, 64) | ||
|
||
mstore(0, _caller) | ||
mstore(32, currency1) | ||
tloadSlot1 := keccak256(0, 64) | ||
} | ||
bytes32[] memory slots = new bytes32[](2); | ||
slots[0] = tloadSlot0; | ||
slots[1] = tloadSlot1; | ||
bytes32[] memory result = manager.exttload(slots); | ||
return toBalanceDelta(int256(uint256(result[0])).toInt128(), int256(uint256(result[1])).toInt128()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "forge-std/Test.sol"; | ||
import {IERC20} from "forge-std/interfaces/IERC20.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; | ||
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; | ||
|
||
import {MockCurrencyDeltaReader} from "../mocks/MockCurrencyDeltaReader.sol"; | ||
|
||
contract CurrencyDeltasTest is Test, Deployers { | ||
using CurrencyLibrary for Currency; | ||
|
||
MockCurrencyDeltaReader reader; | ||
|
||
function setUp() public { | ||
deployFreshManagerAndRouters(); | ||
deployMintAndApprove2Currencies(); | ||
|
||
reader = new MockCurrencyDeltaReader(manager); | ||
|
||
IERC20 token0 = IERC20(Currency.unwrap(currency0)); | ||
IERC20 token1 = IERC20(Currency.unwrap(currency1)); | ||
|
||
token0.approve(address(reader), type(uint256).max); | ||
token1.approve(address(reader), type(uint256).max); | ||
|
||
// send tokens to PoolManager so tests can .take() | ||
token0.transfer(address(manager), 1_000_000e18); | ||
token1.transfer(address(manager), 1_000_000e18); | ||
|
||
// convert some ERC20s into ERC6909 | ||
claimsRouter.deposit(currency0, address(this), 1_000_000e18); | ||
claimsRouter.deposit(currency1, address(this), 1_000_000e18); | ||
manager.approve(address(reader), currency0.toId(), type(uint256).max); | ||
manager.approve(address(reader), currency1.toId(), type(uint256).max); | ||
} | ||
|
||
function test_fuzz_currencyDeltas(uint8 depth, uint256 seed, uint128 amount0, uint128 amount1) public { | ||
int128 delta0Expected = 0; | ||
int128 delta1Expected = 0; | ||
|
||
bytes[] memory calls = new bytes[](depth); | ||
for (uint256 i = 0; i < depth; i++) { | ||
amount0 = uint128(bound(amount0, 1, 100e18)); | ||
amount1 = uint128(bound(amount1, 1, 100e18)); | ||
uint256 _seed = seed % (i + 1); | ||
if (_seed % 8 == 0) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.settle.selector, currency0, amount0); | ||
delta0Expected += int128(amount0); | ||
} else if (_seed % 8 == 1) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.settle.selector, currency1, amount1); | ||
delta1Expected += int128(amount1); | ||
} else if (_seed % 8 == 2) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.burn.selector, currency0, amount0); | ||
delta0Expected += int128(amount0); | ||
} else if (_seed % 8 == 3) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.burn.selector, currency1, amount1); | ||
delta1Expected += int128(amount1); | ||
} else if (_seed % 8 == 4) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.take.selector, currency0, amount0); | ||
delta0Expected -= int128(amount0); | ||
} else if (_seed % 8 == 5) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.take.selector, currency1, amount1); | ||
delta1Expected -= int128(amount1); | ||
} else if (_seed % 8 == 6) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.mint.selector, currency0, amount0); | ||
delta0Expected -= int128(amount0); | ||
} else if (_seed % 8 == 7) { | ||
calls[i] = abi.encodeWithSelector(MockCurrencyDeltaReader.mint.selector, currency1, amount1); | ||
delta1Expected -= int128(amount1); | ||
} | ||
} | ||
|
||
BalanceDelta delta = reader.execute(calls, currency0, currency1); | ||
assertEq(delta.amount0(), delta0Expected); | ||
assertEq(delta.amount1(), delta1Expected); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; | ||
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; | ||
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; | ||
import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol"; | ||
|
||
import {CurrencyDeltas} from "../../src/libraries/CurrencyDeltas.sol"; | ||
|
||
/// @dev A minimal helper strictly for testing | ||
contract MockCurrencyDeltaReader { | ||
using TransientStateLibrary for IPoolManager; | ||
using CurrencyDeltas for IPoolManager; | ||
using CurrencySettler for Currency; | ||
|
||
IPoolManager public poolManager; | ||
|
||
address sender; | ||
|
||
constructor(IPoolManager _poolManager) { | ||
poolManager = _poolManager; | ||
} | ||
|
||
/// @param calls an array of abi.encodeWithSelector | ||
function execute(bytes[] calldata calls, Currency currency0, Currency currency1) external returns (BalanceDelta) { | ||
sender = msg.sender; | ||
return abi.decode(poolManager.unlock(abi.encode(calls, currency0, currency1)), (BalanceDelta)); | ||
} | ||
|
||
function unlockCallback(bytes calldata data) external returns (bytes memory) { | ||
(bytes[] memory calls, Currency currency0, Currency currency1) = abi.decode(data, (bytes[], Currency, Currency)); | ||
for (uint256 i; i < calls.length; i++) { | ||
(bool success,) = address(this).call(calls[i]); | ||
if (!success) revert("CurrencyDeltaReader"); | ||
} | ||
|
||
BalanceDelta delta = poolManager.currencyDeltas(address(this), currency0, currency1); | ||
int256 delta0 = poolManager.currencyDelta(address(this), currency0); | ||
int256 delta1 = poolManager.currencyDelta(address(this), currency1); | ||
|
||
// confirm agreement between currencyDeltas and single-read currencyDelta | ||
require(delta.amount0() == int128(delta0), "CurrencyDeltaReader: delta0"); | ||
require(delta.amount1() == int128(delta1), "CurrencyDeltaReader: delta1"); | ||
|
||
// close deltas | ||
if (delta.amount0() < 0) currency0.settle(poolManager, sender, uint256(-int256(delta.amount0())), false); | ||
if (delta.amount1() < 0) currency1.settle(poolManager, sender, uint256(-int256(delta.amount1())), false); | ||
if (delta.amount0() > 0) currency0.take(poolManager, sender, uint256(int256(delta.amount0())), false); | ||
if (delta.amount1() > 0) currency1.take(poolManager, sender, uint256(int256(delta.amount1())), false); | ||
return abi.encode(delta); | ||
} | ||
|
||
function settle(Currency currency, uint256 amount) external { | ||
currency.settle(poolManager, sender, amount, false); | ||
} | ||
|
||
function burn(Currency currency, uint256 amount) external { | ||
currency.settle(poolManager, sender, amount, true); | ||
} | ||
|
||
function take(Currency currency, uint256 amount) external { | ||
currency.take(poolManager, sender, amount, false); | ||
} | ||
|
||
function mint(Currency currency, uint256 amount) external { | ||
currency.take(poolManager, sender, amount, true); | ||
} | ||
} |