Skip to content

Commit

Permalink
Merge pull request #32 from alcueca/new-wrappers
Browse files Browse the repository at this point in the history
Solidly wrapper
  • Loading branch information
ultrasecreth authored Mar 22, 2024
2 parents c095388 + fed45fd commit b8b73c7
Show file tree
Hide file tree
Showing 6 changed files with 468 additions and 0 deletions.
70 changes: 70 additions & 0 deletions script/SolidlyDeploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.19 <=0.9.0;

import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";

import "./Network.sol";

import { Registry } from "src/Registry.sol";

import { SolidlyWrapper } from "../src/solidly/SolidlyWrapper.sol";

/// @dev See the Solidity Scripting tutorial: https://book.getfoundry.sh/tutorials/solidity-scripting
contract SolidlyDeploy is Script {
bytes32 public constant SALT = keccak256("ultrasecr.eth");

struct Fork {
string name;
address factory;
address weth;
address usdc;
}

mapping(Network network => Fork fork) public forks;

Registry internal registry = Registry(0xa348320114210b8F4eaF1b0795aa8F70803a93EA);

constructor() {
forks[BASE] = Fork({
name: "AerodromeWrapper",
factory: 0x420DD381b31aEf6683db6B902084cB0FFECe40Da,
weth: 0x4200000000000000000000000000000000000006,
usdc: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
});
forks[OPTIMISM] = Fork({
name: "VelodromeWrapper",
factory: 0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a,
weth: 0x4200000000000000000000000000000000000006,
usdc: 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85
});
}

function run() public {
console2.log("Deploying as %s", msg.sender);

Network network = currentNetwork();

Fork memory fork = forks[network];
require(fork.weth != address(0), "Fork not supported");

bytes memory paramsBytes = abi.encode(fork.factory, fork.weth, fork.usdc);

string memory key = fork.name;

if (keccak256(registry.get(key)) != keccak256(paramsBytes)) {
console2.log("Updating registry");
vm.broadcast();
registry.set(key, paramsBytes);
}

(address _factory, address _weth, address _usdc) = abi.decode(registry.get(key), (address, address, address));
console2.log("Factory: %s", _factory);
console2.log("WETH: %s", _weth);
console2.log("USDC: %s", _usdc);

vm.broadcast();
SolidlyWrapper wrapper = new SolidlyWrapper{ salt: SALT }(key, registry);
console2.log("SolidlyWrapper deployed at: %s", address(wrapper));
}
}
137 changes: 137 additions & 0 deletions src/solidly/SolidlyWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: MIT
// Thanks to sunnyRK, yashnaman & ultrasecr.eth
pragma solidity ^0.8.19;

import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";

import { Registry } from "src/Registry.sol";

import { IPoolFactory } from "./interfaces/IPoolFactory.sol";
import { IPoolCallee } from "./interfaces/IPoolCallee.sol";
import { IPool } from "./interfaces/IPool.sol";

import { BaseWrapper, IERC7399, IERC20 } from "../BaseWrapper.sol";

