Skip to content

Commit

Permalink
feat(blue-sdk-viem): make queries ultra fast
Browse files Browse the repository at this point in the history
  • Loading branch information
Rubilmax committed Sep 11, 2024
1 parent bed06ff commit 774c8a2
Show file tree
Hide file tree
Showing 40 changed files with 2,164 additions and 498 deletions.
2 changes: 1 addition & 1 deletion packages/blue-sdk-ethers/src/fetch/Holding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export async function fetchHolding(
runner,
)
.isWhitelisted(user, overrides)
.catch(() => undefined);
.catch(() => false);

return holding;
}
19 changes: 3 additions & 16 deletions packages/blue-sdk-viem/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { MarketConfig } from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/MarketConfig";

const config = await MarketConfig.fetch(
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc",
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc" as MarketId,
client // viem client.
);

Expand All @@ -67,16 +67,10 @@ import { Market } from "@morpho-org/blue-sdk";
import "@morpho-org/blue-sdk-viem/lib/augment/Market";

const market = await Market.fetch(
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc",
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc" as MarketId,
client // viem client.
);

// Or from a config, to fetch faster (skips fetching the config):
// const market = Market.fetchFromConfig(
// config,
// client // viem client.
// );

market.utilization; // e.g. 92% (scaled by WAD).
market.liquidity; // e.g. 23_000000n (in loan assets).
market.apyAtTarget; // e.g. 3% (scaled by WAD).
Expand All @@ -97,17 +91,10 @@ import "@morpho-org/blue-sdk-viem/lib/augment/Position";

const position = await AccrualPosition.fetch(
"0x7f65e7326F22963e2039734dDfF61958D5d284Ca",
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc",
"0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc" as MarketId,
client // viem client.
);

// Or from a config, to fetch faster:
// const position = AccrualPosition.fetchFromConfig(
// "0x7f65e7326F22963e2039734dDfF61958D5d284Ca",
// config,
// client // viem client.
// );

position.borrowAssets; // e.g. 23_000000n (in loan assets).
position.isHealthy; // e.g. true.
position.maxBorrowableAssets; // e.g. 2100_000000n (in loan assets).
Expand Down
87 changes: 87 additions & 0 deletions packages/blue-sdk-viem/contracts/GetHolding.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IERC20Permit} from "./interfaces/IERC20Permit.sol";
import {IPermit2, Permit2Allowance} from "./interfaces/IPermit2.sol";
import {IWrappedBackedToken} from "./interfaces/IWrappedBackedToken.sol";
import {IWhitelistControllerAggregator} from "./interfaces/IWhitelistControllerAggregator.sol";
import {IERC20Permissioned} from "./interfaces/IERC20Permissioned.sol";

struct ERC20Allowances {
uint256 morpho;
uint256 permit2;
uint256 bundler;
}

struct Permit2Allowances {
Permit2Allowance morpho;
Permit2Allowance bundler;
}

enum Boolean {
Undefined,
False,
True
}

struct HoldingResponse {
uint256 balance;
ERC20Allowances erc20Allowances;
Permit2Allowances permit2Allowances;
bool isErc2612;
uint256 erc2612Nonce;
bool canTransfer;
}

contract GetHolding {
function query(
IERC20Permit token,
address account,
address morpho,
IPermit2 permit2,
address bundler,
bool isWrappedBackedToken,
bool isErc20Permissioned
) external view returns (HoldingResponse memory res) {
if (address(token) == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
res.balance = account.balance;
res.erc20Allowances =
ERC20Allowances({morpho: type(uint256).max, permit2: type(uint256).max, bundler: type(uint256).max});
res.canTransfer = true;
} else {
res.balance = token.balanceOf(account);
res.erc20Allowances = ERC20Allowances({
morpho: token.allowance(account, morpho),
permit2: token.allowance(account, address(permit2)),
bundler: token.allowance(account, bundler)
});
res.permit2Allowances = Permit2Allowances({
morpho: permit2.allowance(account, address(token), morpho),
bundler: permit2.allowance(account, address(token), bundler)
});

try token.nonces(account) returns (uint256 nonce) {
res.isErc2612 = true;
res.erc2612Nonce = nonce;
} catch {}

res.canTransfer = !isWrappedBackedToken && !isErc20Permissioned;

if (isWrappedBackedToken) {
try IWrappedBackedToken(address(token)).whitelistControllerAggregator() returns (
IWhitelistControllerAggregator whitelistControllerAggregator
) {
try whitelistControllerAggregator.isWhitelisted(account) returns (bool isWhitelisted) {
if (isWhitelisted) res.canTransfer = true;
} catch {}
} catch {}
}

if (isErc20Permissioned) {
try IERC20Permissioned(address(token)).hasPermission(account) returns (bool hasPermission) {
if (hasPermission) res.canTransfer = true;
} catch {}
}
}
}
}
32 changes: 32 additions & 0 deletions packages/blue-sdk-viem/contracts/GetMarket.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IMorpho, Id, MarketParams, Market} from "./interfaces/IMorpho.sol";
import {IOracle} from "./interfaces/IOracle.sol";
import {IAdaptiveCurveIrm} from "./interfaces/IAdaptiveCurveIrm.sol";

