Skip to content

Commit

Permalink
Complexity of on-chain ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
pysel committed Dec 5, 2024
1 parent 7aa02a2 commit f21f352
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 50 deletions.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ffi = true
fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}]
solc_version = "0.8.26"
evm_version = "cancun"
viaIr = true

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
remappings = [
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
2 changes: 1 addition & 1 deletion lib/openzeppelin-contracts
2 changes: 1 addition & 1 deletion lib/v4-periphery
Submodule v4-periphery updated 183 files
65 changes: 40 additions & 25 deletions src/ClvrModel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,33 @@ contract ClvrModel {
set_reserve_y(reserve_y);

int128 lnP0 = p0.lnU256().toInt128();
uint256 cachedY = reserveY;
uint256 cachedX = reserveX;

for (uint256 i = 1; i < o.length; ) {
uint256 candidateIndex = i;
int128 unsquaredCandidateValue = lnP0 - P(o, i).lnU256().toInt128();
(uint256 p, uint256 y_cached, uint256 x_cached) = P_cached(o, i, cachedY, cachedX);

int256 unsquaredCandidateValue = lnP0 - p.lnU256().toInt128();
int256 candidateValue = unsquaredCandidateValue ** 2 / 1e18;

for (uint256 j = i + 1; j < o.length; ) {
swap(o, i, j);
swap(o, i, j); // try the next element at position i

(uint256 p_new, uint256 y_cached_new, uint256 x_cached_new) = P_cached(o, i, cachedY, cachedX);

int256 unsquaredValue = lnP0 - P(o, i).lnU256().toInt128();
int256 value = unsquaredValue ** 2 / 1e18;
int256 unsquaredNewValue = lnP0 - p_new.lnU256().toInt128();
int256 newCandidateValue = unsquaredNewValue ** 2 / 1e18;

if (value < candidateValue) {
if (newCandidateValue < candidateValue) {
candidateIndex = j;
candidateValue = value;
candidateValue = newCandidateValue;

y_cached = y_cached_new;
x_cached = x_cached_new;
}

swap(o, j, i);
swap(o, j, i); // swap back

unchecked {
j++;
Expand All @@ -68,6 +78,9 @@ contract ClvrModel {
swap(o, i, candidateIndex);
}

cachedY = y_cached;
cachedX = x_cached;

unchecked {
i++;
}
Expand Down Expand Up @@ -100,12 +113,6 @@ contract ClvrModel {
o[i2] = temp;
}

// CONTRACT: note in TradeMinimal implemented!
function P(ClvrHook.SwapParamsExtended[] memory o, uint256 i) public view returns (uint256) {
uint256 base = 1e18;
return Y(o, i) * base / X(o, i);
}

function set_reserve_x(uint256 reserve_x) internal {
reserveX = reserve_x;
}
Expand All @@ -116,42 +123,50 @@ contract ClvrModel {

// INTERNAL FUNCTIONS

function y_out(ClvrHook.SwapParamsExtended[] memory o, uint256 i) private view returns (uint256) {
function y_out_cached(ClvrHook.SwapParamsExtended[] memory o, uint256 i, uint256 cachedY, uint256 cachedX) private pure returns (uint256) {
if (direction(o[i]) == Direction.Sell) {
uint256 fraction = Y(o, i - 1) / (X(o, i - 1) + amountIn(o[i]));
uint256 fraction = cachedY / (cachedX + amountIn(o[i]));
return fraction * amountIn(o[i]);
}

return 0;
}

function x_out(ClvrHook.SwapParamsExtended[] memory o, uint256 i) private view returns (uint256) {
function x_out_cached(ClvrHook.SwapParamsExtended[] memory o, uint256 i, uint256 cachedY, uint256 cachedX) private pure returns (uint256) {
if (direction(o[i]) == Direction.Buy) {
uint256 fraction = X(o, i - 1) / (Y(o, i - 1) + amountIn(o[i]));
uint256 fraction = cachedX / (cachedY + amountIn(o[i]));
return fraction * amountIn(o[i]);
}
return 0;
}

function Y(ClvrHook.SwapParamsExtended[] memory o, uint256 i) private view returns (uint256) {
function Y_cached(ClvrHook.SwapParamsExtended[] memory o, uint256 i, uint256 cachedY, uint256 cachedX) private view returns (uint256) {
if (i == 0) {
return reserveY;
} else if (i > 0 && direction(o[i]) == Direction.Buy) {
return Y(o, i - 1) + amountIn(o[i]);
return cachedY + amountIn(o[i]);
} else if (i > 0 && direction(o[i]) == Direction.Sell) {
return Y(o, i - 1) - y_out(o, i);
return cachedY - y_out_cached(o, i, cachedY, cachedX);
}
revert("Invalid call to Y");
revert("Invalid call to Y_cached");
}

function X(ClvrHook.SwapParamsExtended[] memory o, uint256 i) private view returns (uint256) {
function X_cached(ClvrHook.SwapParamsExtended[] memory o, uint256 i, uint256 cachedY, uint256 cachedX) private view returns (uint256) {
if (i == 0) {
return reserveX;
} else if (i > 0 && direction(o[i]) == Direction.Sell) {
return X(o, i - 1) + amountIn(o[i]);
return cachedX + amountIn(o[i]);
} else if (i > 0 && direction(o[i]) == Direction.Buy) {
return X(o, i - 1) - x_out(o, i);
return cachedX - x_out_cached(o, i, cachedY, cachedX);
}
revert("Invalid call to X");
revert("Invalid call to X_cached");
}

/// @notice Returns the price, the new y and the new x after the swap
function P_cached(ClvrHook.SwapParamsExtended[] memory o, uint256 i, uint256 cachedY, uint256 cachedX) private view returns (uint256, uint256, uint256) {
uint256 y_cached_new = Y_cached(o, i, cachedY, cachedX);
uint256 x_cached_new = X_cached(o, i, cachedY, cachedX);
return (y_cached_new * 1e18 / x_cached_new, y_cached_new, x_cached_new);
}

function direction(ClvrHook.SwapParamsExtended memory o) private pure returns (Direction) {
Expand Down
14 changes: 6 additions & 8 deletions test/ClvrHook.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ contract ClvrHookTest is Test, Deployers, Fixtures {
PoolId poolId;
ClvrHook hook;

address[10] users;
uint256 constant USERS_COUNT = 10;

address[USERS_COUNT] users;

uint256 tokenId;
int24 tickLower;
Expand Down Expand Up @@ -101,13 +103,9 @@ contract ClvrHookTest is Test, Deployers, Fixtures {
users[0] = makeAddr("Alice");
users[1] = makeAddr("Bob");
users[2] = makeAddr("Carol");
users[3] = makeAddr("3");
users[4] = makeAddr("4");
users[5] = makeAddr("5");
users[6] = makeAddr("6");
users[7] = makeAddr("7");
users[8] = makeAddr("8");
users[9] = makeAddr("9");
for (uint256 i = 3; i < users.length; i++) {
users[i] = makeAddr(string(abi.encodePacked(i)));
}
}

function dealCurrencyToUsers() internal {
Expand Down
44 changes: 33 additions & 11 deletions test/ClvrModel.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@ contract ClvrModelTest is Test {
Box::new(Trade::new(size(2), TradeDirection::Sell)),
*/
function testModel() public {
ClvrHook.SwapParamsExtended[] memory o = new ClvrHook.SwapParamsExtended[](4);
o[0] = ClvrHook.SwapParamsExtended(address(0), address(0), IPoolManager.SwapParams(false, 0, 0)); // first one is mock (address = 0)
o[3] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0));
o[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0));
o[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0));

ClvrHook.SwapParamsExtended[] memory expected = new ClvrHook.SwapParamsExtended[](4);
expected[0] = ClvrHook.SwapParamsExtended(address(0), address(0), IPoolManager.SwapParams(false, 0, 0));
expected[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0));
expected[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0));
expected[3] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0));
ClvrHook.SwapParamsExtended[] memory o = new ClvrHook.SwapParamsExtended[](3);
o[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0));
o[0] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0));
o[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0));

ClvrHook.SwapParamsExtended[] memory expected = new ClvrHook.SwapParamsExtended[](3);
expected[0] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -2e18, 0));
expected[1] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(SELL, -5e18, 0));
expected[2] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(BUY, -10e18, 0));

uint256 gas = gasleft();

Expand All @@ -46,4 +44,28 @@ contract ClvrModelTest is Test {
assertEq(o[i].params.amountSpecified, expected[i].params.amountSpecified);
}
}

function testModelGasConsumption() public {
for (uint256 i = 10; i <= 400; i += 10) {
runSwaps(i);
}
}

function runSwaps(uint256 swaps) internal {
ClvrHook.SwapParamsExtended[] memory o = new ClvrHook.SwapParamsExtended[](swaps);

// create `swaps` alternating buys and sells
for (uint256 i = 0; i < swaps; i++) {
bool direction = i % 2 == 0 ? BUY : SELL;
o[i] = ClvrHook.SwapParamsExtended(address(1), address(1), IPoolManager.SwapParams(direction, -10e18, 0));
}

uint256 gas = gasleft();
o = model.clvrReorder(1e18, o, 100e18, 100e18);
console.log("Gas (in dollars) used for ", swaps, " swaps: ", gasToDollars(gas - gasleft()));
}

function gasToDollars(uint256 gas) internal pure returns (uint256) {
return gas * 1e9 * 30000 / 1e18;
}
}

0 comments on commit f21f352

Please sign in to comment.