/// @dev Solidly Flash Lender that uses Solidly Pools as source of liquidity.
/// Solidly allows pushing repayments, so we override `_repayTo`.
contract SolidlyWrapper is BaseWrapper, IPoolCallee {
using { canLoan, balance } for IPool;

uint256 private constant WAD = 1e18;

error Unauthorized();
error UnknownPool();
error UnsupportedCurrency(address asset);

// CONSTANTS
IPoolFactory public immutable factory;

// DEFAULT ASSETS
address public immutable weth;
address public immutable usdc;

/// @param reg Registry storing constructor parameters
constructor(string memory name, Registry reg) {
// @param factory_ Solidly SolidlyFactory address
// @param weth_ Weth contract used in Solidly Pairs
// @param usdc_ usdc contract used in Solidly Pairs
(factory, weth, usdc) = abi.decode(reg.getSafe(name), (IPoolFactory, address, address));
}

/**
* @dev Get the Solidly Pool that will be used as the source of a loan. The opposite asset will be WETH, except for
* WETH that will be usdc.
* @param asset The loan currency.
* @param amount The amount of assets to borrow.
* @return pool The Solidly Pool that will be used as the source of the flash loan.
*/
function cheapestPool(address asset, uint256 amount) public view returns (IPool pool, uint256 fee, bool stable) {
address assetOther = asset == weth ? usdc : weth;
IPool sPool = _pool(asset, assetOther, true);
IPool vPool = _pool(asset, assetOther, false);

uint256 sFee = address(sPool) != address(0) ? factory.getFee(sPool, true) : type(uint256).max;
uint256 vFee = address(vPool) != address(0) ? factory.getFee(vPool, false) : type(uint256).max;

if (sFee < vFee) {
if (sPool.canLoan(asset, amount)) return (sPool, sFee, true);
if (vPool.canLoan(asset, amount)) return (vPool, vFee, false);
} else {
if (vPool.canLoan(asset, amount)) return (vPool, vFee, false);
if (sPool.canLoan(asset, amount)) return (sPool, sFee, true);
}
}

/// @inheritdoc IERC7399
function maxFlashLoan(address asset) external view returns (uint256) {
return _maxFlashLoan(asset);
}

function _feeAmount(uint256 amount, uint256 fee) internal pure returns (uint256) {
uint256 feeWAD = fee * 1e14;
uint256 derivedFee = Math.mulDiv(WAD, WAD, WAD - feeWAD, Math.Rounding.Ceil) - WAD;
return Math.mulDiv(amount, derivedFee, WAD, Math.Rounding.Ceil);
}

/// @inheritdoc IERC7399
function flashFee(address asset, uint256 amount) external view returns (uint256) {
(IPool pool, uint256 fee,) = cheapestPool(asset, amount);
if (address(pool) == address(0)) revert UnsupportedCurrency(asset);

return _feeAmount(amount, fee);
}

/// @inheritdoc IPoolCallee
function hook(address sender, uint256 amount0, uint256 amount1, bytes calldata params) external override {
(address asset0, address asset1, uint256 fee, bool stable, bytes memory data) =
abi.decode(params, (address, address, uint256, bool, bytes));

IPool pool = _pool(asset0, asset1, stable);
if (msg.sender != address(pool)) revert UnknownPool();
if (sender != address(this)) revert Unauthorized();

(address asset, uint256 amount) = amount0 > 0 ? (asset0, amount0) : (asset1, amount1);

_bridgeToCallback(asset, amount, _feeAmount(amount, fee), data);
}

function _flashLoan(address asset, uint256 amount, bytes memory data) internal override {
(IPool pool, uint256 fee, bool stable) = cheapestPool(asset, amount);
if (address(pool) == address(0)) revert UnsupportedCurrency(asset);

(address asset0, address asset1) = pool.tokens();
uint256 amount0 = asset == asset0 ? amount : 0;
uint256 amount1 = asset == asset1 ? amount : 0;
bytes memory params = abi.encode(asset0, asset1, fee, stable, data);

pool.swap(amount0, amount1, address(this), params);
}

function _repayTo() internal view override returns (address) {
return msg.sender;
}

function _pool(address tokenA, address tokenB, bool stable) internal view returns (IPool pool) {
(tokenA, tokenB) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
pool = factory.getPool(tokenA, tokenB, stable);
}

function _maxFlashLoan(address asset) internal view returns (uint256 max) {
address assetOther = asset == weth ? usdc : weth;
IPool stable = _pool(asset, assetOther, true);
IPool volatile = _pool(asset, assetOther, false);

uint256 stableBalance = balance(stable, asset);
uint256 volatileBalance = balance(volatile, asset);

return stableBalance > volatileBalance ? stableBalance : volatileBalance;
}
}

function canLoan(IPool pool, address asset, uint256 amount) view returns (bool) {
return balance(pool, asset) >= amount;
}

function balance(IPool pool, address asset) view returns (uint256) {
return IERC20(asset).balanceOf(address(pool));
}
115 changes: 115 additions & 0 deletions src/solidly/interfaces/IPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