struct MarketResponse {
MarketParams marketParams;
Market market;
uint256 price;
uint256 rateAtTarget;
}

contract GetMarket {
function query(IMorpho morpho, Id id, IAdaptiveCurveIrm adaptiveCurveIrm)
external
view
returns (MarketResponse memory res)
{
res.marketParams = morpho.idToMarketParams(id);
res.market = morpho.market(id);

if (res.marketParams.oracle != address(0)) {
res.price = IOracle(res.marketParams.oracle).price();
}

if (res.marketParams.irm == address(adaptiveCurveIrm)) {
res.rateAtTarget = uint256(adaptiveCurveIrm.rateAtTarget(id));
}
}
}
90 changes: 90 additions & 0 deletions packages/blue-sdk-viem/contracts/GetVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IMorpho, Id, MarketParams} from "./interfaces/IMorpho.sol";
import {IMetaMorpho, PendingUint192, PendingAddress} from "./interfaces/IMetaMorpho.sol";
import {IPublicAllocator} from "./interfaces/IPublicAllocator.sol";

struct VaultConfig {
address asset;
string symbol;
string name;
uint256 decimals;
uint256 decimalsOffset;
}

struct PublicAllocatorConfig {
address admin;
uint256 fee;
uint256 accruedFee;
}

struct VaultResponse {
VaultConfig config;
address owner;
address curator;
address guardian;
uint256 timelock;
PendingUint192 pendingTimelock;
PendingAddress pendingGuardian;
address pendingOwner;
uint256 fee;
address feeRecipient;
address skimRecipient;
uint256 totalSupply;
uint256 totalAssets;
uint256 lastTotalAssets;
Id[] supplyQueue;
Id[] withdrawQueue;
PublicAllocatorConfig publicAllocatorConfig;
}

contract GetVault {
function query(IMetaMorpho vault, IPublicAllocator publicAllocator)
external
view
returns (VaultResponse memory res)
{
res.config = VaultConfig({
asset: vault.asset(),
symbol: vault.symbol(),
name: vault.name(),
decimals: vault.decimals(),
decimalsOffset: vault.DECIMALS_OFFSET()
});

res.owner = vault.owner();
res.curator = vault.curator();
res.guardian = vault.guardian();
res.timelock = vault.timelock();
res.pendingTimelock = vault.pendingTimelock();
res.pendingGuardian = vault.pendingGuardian();
res.pendingOwner = vault.pendingOwner();
res.fee = vault.fee();
res.feeRecipient = vault.feeRecipient();
res.skimRecipient = vault.skimRecipient();
res.totalSupply = vault.totalSupply();
res.totalAssets = vault.totalAssets();
res.lastTotalAssets = vault.lastTotalAssets();

uint256 supplyQueueLength = vault.supplyQueueLength();
res.supplyQueue = new Id[](supplyQueueLength);
for (uint256 i; i < supplyQueueLength; ++i) {
res.supplyQueue[i] = vault.supplyQueue(i);
}

uint256 withdrawQueueLength = vault.withdrawQueueLength();
res.withdrawQueue = new Id[](withdrawQueueLength);
for (uint256 i; i < withdrawQueueLength; ++i) {
res.withdrawQueue[i] = vault.withdrawQueue(i);
}

if (vault.isAllocator(address(publicAllocator))) {
res.publicAllocatorConfig = PublicAllocatorConfig({
admin: publicAllocator.admin(address(vault)),
fee: publicAllocator.fee(address(vault)),
accruedFee: publicAllocator.accruedFee(address(vault))
});
}
}
}
18 changes: 18 additions & 0 deletions packages/blue-sdk-viem/contracts/interfaces/IAdaptiveCurveIrm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0;

import {IIrm} from "./IIrm.sol";
import {Id} from "./IMorpho.sol";

/// @title IAdaptiveCurveIrm
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Interface exposed by the AdaptiveCurveIrm.
interface IAdaptiveCurveIrm is IIrm {
/// @notice Address of Morpho.
function MORPHO() external view returns (address);

/// @notice Rate at target utilization.
/// @dev Tells the height of the curve.
function rateAtTarget(Id id) external view returns (int256);
}
81 changes: 81 additions & 0 deletions packages/blue-sdk-viem/contracts/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol)

pragma solidity >=0.5.0;

/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);

/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);

/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);

/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);

/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);

/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);

/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

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

interface IERC20Permissioned is IERC20Permit {
function hasPermission(address account) external view returns (bool);
}
Loading

0 comments on commit 774c8a2

Please sign in to comment.