Skip to content

Commit

Permalink
fix(#72): Adds timestamp math. Refactors Create function.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexangelj committed Jun 20, 2021
1 parent 4839718 commit b4f3deb
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 86 deletions.
115 changes: 65 additions & 50 deletions contracts/PrimitiveEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import "./libraries/Margin.sol";
import "./libraries/Position.sol";
import "./libraries/ReplicationMath.sol";
import "./libraries/Reserve.sol";
import "./libraries/Units.sol";
import "./libraries/SafeCast.sol";
import "./libraries/Transfers.sol";
import "./libraries/Units.sol";

import "./interfaces/callback/IPrimitiveLendingCallback.sol";
import "./interfaces/callback/IPrimitiveLiquidityCallback.sol";
Expand All @@ -31,6 +32,7 @@ contract PrimitiveEngine is IPrimitiveEngine {
using BlackScholes for int128;
using ReplicationMath for int128;
using Units for *;
using SafeCast for *;
using Reserve for mapping(bytes32 => Reserve.Data);
using Reserve for Reserve.Data;
using Margin for mapping(address => Margin.Data);
Expand All @@ -42,8 +44,9 @@ contract PrimitiveEngine is IPrimitiveEngine {
/// @dev Parameters of each pool
struct Calibration {
uint128 strike; // strike price of the option
uint64 sigma; // implied volatility of the option
uint64 time; // the time in seconds until the option expires
uint64 sigma; // volatility of the option
uint32 time; // time in seconds until the option expires
uint32 blockTimestamp; // time stamp of initialization
}

/// @inheritdoc IPrimitiveEngineView
Expand Down Expand Up @@ -87,49 +90,60 @@ contract PrimitiveEngine is IPrimitiveEngine {

/// @notice Block timestamp... but casted as a uint32
function _blockTimestamp() internal view returns (uint32 blockTimestamp) {
// solhint-disable-next-line
// solhint-disable-next-line
blockTimestamp = uint32(block.timestamp);
}

/// @inheritdoc IPrimitiveEngineActions
function create(
uint256 strike,
uint64 sigma,
uint64 time,
uint32 time,
uint256 riskyPrice,
uint256 delLiquidity,
bytes calldata data
) external override lock returns (bytes32 pid) {
require(time > 0 && sigma > 0 && strike > 0, "Zero");
require(delLiquidity > 0, "Zero liquidity");

)
external
override
lock
returns (
bytes32 pid,
uint256 delRisky,
uint256 delStable
)
{
require((time * sigma * strike * delLiquidity) > 0, "Zero");
pid = getPoolId(strike, sigma, time);
require(settings[pid].time == 0, "Initialized");
settings[pid] = Calibration({strike: uint128(strike), sigma: uint64(sigma), time: uint64(time)});

int128 optionDelta = BlackScholes.deltaCall(riskyPrice, strike, sigma, time);
uint256 reserveRisky = uint256(1).fromUInt().sub(optionDelta).parseUnits();
uint256 reserveStable = ReplicationMath.getTradingFunction(reserveRisky, 1e18, strike, sigma, time).parseUnits();
reserves[pid] = Reserve.Data({
reserveRisky: uint128((reserveRisky * delLiquidity) / 1e18), // risky token balance
reserveStable: uint128((reserveStable * delLiquidity) / 1e18), // stable token balance
liquidity: uint128(delLiquidity), // 1 unit
float: 0, // the LP shares available to be borrowed on a given pid
debt: 0, // the LP shares borrowed from the float
blockTimestamp: _blockTimestamp(),
cumulativeRisky: 0,
cumulativeStable: 0,
cumulativeLiquidity: 0
});

{
// avoids stack too deep errors
(uint256 strikePrice, uint256 vol, uint32 maturity) = (strike, sigma, time);
uint32 timeDelta = (maturity - _blockTimestamp()) / 1000;
int128 callDelta = BlackScholes.deltaCall(riskyPrice, strikePrice, vol, timeDelta);
uint256 resRisky = uint256(1).fromUInt().sub(callDelta).parseUnits(); // risky = 1 - delta
uint256 resStable =
ReplicationMath.getTradingFunction(resRisky, 1e18, strikePrice, vol, timeDelta).parseUnits();
delRisky = (resRisky * delLiquidity) / 1e18;
delStable = (resStable * delLiquidity) / 1e18;
}

uint256 balRisky = balanceRisky();
uint256 balStable = balanceStable();
IPrimitiveCreateCallback(msg.sender).createCallback(reserveRisky, reserveStable, data);
require(balanceRisky() >= reserveRisky + balRisky, "Not enough risky tokens");
require(balanceStable() >= reserveStable + balStable, "Not enough stable tokens");

IPrimitiveCreateCallback(msg.sender).createCallback(delRisky, delStable, data);
require(balanceRisky() >= delRisky + balRisky, "Risky");
require(balanceStable() >= delStable + balStable, "Stable");

Reserve.Data storage reserve = reserves[pid];
reserve.allocate(delRisky, delStable, delLiquidity, _blockTimestamp());
positions.fetch(msg.sender, pid).allocate(delLiquidity - 1000); // give liquidity to `msg.sender`, burn 1000 wei
emit Create(msg.sender, pid, strike, sigma, time);
settings[pid] = Calibration({
strike: strike.toUint128(),
sigma: sigma,
time: time,
blockTimestamp: _blockTimestamp()
});
emit Create(msg.sender, strike, sigma, time);
}

// ===== Margin =====
Expand Down Expand Up @@ -170,12 +184,13 @@ contract PrimitiveEngine is IPrimitiveEngine {
bool fromMargin,
bytes calldata data
) external override lock returns (uint256 delRisky, uint256 delStable) {
Reserve.Data storage res = reserves[pid];
(uint256 liquidity, uint256 reserveRisky, uint256 reserveStable) = (res.liquidity, res.reserveRisky, res.reserveStable);
require(liquidity > 0, "Not initialized");
Reserve.Data storage reserve = reserves[pid];
(uint256 resLiquidity, uint256 resRisky, uint256 resStable) =
(reserve.liquidity, reserve.reserveRisky, reserve.reserveStable);

delRisky = (delLiquidity * reserveRisky) / liquidity;
delStable = (delLiquidity * reserveStable) / liquidity;
require(resLiquidity > 0, "Not initialized");
delRisky = (resRisky * delLiquidity) / resLiquidity;
delStable = (resStable * delLiquidity) / resLiquidity;
require(delRisky * delStable > 0, "Deltas are 0");

if (fromMargin) {
Expand All @@ -189,9 +204,9 @@ contract PrimitiveEngine is IPrimitiveEngine {
}

bytes32 pid_ = pid;
Position.Data storage pos = positions.fetch(owner, pid_);
pos.allocate(delLiquidity);
res.allocate(delRisky, delStable, delLiquidity, _blockTimestamp());
Position.Data storage position = positions.fetch(owner, pid_);
position.allocate(delLiquidity); // increase position liquidity
reserve.allocate(delRisky, delStable, delLiquidity, _blockTimestamp()); // increase reserves and liquidity
emit Allocated(msg.sender, delRisky, delStable);
}

Expand All @@ -203,20 +218,20 @@ contract PrimitiveEngine is IPrimitiveEngine {
bytes calldata data
) external override lock returns (uint256 delRisky, uint256 delStable) {
require(delLiquidity > 0, "Cannot be 0");
Reserve.Data storage res = reserves[pid];
Reserve.Data storage reserve = reserves[pid];

uint256 nextRisky;
uint256 nextStable;

{
// scope for calculting invariants
(uint256 reserveRisky, uint256 reserveStable, uint256 liquidity) = (res.reserveRisky, res.reserveStable, res.liquidity);
require(liquidity >= delLiquidity, "Above max burn");
delRisky = (delLiquidity * reserveRisky) / liquidity;
delStable = (delLiquidity * reserveStable) / liquidity;
(uint256 resRisky, uint256 resStable, uint256 resLiquidity) =
(reserve.reserveRisky, reserve.reserveStable, reserve.liquidity);
require(resLiquidity >= delLiquidity, "Above max burn");
delRisky = (resRisky * delLiquidity) / resLiquidity;
delStable = (resStable * delLiquidity) / resLiquidity;
require(delRisky * delStable > 0, "Deltas are 0");
nextRisky = reserveRisky - delRisky;
nextStable = reserveStable - delStable;
nextRisky = resRisky - delRisky;
nextStable = resStable - delStable;
}

// Updated state
Expand All @@ -233,8 +248,8 @@ contract PrimitiveEngine is IPrimitiveEngine {
require(balanceStable() >= balStable - delStable, "Not enough stable");
}

positions.remove(pid, delLiquidity); // Updated position liqudiity
res.remove(delRisky, delStable, delLiquidity, _blockTimestamp());
positions.remove(pid, delLiquidity); // Update position liquidity
reserve.remove(delRisky, delStable, delLiquidity, _blockTimestamp());
emit Removed(msg.sender, delRisky, delStable);
}

Expand Down Expand Up @@ -488,8 +503,8 @@ contract PrimitiveEngine is IPrimitiveEngine {
/// @inheritdoc IPrimitiveEngineView
function getPoolId(
uint256 strike,
uint256 sigma,
uint256 time
uint64 sigma,
uint32 time
) public view override returns (bytes32 pid) {
pid = keccak256(abi.encodePacked(factory, time, sigma, strike));
}
Expand Down
26 changes: 17 additions & 9 deletions contracts/interfaces/engine/IPrimitiveEngineActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@ pragma solidity 0.8.0;
interface IPrimitiveEngineActions {
// Curve

/// @notice Initializes a curve with parameters in the `settings` storage mapping in the Engine
/// @param strike The strike price of the option to calibrate to
/// @param sigma The volatility of the option to calibrate to
/// @param time The time until expiry of the option to calibrate to
/// @param riskyPrice The amount of stable tokens required to purchase 1 unit of the risky token, spot price
/// @notice Initializes a curve with parameters in the `settings` storage mapping in the Engine
/// @param strike Strike price of the option to calibrate to
/// @param sigma Volatility of the option to calibrate to
/// @param time Maturity timestamp of the option
/// @param riskyPrice Amount of stable tokens required to purchase 1 unit of the risky token, spot price
/// @param delLiquidity Amount of liquidity to initialize the pool with
/// @param data Arbitrary data that is passed to the createCallback function
/// @return pid The keccak256 hash of the parameters strike, sigma, and time, use to identify this option
/// @return pid Keccak256 hash of the parameters strike, sigma, and time, use to identify this option
/// delRisky Amount of risky tokens provided to reserves
/// delStable Amount of stable tokens provided to reserves
function create(
uint256 strike,
uint256 sigma,
uint256 time,
uint64 sigma,
uint32 time,
uint256 riskyPrice,
uint256 delLiquidity,
bytes calldata data
) external returns (bytes32 pid);
)
external
returns (
bytes32 pid,
uint256 delRisky,
uint256 delStable
);

// Margin

Expand Down
3 changes: 1 addition & 2 deletions contracts/interfaces/engine/IPrimitiveEngineEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ pragma solidity 0.8.0;
interface IPrimitiveEngineEvents {
/// @notice Creates a new calibrated curve and initialized its liquidity
/// @param from The calling `msg.sender` of the create function
/// @param pid The keccak hash of the option parameters of a curve to interact with
/// @param strike The strike price of the option of the curve to calibrate to
/// @param sigma The volatility of the option of the curve to calibrate to
/// @param time The time until expiry of the option of the curve to calibrate to
event Create(address indexed from, bytes32 indexed pid, uint256 strike, uint256 sigma, uint256 time);
event Create(address indexed from, uint256 indexed strike, uint256 sigma, uint256 indexed time);

// ===== Margin ====
/// @notice Added stable and/or risky tokens to a margin accouynt
Expand Down
32 changes: 17 additions & 15 deletions contracts/interfaces/engine/IPrimitiveEngineView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,29 @@ interface IPrimitiveEngineView {
uint256 cumulativeLiquidity
);

/// @notice Fetches The calibrated and initialized pool's parameters
/// @param pid The pool id to fetch the parameters of
/// @return strike The strike price of the pool
/// sigma The volatility of the pool
/// time The time until expiry of the pool
/// @notice Fetches Calibrated and initialized pool's parameters
/// @param pid Pool id to fetch the parameters of
/// @return strike Strike price of the pool
/// sigma Volatility of the pool
/// time Time until expiry of the pool
/// blockTimestamp Timestamp on pool creation
function settings(bytes32 pid)
external
view
returns (
uint128 strike,
uint64 sigma,
uint64 time
uint32 time,
uint32 blockTimestamp
);

/// @notice Fetches The position data struct using a position id
/// @param posId The position id
/// @return balanceRisky The risky balance of the position debt
/// balanceStable The stable balance of the position debt
/// float The liquidity shares that are marked for loans
/// liquidity The liquidity shares in the position
/// debt The liquidity shares in debt, must be repaid
/// @notice Fetches Position data struct using a position id
/// @param posId Position id
/// @return balanceRisky Risky balance of the position debt
/// balanceStable Stable balance of the position debt
/// float Liquidity shares that are marked for loans
/// liquidity Liquidity shares in the position
/// debt Liquidity shares in debt, must be repaid
function positions(bytes32 posId)
external
view
Expand All @@ -117,7 +119,7 @@ interface IPrimitiveEngineView {
/// @return The keccak256 hash of the `calibration` parameters
function getPoolId(
uint256 strike,
uint256 sigma,
uint256 time
uint64 sigma,
uint32 time
) external view returns (bytes32);
}
4 changes: 4 additions & 0 deletions contracts/libraries/SafeCast.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ library SafeCast {
function toUint128(uint256 x) internal pure returns (uint128 z) {
require((z = uint128(x)) == x);
}

function toUint64(uint256 x) internal pure returns (uint128 z) {
require((z = uint64(x)) == x);
}
}
2 changes: 1 addition & 1 deletion contracts/test/engine/EngineCreate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ contract EngineCreate {
bytes calldata data
) public {
CALLER = msg.sender;
IPrimitiveEngine(engine).create(strike, sigma, time, riskyPrice, delLiquidity, data);
IPrimitiveEngine(engine).create(strike, uint64(sigma), uint32(time), riskyPrice, delLiquidity, data);
}

function createCallback(
Expand Down
17 changes: 8 additions & 9 deletions test/unit/primitiveEngine/effect/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import loadContext from '../../context'

import { createFragment } from '../fragments'

const [strike, sigma, time, spot] = [parseWei('1000').raw, 0.85 * PERCENTAGE, 31449600, parseWei('1100').raw]
const [strike, sigma, time, spot] = [
parseWei('1000').raw,
0.85 * PERCENTAGE,
Math.floor(Date.now() / 1000) + 31449600,
parseWei('1100').raw,
]
const empty: BytesLike = constants.HashZero

describe('create', function () {
Expand All @@ -22,20 +27,14 @@ describe('create', function () {
it('emits the Create event', async function () {
await expect(this.contracts.engineCreate.create(strike, sigma, time, spot, parseWei('1').raw, empty))
.to.emit(this.contracts.engine, 'Create')
.withArgs(
this.contracts.engineCreate.address,
'0xbd2b5718c3094a357b195e108feebdacded45272d1086596e5c59b43d017083b',
strike,
sigma,
time
)
.withArgs(this.contracts.engineCreate.address, strike, sigma, time)
})

it('reverts when the pool already exists', async function () {
await this.contracts.engineCreate.create(strike, sigma, time, spot, parseWei('1').raw, empty)
await expect(
this.contracts.engineCreate.create(strike, sigma, time, spot, parseWei('1').raw, empty)
).to.be.revertedWith('Already created')
).to.be.revertedWith('Initialized')
})
})
})

0 comments on commit b4f3deb

Please sign in to comment.