interface IPool {
struct Observation {
uint256 timestamp;
uint256 reserve0Cumulative;
uint256 reserve1Cumulative;
}

error BelowMinimumK();
error DepositsNotEqual();
error FactoryAlreadySet();
error InsufficientInputAmount();
error InsufficientLiquidity();
error InsufficientLiquidityBurned();
error InsufficientLiquidityMinted();
error InsufficientOutputAmount();
error InvalidTo();
error IsPaused();
error K();
error NotEmergencyCouncil();
error StringTooLong(string str);

function DOMAIN_SEPARATOR() external view returns (bytes32);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function blockTimestampLast() external view returns (uint256);
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function claimFees() external returns (uint256 claimed0, uint256 claimed1);
function claimable0(address) external view returns (uint256);
function claimable1(address) external view returns (uint256);
function currentCumulativePrices()
external
view
returns (uint256 reserve0Cumulative, uint256 reserve1Cumulative, uint256 blockTimestamp);
function decimals() external view returns (uint8);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
function factory() external view returns (address);
function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256);
function getK() external returns (uint256);
function getReserves() external view returns (uint256 _reserve0, uint256 _reserve1, uint256 _blockTimestampLast);
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function index0() external view returns (uint256);
function index1() external view returns (uint256);
function initialize(address _token0, address _token1, bool _stable) external;
function lastObservation() external view returns (Observation memory);
function metadata()
external
view
returns (uint256 dec0, uint256 dec1, uint256 r0, uint256 r1, bool st, address t0, address t1);
function mint(address to) external returns (uint256 liquidity);
function name() external view returns (string memory);
function nonces(address owner) external view returns (uint256);
function observationLength() external view returns (uint256);
function observations(uint256)
external
view
returns (uint256 timestamp, uint256 reserve0Cumulative, uint256 reserve1Cumulative);
function periodSize() external view returns (uint256);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external;
function poolFees() external view returns (address);
function prices(address tokenIn, uint256 amountIn, uint256 points) external view returns (uint256[] memory);
function quote(address tokenIn, uint256 amountIn, uint256 granularity) external view returns (uint256 amountOut);
function reserve0() external view returns (uint256);
function reserve0CumulativeLast() external view returns (uint256);
function reserve1() external view returns (uint256);
function reserve1CumulativeLast() external view returns (uint256);
function sample(
address tokenIn,
uint256 amountIn,
uint256 points,
uint256 window
)
external
view
returns (uint256[] memory);
function setName(string memory __name) external;
function setSymbol(string memory __symbol) external;
function skim(address to) external;
function stable() external view returns (bool);
function supplyIndex0(address) external view returns (uint256);
function supplyIndex1(address) external view returns (uint256);
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) external;
function symbol() external view returns (string memory);
function sync() external;
function token0() external view returns (address);
function token1() external view returns (address);
function tokens() external view returns (address, address);
function totalSupply() external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
6 changes: 6 additions & 0 deletions src/solidly/interfaces/IPoolCallee.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IPoolCallee {
function hook(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;
}
35 changes: 35 additions & 0 deletions src/solidly/interfaces/IPoolFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

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

interface IPoolFactory {
error FeeInvalid();
error FeeTooHigh();
error InvalidPool();
error NotFeeManager();
error NotPauser();
error NotVoter();
error PoolAlreadyExists();
error SameAddress();
error ZeroAddress();
error ZeroFee();

function MAX_FEE() external view returns (uint256);
function ZERO_FEE_INDICATOR() external view returns (uint256);
function allPools(uint256) external view returns (address);
function allPoolsLength() external view returns (uint256);
function createPool(address tokenA, address tokenB, bool stable) external returns (address pool);
function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool);
function customFee(address) external view returns (uint256);
function feeManager() external view returns (address);
function getFee(IPool pool, bool _stable) external view returns (uint256);
function getPool(address tokenA, address tokenB, uint24 fee) external view returns (IPool);
function getPool(address tokenA, address tokenB, bool stable) external view returns (IPool);
function implementation() external view returns (address);
function isPaused() external view returns (bool);
function isPool(IPool pool) external view returns (bool);
function stableFee() external view returns (uint256);
function volatileFee() external view returns (uint256);
function voter() external view returns (address);
}
Loading

0 comments on commit b8b73c7

Please sign in to comment.