Skip to content

Commit

Permalink
Merge pull request #10 from primitivefinance/upgrade/enigma
Browse files Browse the repository at this point in the history
Upgrade/enigma
  • Loading branch information
Alexangelj authored Jun 4, 2022
2 parents d46a1a3 + 6eb8265 commit c6f692f
Show file tree
Hide file tree
Showing 15 changed files with 195 additions and 152 deletions.
63 changes: 21 additions & 42 deletions contracts/Compiler.sol → contracts/Decompiler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import "@rari-capital/solmate/src/tokens/ERC20.sol";
import "./HyperLiquidity.sol";
import "./HyperSwap.sol";

/// @title Enigma Compiler
/// @title Enigma Decompiler
/// @notice Main contract of the Enigma that implements instruction processing.
/// @dev Eliminates the use of function signatures. Expects encoded bytes as msg.data in the fallback.
contract Compiler is HyperLiquidity, HyperSwap {
contract Decompiler is HyperLiquidity, HyperSwap {
// --- Fallback --- //

/// @notice Main touchpoint for receiving calls.
/// @dev Critical: data must be encoded properly to be processed.
/// @custom:security Critical. Guarded against re-entrancy. This is like the bank vault door.
/// @custom:mev Higher level security checks must be implemented by calling contract.
fallback() external payable lock {
if (msg.data[0] != INSTRUCTION_JUMP) _process(msg.data);
if (msg.data[0] != Instructions.INSTRUCTION_JUMP) _process(msg.data);
else _jumpProcess(msg.data);
_settleBalances();
}
Expand All @@ -30,10 +30,13 @@ contract Compiler is HyperLiquidity, HyperSwap {
/// @custom:security High. Without pairIds to loop through, no token amounts are settled.
uint16[] internal _tempPairIds;

/// @dev Token -> Touched Flag. Stored temporary to signal which token reserves were tapped.
mapping(address => bool) internal _addressCache;

/// @dev Flag set to `true` during `_process`. Set to `false` during `_settleToken`.
/// @custom:security High. Referenced in settlement to pay for tokens due.
function _cacheAddress(address token, bool flag) internal {
addressCache[token] = flag;
_addressCache[token] = flag;
}

// --- Internal --- //
Expand Down Expand Up @@ -61,66 +64,40 @@ contract Compiler is HyperLiquidity, HyperSwap {
emit Debit(token, amount);
}

/// @notice First byte should always be the INSTRUCTION_JUMP Enigma code.
/// @dev Expects a special encoding method for multiple instructions.
/// @param data Includes opcode as byte at index 0. First byte should point to next instruction.
/// @custom:security Critical. Processes multiple instructions. Data must be encoded perfectly.
function _jumpProcess(bytes calldata data) internal {
uint8 length = uint8(data[1]);
uint8 pointer = JUMP_PROCESS_START_POINTER; // note: [opcode, length, pointer, ...instruction, pointer, ...etc]
uint256 start;

// For each instruction set...
for (uint256 i; i != length; ++i) {
// Start at the index of the first byte of the next instruction.
start = pointer;

// Set the new pointer to the next instruction, located at the pointer.
pointer = uint8(data[pointer]);

// The `start:` includes the pointer byte, while the `:end` `pointer` is excluded.
if (pointer > data.length) revert JumpError(pointer);
bytes calldata instruction = data[start:pointer];

// Process the instruction.
_process(instruction[1:]); // note: Removes the pointer to the next instruction.
}
}

/// @notice Single instruction processor that will forward instruction to appropriate function.
/// @dev Critical: Every token of every pair interacted with is cached to be settled later.
/// @param data Encoded Enigma data. First byte must be an Enigma instruction.
/// @custom:security Critical. Directly sends instructions to be executed.
function _process(bytes calldata data) internal returns (uint16 pairId) {
function _process(bytes calldata data) internal override {
uint48 poolId;
bytes1 instruction = bytes1(data[0] & 0x0f);
if (instruction == UNKNOWN) revert UnknownInstruction();
if (instruction == Instructions.UNKNOWN) revert UnknownInstruction();

if (instruction == ADD_LIQUIDITY) {
if (instruction == Instructions.ADD_LIQUIDITY) {
(poolId, ) = _addLiquidity(data);
} else if (instruction == REMOVE_LIQUIDITY) {
} else if (instruction == Instructions.REMOVE_LIQUIDITY) {
(poolId, , ) = _removeLiquidity(data);
} else if (instruction == SWAP) {
} else if (instruction == Instructions.SWAP) {
(poolId, ) = _swapExactForExact(data);
} else if (instruction == CREATE_POOL) {
} else if (instruction == Instructions.CREATE_POOL) {
(poolId, , ) = _createPool(data);
} else if (instruction == CREATE_CURVE) {
} else if (instruction == Instructions.CREATE_CURVE) {
_createCurve(data);
} else if (instruction == CREATE_PAIR) {
} else if (instruction == Instructions.CREATE_PAIR) {
_createPair(data);
} else {
revert UnknownInstruction();
}

// note: Only pool interactions have a non-zero poolId.
if (poolId != 0) {
pairId = uint16(poolId >> 32);
uint16 pairId = uint16(poolId >> 32);
// Add the pair to the array to track all the pairs that have been interacted with.
_tempPairIds.push(pairId); // note: critical to push the tokens interacted with.
// Caching the addresses to settle the pools interacted with in the fallback function.
Pair memory pair = pairs[pairId]; // note: pairIds start at 1 because nonce is incremented first.
if (!addressCache[pair.tokenBase]) _cacheAddress(pair.tokenBase, true);
if (!addressCache[pair.tokenQuote]) _cacheAddress(pair.tokenQuote, true);
if (!_addressCache[pair.tokenBase]) _cacheAddress(pair.tokenBase, true);
if (!_addressCache[pair.tokenQuote]) _cacheAddress(pair.tokenQuote, true);
}
}

Expand All @@ -144,7 +121,7 @@ contract Compiler is HyperLiquidity, HyperSwap {
/// @param token Target token to pay or credit.
/// @custom:security Critical. Handles crediting accounts or requesting payment for debits.
function _settleToken(address token) internal {
if (!addressCache[token]) return; // note: Early short circuit, since attempting to settle twice is common for big orders.
if (!_addressCache[token]) return; // note: Early short circuit, since attempting to settle twice is common for big orders.

uint256 global = globalReserves[token];
uint256 actual = _balanceOf(token, address(this));
Expand Down Expand Up @@ -178,4 +155,6 @@ contract Compiler is HyperLiquidity, HyperSwap {
_applyCredit(token, amount);
SafeTransferLib.safeTransferFrom(ERC20(token), msg.sender, address(this), amount);
}

// --- View --- //
}
112 changes: 98 additions & 14 deletions contracts/EnigmaVirtualMachine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import "./interfaces/IEnigma.sol";
import "./interfaces/IERC20.sol";
import "./libraries/Decoder.sol";
import "./libraries/Instructions.sol";
import "./libraries/SafeCast.sol";

/// @title Enigma Virtual Machine
/// @notice Defines the possible instruction set which must be processed in a higher-level compiler.
/// @dev Implements low-level `balanceOf`, re-entrancy guard, instruction constants and state.
/// @title Enigma Virtual Machine.
/// @notice Stores the state of the Enigma with functions to change state.
/// @dev Implements low-level internal virtual functions, re-entrancy guard and state.
abstract contract EnigmaVirtualMachine is IEnigma {
using SafeCast for uint256;
// --- Reentrancy --- //
modifier lock() {
if (locked != 1) revert LockedError();
Expand All @@ -19,7 +21,51 @@ abstract contract EnigmaVirtualMachine is IEnigma {
locked = 1;
}

// --- View --- //

/// @inheritdoc IEnigmaView
function checkJitLiquidity(address account, uint48 poolId)
public
view
virtual
returns (uint256 distance, uint256 timestamp)
{
uint256 previous = positions[account][poolId].blockTimestamp;
timestamp = _blockTimestamp();
distance = timestamp - previous;
}

// --- Internal --- //
/// @dev Must be implemented by the highest level contract.
/// @notice Processing logic for instructions.
function _process(bytes calldata data) internal virtual;

/// @notice First byte should always be the INSTRUCTION_JUMP Enigma code.
/// @dev Expects a special encoding method for multiple instructions.
/// @param data Includes opcode as byte at index 0. First byte should point to next instruction.
/// @custom:security Critical. Processes multiple instructions. Data must be encoded perfectly.
function _jumpProcess(bytes calldata data) internal {
uint8 length = uint8(data[1]);
uint8 pointer = JUMP_PROCESS_START_POINTER; // note: [opcode, length, pointer, ...instruction, pointer, ...etc]
uint256 start;

// For each instruction set...
for (uint256 i; i != length; ++i) {
// Start at the index of the first byte of the next instruction.
start = pointer;

// Set the new pointer to the next instruction, located at the pointer.
pointer = uint8(data[pointer]);

// The `start:` includes the pointer byte, while the `:end` `pointer` is excluded.
if (pointer > data.length) revert JumpError(pointer);
bytes calldata instruction = data[start:pointer];

// Process the instruction.
_process(instruction[1:]); // note: Removes the pointer to the next instruction.
}
}

/// @dev Gas optimized `balanceOf` method.
function _balanceOf(address token, address account) internal view returns (uint256) {
(bool success, bytes memory data) = token.staticcall(
Expand All @@ -39,24 +85,62 @@ abstract contract EnigmaVirtualMachine is IEnigma {
return JUST_IN_TIME_LIQUIDITY_POLICY;
}

// --- Instructions --- //
bytes1 public constant UNKNOWN = 0x00;
bytes1 public constant ADD_LIQUIDITY = 0x01;
bytes1 public constant REMOVE_LIQUIDITY = 0x03;
bytes1 public constant SWAP = 0x05;
bytes1 public constant CREATE_POOL = 0x0B;
bytes1 public constant CREATE_PAIR = 0x0C;
bytes1 public constant CREATE_CURVE = 0x0D;
bytes1 public constant INSTRUCTION_JUMP = 0xAA;
// --- Global --- //

/// @dev Most important function because it manages the solvency of the Engima.
/// @custom:security Critical. Global balances of tokens are compared with the actual `balanceOf`.
function _increaseGlobal(address token, uint256 amount) internal {
globalReserves[token] += amount;
emit IncreaseGlobal(token, amount);
}

/// @dev Equally important to `_increaseGlobal`.
/// @custom:security Critical. Same as above. Implicitly reverts on underflow.
function _decreaseGlobal(address token, uint256 amount) internal {
globalReserves[token] -= amount;
emit DecreaseGlobal(token, amount);
}

// --- Positions --- //

/// @dev Assumes the position is properly allocated to an account by the end of the transaction.
/// @custom:security High. Only method of increasing the liquidity held by accounts.
function _increasePosition(uint48 poolId, uint256 deltaLiquidity) internal {
Position storage pos = positions[msg.sender][poolId];

pos.liquidity += deltaLiquidity.toUint128();
pos.blockTimestamp = _blockTimestamp();

emit IncreasePosition(msg.sender, poolId, deltaLiquidity);
}

/// @dev Equally important as `_increasePosition`.
/// @custom:security Critical. Includes the JIT liquidity check. Implicitly reverts on liquidity underflow.
function _decreasePosition(uint48 poolId, uint256 deltaLiquidity) internal {
Position storage pos = positions[msg.sender][poolId];

pos.liquidity -= deltaLiquidity.toUint128();
pos.blockTimestamp = _blockTimestamp();

emit DecreasePosition(msg.sender, poolId, deltaLiquidity);
}

/// @dev Reverts if liquidity was allocated within time elapsed in seconds returned by `_liquidityPolicy`.
/// @custom:security High. Must be used in place of `_decreasePosition` in most scenarios.
function _decreasePositionCheckJit(uint48 poolId, uint256 deltaLiquidity) internal {
(uint256 distance, uint256 timestamp) = checkJitLiquidity(msg.sender, poolId);
if (_liquidityPolicy() > distance) revert JitLiquidity(distance);

_decreasePosition(poolId, deltaLiquidity);
}

// --- State --- //
/// @dev Pool id -> Pair of a Pool.
mapping(uint16 => Pair) public pairs;
/// @dev Pool id -> Pool Data Structure.
mapping(uint48 => Pool) public pools;
/// @dev Pool id -> Curve Data Structure stores parameters.
mapping(uint32 => Curve) public curves;
/// @dev Token -> Touched Flag. Stored temporary to signal which token reserves were tapped.
mapping(address => bool) public addressCache;
/// @dev Raw curve parameters packed into bytes32 mapped onto a Curve id when it was deployed.
mapping(bytes32 => uint32) public getCurveIds;
/// @dev Token -> Physical Reserves.
Expand Down
55 changes: 1 addition & 54 deletions contracts/HyperLiquidity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity 0.8.10;

import "@primitivefi/rmm-core/contracts/libraries/ReplicationMath.sol";
import "@primitivefi/rmm-core/contracts/libraries/SafeCast.sol";

import "./interfaces/IERC20.sol";
import "./EnigmaVirtualMachine.sol";
Expand All @@ -15,17 +14,6 @@ abstract contract HyperLiquidity is EnigmaVirtualMachine {

// --- View --- //

/// @inheritdoc IEnigmaView
function checkJitLiquidity(address account, uint48 poolId)
public
view
returns (uint256 distance, uint256 timestamp)
{
Position memory pos = positions[account][poolId];
timestamp = _blockTimestamp();
distance = timestamp - pos.blockTimestamp;
}

/// @inheritdoc IEnigmaView
function getLiquidityMinted(
uint48 poolId,
Expand All @@ -40,47 +28,6 @@ abstract contract HyperLiquidity is EnigmaVirtualMachine {

// --- Internal --- //

// --- Global --- //

/// @dev Most important function because it manages the solvency of the Engima.
/// @custom:security Critical. Global balances of tokens are compared with the actual `balanceOf`.
function _increaseGlobal(address token, uint256 amount) internal {
globalReserves[token] += amount;
emit IncreaseGlobal(token, amount);
}

/// @dev Equally important to `_increaseGlobal`.
/// @custom:security Critical. Same as above. Implicitly reverts on underflow.
function _decreaseGlobal(address token, uint256 amount) internal {
globalReserves[token] -= amount;
emit DecreaseGlobal(token, amount);
}

// --- Posiitons --- //

/// @dev Assumes the position is properly allocated to an account by the end of the transaction.
/// @custom:security High. Only method of increasing the liquidity held by accounts.
function _increasePosition(uint48 poolId, uint256 deltaLiquidity) internal {
Position storage pos = positions[msg.sender][poolId];
pos.liquidity += deltaLiquidity.toUint128();
pos.blockTimestamp = _blockTimestamp();

emit IncreasePosition(msg.sender, poolId, deltaLiquidity);
}

/// @dev Equally important as `_decreasePosition`.
/// @custom:security Critical. Includes the JIT liquidity check. Implicitly reverts on liquidity underflow.
function _decreasePosition(uint48 poolId, uint256 deltaLiquidity) internal {
Position storage pos = positions[msg.sender][poolId];
(uint256 distance, uint256 timestamp) = checkJitLiquidity(msg.sender, poolId);
if (_liquidityPolicy() > distance) revert JitLiquidity(pos.blockTimestamp, timestamp);

pos.liquidity -= deltaLiquidity.toUint128();
pos.blockTimestamp = timestamp.toUint128();

emit DecreasePosition(msg.sender, poolId, deltaLiquidity);
}

// --- Liquidity --- //

/// @notice Increases internal reserves, liquidity position, and global token balances.
Expand Down Expand Up @@ -163,7 +110,7 @@ abstract contract HyperLiquidity is EnigmaVirtualMachine {
pool.internalLiquidity -= deltaLiquidity;
pool.blockTimestamp = _blockTimestamp();

_decreasePosition(poolId, deltaLiquidity);
_decreasePositionCheckJit(poolId, deltaLiquidity);

Pair memory pair = pairs[pairId];
_decreaseGlobal(pair.tokenBase, deltaBase);
Expand Down
1 change: 0 additions & 1 deletion contracts/HyperSwap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity 0.8.10;

import "@primitivefi/rmm-core/contracts/libraries/ReplicationMath.sol";
import "@primitivefi/rmm-core/contracts/libraries/SafeCast.sol";

import "./EnigmaVirtualMachine.sol";

Expand Down
4 changes: 2 additions & 2 deletions contracts/interfaces/enigma/IEnigmaErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.10;
/// @title IEnigmaErrors
/// @dev All errors thrown by the Enigma and its higher level contracts.
interface IEnigmaErrors {
// --- Compiler --- //
// --- Decompiler --- //
/// @dev Thrown when attempting to remove more internal token balance than owned by `msg.sender`.
error DrawBalance();
/// @dev Thrown when the jump pointer is further than the length of the next instruction.
Expand Down Expand Up @@ -50,7 +50,7 @@ interface IEnigmaErrors {

// --- Special --- //
/// @dev Thrown if the JIT liquidity condition is false.
error JitLiquidity(uint256 lastTime, uint256 timestamp);
error JitLiquidity(uint256 distance);

// --- Swap --- //
/// @dev Thrown if the effects of a swap put the pool in an invalid state according the the trading function.
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/enigma/IEnigmaEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface IEnigmaEvents {
/// @custom:security High.
event DecreaseGlobal(address indexed token, uint256 amount);

// --- Compiler --- //
// --- Decompiler --- //
/// @dev A payment requested by this contract that must be paid by the `msg.sender` account.
event Debit(address indexed token, uint256 amount);
/// @dev A payment that is paid out to the `msg.sender` account from this contract.
Expand Down
Loading

0 comments on commit c6f692f

Please sign in to comment.