Skip to content

Commit

Permalink
use prettier formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
funderbrker committed Jul 11, 2023
1 parent c943fab commit 063e1d2
Show file tree
Hide file tree
Showing 37 changed files with 458 additions and 307 deletions.
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Ignore artifacts:
lib
out
broadcast
cache

src/interfaces/external
16 changes: 16 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"printWidth": 100,
"tabWidth": 4,
"singleQuote": true,
"overrides": [
{
"files": "*.sol",
"options": {
"semi": false,
"printWidth": 120,
"tabWidth": 4,
"singleQuote": false
}
}
]
}
15 changes: 14 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,17 @@ mainnet = "${ALCHEMY_ETH_API_URL}"
goerli = "${GOERLI_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"

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

# NOTE Project is using prettier formatting, as opposed to forge.
# See more format options https://book.getfoundry.sh/reference/config/formatter
# [fmt]
# line_length = 120
# tab_width = 4
# bracket_spacing = false
# int_types = "long"
# # If function parameters are multiline then always put the function attributes on separate lines
# func_attrs_with_params_multiline = false
# multiline_func_header = "params_first"
# quote_style = "double"
# number_underscore = "thousands"
80 changes: 45 additions & 35 deletions src/Bookkeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
event LiquidationKicked(address liquidator, address position);

constructor() Tractor(PROTOCOL_NAME, PROTOCOL_VERSION) {}

// receive() external {}
// fallback() external {}

