Skip to content

Commit

Permalink
Merge pull request #125 from primitivefinance/beta/prep
Browse files Browse the repository at this point in the history
Beta/prep
  • Loading branch information
Alexangelj authored Dec 29, 2022
2 parents 79021d7 + 000d65d commit 2bc2f73
Show file tree
Hide file tree
Showing 39 changed files with 934 additions and 179 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ Changing a position:
- Position Liquidity is touched
- Pool is touched
- Reserves are touched

### Epochs

Every curve parameter set has a "maturity" date. Once this timestamp is eclipsed, the curve becomes unusable in new pools. This creates waste + limits the reusability of curve parameters.

One idea is to introduce an epoch system in the contracts (which we've had at one point in time before, and kept it in this version). With an epoch system, pools can "ride along" with the epoch, instead of rely on it being a parameter in the curve parameter explicitly. It could be a parameter in the curve, but it would be more dynamic, e.g. "10 epoch cycles" instead of "January 21, 2023 at 3pm UTC".

A pool can not have its epoch cycle reset. This is an assumption though, it might be possible, but we don't know that yet.
28 changes: 2 additions & 26 deletions compiler/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,6 @@ export function encodeCreatePool(pairId: number, curveId: number, price: BigNumb
export function encodeUnallocate(
useMax: boolean,
poolId: number,
loTick: number,
hiTick: number,
liquidity: BigNumber
): { bytes: number[]; hex: string } {
const opcode = Instructions.UNALLOCATE
Expand All @@ -204,25 +202,13 @@ export function encodeUnallocate(
const amountBytes = hexToBytes(BigNumber.from(amount)._hex)
const powerByte = encodeSecondByte(0, decimals)

const loTickBytes = hexZeroPad(hexlify(loTick), 3)
const hiTickBytes = hexZeroPad(hexlify(hiTick), 3)

const bytes = [
firstByte,
...hexToBytes(poolIdBytes),
...hexToBytes(loTickBytes),
...hexToBytes(hiTickBytes),
powerByte,
...amountBytes,
]
const bytes = [firstByte, ...hexToBytes(poolIdBytes), powerByte, ...amountBytes]
return { bytes, hex: bytesToHex(bytes) }
}

export function encodeAllocate(
useMax: boolean,
poolId: number,
loTick: number,
hiTick: number,
liquidity: BigNumber
): { bytes: number[]; hex: string } {
const opcode = Instructions.ALLOCATE
Expand All @@ -233,17 +219,8 @@ export function encodeAllocate(
const { amount: encodedAmount, decimals: decimalsBase } = trailingRunLengthEncode(liquidity.toString())
const amountBytes = hexToBytes(BigNumber.from(encodedAmount)._hex)
const powerByte = decimalsBase
const loTickBytes = hexZeroPad(hexlify(loTick), 3)
const hiTickBytes = hexZeroPad(hexlify(hiTick), 3)

const bytes = [
firstByte,
...hexToBytes(poolIdBytes),
...hexToBytes(loTickBytes),
...hexToBytes(hiTickBytes),
powerByte,
...amountBytes,
]
const bytes = [firstByte, ...hexToBytes(poolIdBytes), powerByte, ...amountBytes]

return { bytes, hex: bytesToHex(bytes) }
}
Expand Down Expand Up @@ -310,7 +287,6 @@ export function encodeJumpInstruction(instructions: number[][]): { bytes: number
})

const bytes = [opcode, length, ...instructionBytesPacked]
const actualLength = bytes.length
return { bytes, hex: bytesToHex(bytes) }
}

Expand Down
10 changes: 4 additions & 6 deletions compiler/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ export default class HyperSDK {
const forwarder = await factory.deploy()
this.forwarder = forwarder

console.log(`Deployed Hyper at: ${instance.address}`)
console.log(`Deployed Forwarder at: ${forwarder.address}`)
return this.instance
}

Expand All @@ -60,14 +58,14 @@ export default class HyperSDK {
}

/** Adds liquidity to a range of ranges to a pool. */
async allocate(poolId: number, loTick: number, hiTick: number, amount: BigNumber) {
let { hex: data } = instructions.encodeAllocate(false, poolId, loTick, hiTick, amount)
async allocate(poolId: number, amount: BigNumber) {
let { hex: data } = instructions.encodeAllocate(false, poolId, amount)
return this.forward(data)
}

/** Removes liquidity from a range of prices. */
async unallocate(useMax: boolean, poolId: number, loTick: number, hiTick: number, amount: BigNumber) {
let { hex: data } = instructions.encodeUnallocate(useMax, poolId, loTick, hiTick, amount)
async unallocate(useMax: boolean, poolId: number, amount: BigNumber) {
let { hex: data } = instructions.encodeUnallocate(useMax, poolId, amount)
return this.forward(data)
}

Expand Down
16 changes: 12 additions & 4 deletions contracts/Hyper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract Hyper is IHyper {
AccountSystem public __account__;

// ===== Constants ===== //
string public constant VERSION = "prototype-v0.0.1";
string public constant VERSION = "beta-v0.0.1";
/// @dev Canonical Wrapped Ether contract.
address public immutable WETH;
/// @dev Distance between the location of prices on the price grid, so distance between price.
Expand Down Expand Up @@ -289,6 +289,7 @@ contract Hyper is IHyper {
int128(deltaLiquidity) // TODO: add better type safety for these conversions.
);
_changeLiquidity(args);
emit log(deltaAsset, deltaQuote, "allocate");

emit Allocate(poolId, pair.tokenAsset, pair.tokenQuote, deltaAsset, deltaQuote, deltaLiquidity);
}
Expand Down Expand Up @@ -325,7 +326,7 @@ contract Hyper is IHyper {
if (args.deltaLiquidity < 0) {
(uint256 distance, ) = getSecondsSincePositionUpdate(args.owner, args.poolId);
if (_liquidityPolicy() > distance) revert JitLiquidity(distance);
emit DecreasePosition(args.owner, args.poolId, uint128(args.deltaLiquidity));
emit DecreasePosition(args.owner, args.poolId, uint128(-args.deltaLiquidity)); // TODO: Should be an explict function, unary hard to see...
} else {
emit IncreasePosition(args.owner, args.poolId, uint128(args.deltaLiquidity));
}
Expand Down Expand Up @@ -381,10 +382,14 @@ contract Hyper is IHyper {
-int128(deltaLiquidity)
);

emit log(deltaAsset, deltaQuote, "unallocate");
_changeLiquidity(args);
emit Unallocate(poolId, pair.tokenAsset, pair.tokenQuote, deltaQuote, deltaAsset, deltaLiquidity);
}

event log(int128);
event log(uint, uint, string);

/**
* @dev Adds desired amount of liquidity to pending staked liquidity changes of a pool.
*/
Expand Down Expand Up @@ -872,10 +877,13 @@ contract Hyper is IHyper {

/** @dev Computes each side of a pool's reserves __per one unit of liquidity__. */
function _getAmounts(uint48 poolId) internal view returns (uint256 deltaAsset, uint256 deltaQuote) {
uint256 timestamp = _blockTimestamp();
// TODO: Make a note of the importance of using the pool's _unchanged_ timestamp.
// If the blockTimestamp of a pool changes, it will change the pool's price.
// This blockTimestamp variable should be updated in swaps, not liquidity provision or removing.
HyperPool storage pool = pools[poolId];
uint256 timestamp = pool.blockTimestamp;

Curve memory curve = curves[uint32(poolId)];
HyperPool storage pool = pools[poolId];
Price.Expiring memory info = Price.Expiring({
strike: curve.strike,
sigma: curve.sigma,
Expand Down
2 changes: 1 addition & 1 deletion contracts/OS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ using {
struct AccountSystem {
mapping(address => mapping(address => uint)) balances; // Internal user balances.
mapping(address => uint) reserves; // Global balance of tokens held by a contract.
mapping(address => bool) cached; // Tokens interacted with that must be settled.
mapping(address => bool) cached; // Tokens interacted with that must be settled. TODO: Make it a bitmap.
address[] warm; // Transiently stored, must be length zero outside of execution.
bool prepared; // Must be false outside of execution.
bool settled; // Must be true outside of execution.
Expand Down
33 changes: 33 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ gas_reports = ["*"]
ffi = true
fuzz_runs = 1000


[profile.crytic]
via_ir = false
src = 'test/crytic' # not default
Expand All @@ -38,3 +39,35 @@ primitive = "http://${PRIMITIVE_RPC}:8545"


# See more config options https://github.com/gakonst/foundry/tree/master/config

[fuzz]
# The number of fuzz runs for fuzz tests
runs = 1000
# The maximum number of test case rejections allowed by proptest, to be
# encountered during usage of `vm.assume` cheatcode. This will be used
# to set the `max_global_rejects` value in proptest test runner config.
# `max_local_rejects` option isn't exposed here since we're not using
# `prop_filter`.
max_test_rejects = 65536
# The weight of the dictionary
dictionary_weight = 40
# The flag indicating whether to include values from storage
include_storage = true
# The flag indicating whether to include push bytes values
include_push_bytes = true

[invariant]
# The number of runs that must execute for each invariant test group
runs = 1000
# The number of calls executed to attempt to break invariants in one run
depth = 15
# Fails the invariant fuzzing if a revert occurs
fail_on_revert = false
# Allows overriding an unsafe external call when running invariant tests. eg. reentrancy checks
call_override = false
# The weight of the dictionary
dictionary_weight = 80
# The flag indicating whether to include values from storage
include_storage = true
# The flag indicating whether to include push bytes values
include_push_bytes = true
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"prepare:echidna:corpus": "yarn test:hardhat --network ganache",
"prepare:differential": "cd ./test/differential/scripts/ && npm run compile && npm run generate",
"deploy": "forge script scripts/Deploy.s.sol:Deploy --rpc-url $PRIMITIVE_RPC --fork-url $FORK_URL --broadcast",
"deploy:ganache": "forge script scripts/Deploy.s.sol:Deploy --fork-url http://localhost:8545 --broadcast"
"deploy:ganache": "forge script scripts/Deploy.s.sol:Deploy --fork-url http://localhost:8545 --broadcast",
"print:storage": "forge inspect contracts/Hyper.sol:Hyper storage-layout"
},
"repository": {
"type": "git",
Expand Down
Empty file removed test/E2E/E2E.t.sol
Empty file.
Empty file removed test/E2E/E2E.test.ts
Empty file.
78 changes: 78 additions & 0 deletions test/E2E/InvariantAllocate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./setup/InvariantTargetContract.sol";

contract InvariantAllocate is InvariantTargetContract {
constructor(address hyper_, address asset_, address quote_) InvariantTargetContract(hyper_, asset_, quote_) {}

event SentTokens(uint amount);

function send_erc20(uint amount) external {
__asset__.mint(address(__hyper__), amount);
emit SentTokens(amount);
}

function allocate_unallocate(uint deltaLiquidity) external {
vm.assume(deltaLiquidity > 0);
vm.assume(deltaLiquidity < 2 ** 127);
// TODO: Add use max flag support.

// Preconditions
HyperPool memory pool = getPool(address(__hyper__), __poolId__);
assertTrue(pool.blockTimestamp != 0, "Pool not initialized");
assertTrue(pool.lastPrice != 0, "Pool not created with a price");

(uint expectedDeltaAsset, uint expectedDeltaQuote) = __hyper__.getReserveDelta(__poolId__, deltaLiquidity);
__asset__.mint(address(this), expectedDeltaAsset);
__quote__.mint(address(this), expectedDeltaQuote);

// Execution
HyperState memory prev = getState();
(uint deltaAsset, uint deltaQuote) = __hyper__.allocate(__poolId__, deltaLiquidity);
HyperState memory post = getState();

// Postconditions
{
assertEq(deltaAsset, expectedDeltaAsset, "pool-delta-asset");
assertEq(deltaQuote, expectedDeltaQuote, "pool-delta-quote");
assertEq(post.totalPoolLiquidity, prev.totalPoolLiquidity + deltaLiquidity, "pool-total-liquidity");
assertTrue(post.totalPoolLiquidity > prev.totalPoolLiquidity, "pool-liquidity-increases");
assertEq(
post.callerPositionLiquidity,
prev.callerPositionLiquidity + deltaLiquidity,
"position-liquidity-increases"
);

assertEq(post.reserveAsset, prev.reserveAsset + expectedDeltaAsset, "reserve-asset");
assertEq(post.reserveQuote, prev.reserveQuote + expectedDeltaQuote, "reserve-quote");
assertEq(post.physicalBalanceAsset, prev.physicalBalanceAsset + expectedDeltaAsset, "physical-asset");
assertEq(post.physicalBalanceQuote, prev.physicalBalanceQuote + expectedDeltaQuote, "physical-quote");

uint feeDelta0 = post.feeGrowthAssetPosition - prev.feeGrowthAssetPosition;
uint feeDelta1 = post.feeGrowthAssetPool - prev.feeGrowthAssetPool;
assertTrue(feeDelta0 == feeDelta1, "asset-growth");

uint feeDelta2 = post.feeGrowthQuotePosition - prev.feeGrowthQuotePosition;
uint feeDelta3 = post.feeGrowthQuotePool - prev.feeGrowthQuotePool;
assertTrue(feeDelta2 == feeDelta3, "quote-growth");
}

// Unallocate
uint timestamp = block.timestamp + __hyper__.JUST_IN_TIME_LIQUIDITY_POLICY();
vm.warp(timestamp);
__hyper__.setTimestamp(uint128(timestamp));
(uint unallocatedAsset, uint unallocatedQuote) = __hyper__.unallocate(__poolId__, deltaLiquidity);

{
HyperState memory end = getState();
assertEq(unallocatedAsset, deltaAsset);
assertEq(unallocatedQuote, deltaQuote);
assertEq(end.reserveAsset, prev.reserveAsset);
assertEq(end.reserveQuote, prev.reserveQuote);
assertEq(end.totalPoolLiquidity, prev.totalPoolLiquidity);
assertEq(end.totalPositionLiquidity, prev.totalPositionLiquidity);
assertEq(end.callerPositionLiquidity, prev.callerPositionLiquidity);
}
}
}
34 changes: 34 additions & 0 deletions test/E2E/InvariantGlobal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./setup/InvariantTargetContract.sol";

bytes32 constant SLOT_LOCKED = bytes32(uint(5));

contract InvariantGlobal is InvariantTargetContract {
constructor(address hyper_, address asset_, address quote_) InvariantTargetContract(hyper_, asset_, quote_) {}

function invariant_global() public {
bytes32 locked = vm.load(address(__hyper__), SLOT_LOCKED);
assertEq(uint(locked), 1, "invariant-locked");

(bool prepared, bool settled) = __hyper__.__account__();
assertTrue(!prepared, "invariant-prepared");
assertTrue(settled, "invariant-settled");

uint balance = address(__hyper__).balance;
assertEq(balance, 0, "invariant-ether");

(uint reserve, uint physical, uint balances) = getBalances(address(__asset__));
assertTrue(physical >= reserve + balances, "invariant-asset-physical-balance");

(reserve, physical, balances) = getBalances(address(__quote__));
assertTrue(physical >= reserve + balances, "invariant-quite-physical-balance");
}

function getBalances(address token) internal view returns (uint reserve, uint physical, uint balances) {
reserve = getReserve(address(__hyper__), token);
physical = getPhysicalBalance(address(__hyper__), token);
balances = getBalanceSum(address(__hyper__), token, ctx.users());
}
}
25 changes: 25 additions & 0 deletions test/E2E/TestE2E.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "./setup/TestE2ESetup.sol";
import "./setup/TestInvariantSetup.sol";

import {InvariantAllocate} from "./InvariantAllocate.sol";

contract TestE2EInvariant is TestInvariantSetup, TestE2ESetup {
InvariantAllocate internal _allocate;

function setUp() public override {
super.setUp();

_allocate = new InvariantAllocate(address(__hyper__), address(__asset__), address(__quote__));

addTargetContract(address(_allocate));
}

function invariant_global_account() public {
(bool prepared, bool settled) = __hyper__.__account__();
assertTrue(!prepared, "invariant-prepared");
assertTrue(settled, "invariant-settled");
}
}
Loading

0 comments on commit 2bc2f73

Please sign in to comment.