function fillOrder(Fill calldata fill, SignedBlueprint calldata orderBlueprint)
external
nonReentrant
verifySignature(orderBlueprint)
{
function fillOrder(
Fill calldata fill,
SignedBlueprint calldata orderBlueprint
) external nonReentrant verifySignature(orderBlueprint) {
// decode order blueprint data and ensure blueprint metadata is valid pairing with embedded data
(bytes1 blueprintDataType, bytes memory blueprintData) = unpackDataField(orderBlueprint.blueprint.data);
require(uint8(blueprintDataType) == uint8(BlueprintDataType.ORDER), "BKDTMM");
Expand All @@ -78,24 +78,28 @@ contract Bookkeeper is Tractor, ReentrancyGuard {

Agreement memory agreement = _agreementFromOrder(fill, order);

uint256 loanValue =
IOracle(agreement.loanOracle.addr).getResistantValue(agreement.loanAmount, agreement.loanOracle.parameters);
uint256 loanValue = IOracle(agreement.loanOracle.addr).getResistantValue(
agreement.loanAmount,
agreement.loanOracle.parameters
);
uint256 collateralValue;

if (order.isOffer) {
agreement.lenderAccount = order.account;
agreement.borrowerAccount = fill.account;
collateralValue = loanValue * fill.borrowerConfig.initCollateralRatio / C.RATIO_FACTOR;
collateralValue = (loanValue * fill.borrowerConfig.initCollateralRatio) / C.RATIO_FACTOR;
agreement.position.parameters = fill.borrowerConfig.positionParameters;
} else {
agreement.lenderAccount = fill.account;
agreement.borrowerAccount = order.account;
collateralValue = loanValue * order.borrowerConfig.initCollateralRatio / C.RATIO_FACTOR;
collateralValue = (loanValue * order.borrowerConfig.initCollateralRatio) / C.RATIO_FACTOR;
agreement.position.parameters = order.borrowerConfig.positionParameters;
}
console.log("agreement coll value: %s", collateralValue);
agreement.collAmount =
IOracle(agreement.collOracle.addr).getResistantAmount(collateralValue, agreement.collOracle.parameters);
agreement.collAmount = IOracle(agreement.collOracle.addr).getResistantAmount(
collateralValue,
agreement.collOracle.parameters
);
console.log("agreement coll amount: %s", agreement.collAmount);
// Set Position data that cannot be computed off chain by caller.
agreement.deploymentTime = block.timestamp;
Expand All @@ -113,15 +117,13 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
}

// NOTE CEI?
function exitPosition(SignedBlueprint calldata agreementBlueprint)
external
payable
nonReentrant
verifySignature(agreementBlueprint)
{
function exitPosition(
SignedBlueprint calldata agreementBlueprint
) external payable nonReentrant verifySignature(agreementBlueprint) {
(bytes1 blueprintDataType, bytes memory blueprintData) = unpackDataField(agreementBlueprint.blueprint.data);
require(
blueprintDataType == bytes1(uint8(BlueprintDataType.AGREEMENT)), "Bookkeeper: Invalid blueprint data type"
blueprintDataType == bytes1(uint8(BlueprintDataType.AGREEMENT)),
"Bookkeeper: Invalid blueprint data type"
);
Agreement memory agreement = abi.decode(blueprintData, (Agreement));
require(
Expand All @@ -141,11 +143,14 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
if (LibUtils.isValidLoanAssetAsCost(agreement.loanAsset, costAsset)) {
lenderOwed += cost;
distributeValue = msg.value;
} // If cost in eth but loan asset is not eth.
}
// If cost in eth but loan asset is not eth.
else if (costAsset.standard == ETH_STANDARD) {
require(msg.value == cost, "exitPosition: msg.value mismatch from Eth denoted cost");
IAccount(agreement.lenderAccount.addr).loadFromPosition{value: msg.value}(
costAsset, cost, agreement.lenderAccount.parameters
costAsset,
cost,
agreement.lenderAccount.parameters
);
} else {
revert("exitPosition: isLiquidatable: invalid cost asset");
Expand All @@ -155,18 +160,18 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
position.distribute{value: distributeValue}(msg.sender, lenderOwed, agreement);

IAccount(agreement.borrowerAccount.addr).unlockCollateral(
agreement.collAsset, agreement.collAmount, agreement.borrowerAccount.parameters
agreement.collAsset,
agreement.collAmount,
agreement.borrowerAccount.parameters
);

// Marks position as closed from Bookkeeper pov.
position.transferContract(msg.sender);
}

function kick(SignedBlueprint calldata agreementBlueprint)
external
nonReentrant
verifySignature(agreementBlueprint)
{
function kick(
SignedBlueprint calldata agreementBlueprint
) external nonReentrant verifySignature(agreementBlueprint) {
(, bytes memory blueprintData) = unpackDataField(agreementBlueprint.blueprint.data);
// require(blueprintDataType == bytes1(uint8(BlueprintDataType.AGREEMENT)), "BKKIBDT"); // decoding will fail
Agreement memory agreement = abi.decode(blueprintData, (Agreement));
Expand Down Expand Up @@ -205,19 +210,22 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
// NOTE lots of gas savings if collateral can be kept in borrower account until absolutely necessary.
console.log("locking %s of %s as collateral", agreement.collAmount, agreement.collAsset.addr);
IAccount(agreement.borrowerAccount.addr).lockCollateral(
agreement.collAsset, agreement.collAmount, agreement.borrowerAccount.parameters
agreement.collAsset,
agreement.collAmount,
agreement.borrowerAccount.parameters
);
IPosition(agreement.position.addr).deploy(
agreement.loanAsset, agreement.loanAmount, agreement.position.parameters
agreement.loanAsset,
agreement.loanAmount,
agreement.position.parameters
);
}

/// @dev assumes compatibility between match, offer, and request already verified.
function _agreementFromOrder(Fill calldata fill, Order memory order)
private
pure
returns (Agreement memory agreement)
{
function _agreementFromOrder(
Fill calldata fill,
Order memory order
) private pure returns (Agreement memory agreement) {
// NOTE MAKE CHECKS FOR VALIDITY

// NOTE this is prly not gas efficient bc of zero -> non-zero changes...
Expand All @@ -241,8 +249,10 @@ contract Bookkeeper is Tractor, ReentrancyGuard {
function _signAgreement(Agreement memory agreement) private returns (SignedBlueprint memory signedBlueprint) {
// Create blueprint to store signed Agreement off chain via events.
signedBlueprint.blueprint.publisher = address(this);
signedBlueprint.blueprint.data =
packDataField(bytes1(uint8(BlueprintDataType.AGREEMENT)), abi.encode(agreement));
signedBlueprint.blueprint.data = packDataField(
bytes1(uint8(BlueprintDataType.AGREEMENT)),
abi.encode(agreement)
);
signedBlueprint.blueprint.endTime = type(uint256).max;
signedBlueprint.blueprintHash = getBlueprintHash(signedBlueprint.blueprint);
// NOTE: Security: Is is possible to intentionally manufacture a blueprint with different data that creates the same hash?
Expand Down
9 changes: 9 additions & 0 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ https://fravoll.github.io/solidity-patterns/pull_over_push.html

- What are Pharos invariants?
https://www.nascent.xyz/idea/youre-writing-require-statements-wrong

## Temporary Security Limitations
In order to ensure a more secure launch, Pharos will limit some early functionality. This provides us more time to
test, audit, and improve some of the elements that are most novel.

- Testnet / L2 testing will have agreement size limitations.
- Permissionless use of 3rd party modules will be disabled.
- Direct interaction with Position protocol wrappers will not be implemented.

### Core:

### Entity-Centric
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/IAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ import {Asset} from "src/libraries/LibUtils.sol";
interface IAccount {
/// @notice Transfer asset and increment account balance. Pulls asset from sender or uses msg.value.
function loadFromUser(Asset calldata asset, uint256 amount, bytes calldata parameters) external payable;

/// @notice Transfer asset and increment account balance. Pulls asset from sender or uses msg.value.
/// @dev Assets may not literally be coming from a position.
function loadFromPosition(Asset calldata asset, uint256 amount, bytes calldata parameters) external payable;

/// @notice Transfer asset out and decrement account balance. Pushes asset to sender.
function unloadToUser(Asset calldata asset, uint256 amount, bytes calldata parameters) external;

/// @notice Transfer loan or collateral asset from account to Position MPC. Pushes.
function unloadToPosition(
address position,
Expand All @@ -60,6 +63,7 @@ interface IAccount {
) external;

function lockCollateral(Asset calldata asset, uint256 amount, bytes calldata parameters) external;

function unlockCollateral(Asset calldata asset, uint256 amount, bytes calldata parameters) external;

// NOTE is is possible to (securely) require the owner addr to be the first parameter so that owner can
Expand Down
9 changes: 5 additions & 4 deletions src/interfaces/IAssessor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import {Asset} from "src/libraries/LibUtils.sol";
interface IAssessor {
/// @notice Returns the cost of a loan (not including principle) and the asset it is denoted in.
/// @dev Asset must be ETH or ERC20.
function getCost(Agreement calldata agreement, uint256 currentAmount)
external
view
returns (Asset memory asset, uint256 amount);
function getCost(
Agreement calldata agreement,
uint256 currentAmount
) external view returns (Asset memory asset, uint256 amount);

function canHandleAsset(Asset calldata asset, bytes calldata parameters) external view returns (bool);
}
3 changes: 3 additions & 0 deletions src/interfaces/IBookkeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {SignedBlueprint} from "lib/tractor/Tractor.sol";

interface IBookkeeper is ITractor {
function signPublishOrder(Order calldata order, uint256 endTime) external;

function fillOrder(Fill calldata fill, SignedBlueprint calldata orderBlueprint) external;

function exitPosition(SignedBlueprint calldata agreementBlueprint) external payable;

function kick(SignedBlueprint calldata agreementBlueprint) external;
}
9 changes: 5 additions & 4 deletions src/interfaces/ILiquidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ interface ILiquidator {
/// @notice Handles receipt of a position that the bookkeeper has passed along for liquidation.
function receiveKick(address kicker, Agreement calldata agreement) external;

function canHandleAssets(Asset calldata loanAsset, Asset calldata collAsset, bytes calldata parameters)
external
view
returns (bool);
function canHandleAssets(
Asset calldata loanAsset,
Asset calldata collAsset,
bytes calldata parameters
) external view returns (bool);
}
11 changes: 7 additions & 4 deletions src/interfaces/IPosition.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,19 @@ interface IPosition is IAccessControl {

/// @notice Get current exitable value of the position, denoted in loan asset.
function getCloseAmount(bytes calldata parameters) external view returns (uint256);

/// @notice Transfer the position to a new controller. Used for liquidations.
/// @dev Do not set admin role to prevent liquidator from pushing the position back into the protocol.
function transferContract(address controller) external;

function canHandleAsset(Asset calldata asset, bytes calldata parameters) external pure returns (bool);

/// @notice Pass through function to allow the position to interact with other contracts after liquidation.
/// @dev Internal functions are not reachable. // NOTE right? bc allowing controller to be set *back* to bookkeeper will open exploits
function passThrough(address payable destination, bytes calldata data, bool delegateCall)
external
payable
returns (bool, bytes memory);
function passThrough(
address payable destination,
bytes calldata data,
bool delegateCall
) external payable returns (bool, bytes memory);
// function removeEth(address payable recipient) external // ONLY_ROLE(BOOKKEEPER_ROLE)
}
20 changes: 13 additions & 7 deletions src/libraries/LibBookkeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,26 @@ library LibBookkeeper {
uint256 outstandingValue;
if (LibUtils.isValidLoanAssetAsCost(agreement.loanAsset, costAsset)) {
if (cost > exitAmount) return true;
outstandingValue =
IOracle(agreement.loanOracle.addr).getSpotValue(exitAmount - cost, agreement.loanOracle.parameters);
outstandingValue = IOracle(agreement.loanOracle.addr).getSpotValue(
exitAmount - cost,
agreement.loanOracle.parameters
);
} else if (costAsset.standard == ETH_STANDARD) {
uint256 positionValue =
IOracle(agreement.loanOracle.addr).getSpotValue(exitAmount, agreement.loanOracle.parameters);
uint256 positionValue = IOracle(agreement.loanOracle.addr).getSpotValue(
exitAmount,
agreement.loanOracle.parameters
);
if (positionValue > cost) return true;
outstandingValue = positionValue - cost;
} else {
revert("isLiquidatable: invalid cost asset");
}
uint256 collValue =
IOracle(agreement.collOracle.addr).getSpotValue(agreement.collAmount, agreement.collOracle.parameters);
uint256 collValue = IOracle(agreement.collOracle.addr).getSpotValue(
agreement.collAmount,
agreement.collOracle.parameters
);

uint256 collateralRatio = C.RATIO_FACTOR * outstandingValue / collValue;
uint256 collateralRatio = (C.RATIO_FACTOR * outstandingValue) / collValue;

if (collateralRatio < agreement.minCollateralRatio) {
return true;
Expand Down
17 changes: 11 additions & 6 deletions src/libraries/LibUniswapV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ library LibUniswapV3 {
(tokenIn, tokenOut, fee) = path.decodeFirstPool();
tokens[i] = tokenIn;
// console.log("tokenIn: %s, tokenOut: %s, fee: %s", tokenIn, tokenOut, fee);
address pool =
PoolAddress.computeAddress(C.UNI_V3_FACTORY, PoolAddress.getPoolKey(tokenIn, tokenOut, fee));
address pool = PoolAddress.computeAddress(
C.UNI_V3_FACTORY,
PoolAddress.getPoolKey(tokenIn, tokenOut, fee)
);
// console.log("pool: %s", pool);
// Computation depends on PoolAddress.POOL_INIT_CODE_HASH. Default value in Uni repo may not be correct.
ticks[i] = getTWATick(pool, twapTime);
Expand All @@ -72,14 +74,17 @@ library LibUniswapV3 {
/// Get the TWAP of the pool across interval. token1/token0.
function getTWATick(address pool, uint32 twapTime) internal view returns (int24 arithmeticMeanTick) {
if (twapTime == 0) {
(, arithmeticMeanTick,,,,,) = IUniswapV3PoolState(pool).slot0();
(, arithmeticMeanTick, , , , , ) = IUniswapV3PoolState(pool).slot0();
} else {
require(LibUtils.isDeployedContract(pool), "Invalid pool, no contract at address");
require(OracleLibrary.getOldestObservationSecondsAgo(pool) >= twapTime, "UniV3 pool observations too young"); // ensure needed data is available
require(
OracleLibrary.getOldestObservationSecondsAgo(pool) >= twapTime,
"UniV3 pool observations too young"
); // ensure needed data is available
// console.log("oldest observation seconds ago: %s", OracleLibrary.getOldestObservationSecondsAgo(pool));
(,,, uint16 observationCardinality,,,) = IUniswapV3PoolState(pool).slot0();
(, , , uint16 observationCardinality, , , ) = IUniswapV3PoolState(pool).slot0();
require(observationCardinality >= twapTime / 12, "UniV3 pool cardinality too low"); // shortest case scenario should always cover twap time
(arithmeticMeanTick,) = OracleLibrary.consult(pool, twapTime);
(arithmeticMeanTick, ) = OracleLibrary.consult(pool, twapTime);
// console.log("arithmeticMeanTick:");
// console.logInt(arithmeticMeanTick);
}
Expand Down
1 change: 0 additions & 1 deletion src/libraries/LibUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,3 @@ library LibUtils {
return true;
}
}

Loading

0 comments on commit 063e1d2

Please sign in to comment.