Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 16 additions & 28 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
# Contracts admin.
ADMIN=
# Address that can increase/decerease LP conversion rate.
ADJUSTER=
# Deposits to Liquidity Hub are only allowed till this limit is reached.
ASSETS_LIMIT=
# Liquidity mining tiers. Multiplier will be divided by 1000,000,000. So 1750000000 will result in 1.75x.
# There is no limit to the number of tiers, but has to be atleast one.
TIER_1_SECONDS=7776000
TIER_1_MULTIPLIER=300000000
TIER_2_SECONDS=15552000
TIER_2_MULTIPLIER=800000000
TIER_3_SECONDS=31104000
TIER_3_MULTIPLIER=1666666667
# Rebalance caller.
REBALANCE_CALLER=
# Liquidity Pool parameters.
# Value 500 will result in health factor 5.
MIN_HEALTH_FACTOR=500
# Value 20 will result in LTV 20%.
DEFAULT_LTV=20
MPC_ADDRESS=
WITHDRAW_PROFIT=
PAUSER=
# General deployment parameters.
DEPLOYER_PRIVATE_KEY=
# Deploy id + the deployer address acts as a key to determine deployed contract addresses.
# It should only be set once and never change, unless you are planning to redeploy everything.
DEPLOY_ID=MVP

# Upgrade id + the deployer address acts as a key to determine address of new versions of implementations
# or instances. It should be set for a particular release, when upgrade scripts are executed across all chains.
UPGRADE_ID=V1

# Deployer address is used to determine deployed contract addresses when the script/task is executed
# with a key that is different from original deployer (eg. admin).
DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

# Private key that is going to sign transactions be that deployment or other scripts/tasks.
PRIVATE_KEY=

VERIFY=false
BASE_SEPOLIA_RPC=
ETHEREUM_SEPOLIA_RPC=
ARBITRUM_SEPOLIA_RPC=
ETHERSCAN_BASE_SEPOLIA=
ETHERSCAN_ETHEREUM_SEPOLIA=
ETHERSCAN_ARBITRUM_SEPOLIA=
# Upgrade and configuration update parameters.
LIQUIDITY_POOL=
REBALANCER=

# Testing parameters.
FORK_PROVIDER=https://eth-mainnet.public.blastapi.io
USDC_OWNER_ADDRESS=0x7713974908Be4BEd47172370115e8b1219F4A5f0
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ jobs:
run: npm run compile
- name: Hardhat Tests
run: npm run test
- name: Deploy Tests
run: npm run test:deploy
- name: Script Tests
run: npm run test:scripts
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ Local deployment wallet private key is: 0xac0974bec39a17e36ba4a6b4d238ff944bacb4

To deploy to live networks, create a `.env` file using the `.env.example` and fill in the relevant variables (only the ones needed for your deployment).
You need to have a private key specified.
Inspect and modify if needed the `network.config.ts`.
To deploy to Base Testnet do:

npm run deploy-basetest

Make sure to save the output of the deployment. You can use those later in the `.env` file to run other scripts on the already deployed system.

You could optionally set VERIFY to `true` in order to publish the source code after deployemnt to sourcify.dev.
You could optionally set VERIFY to `true` in order to publish the source code after deployment to Etherscan.
10 changes: 10 additions & 0 deletions contracts/LiquidityHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import {IManagedToken} from "./interfaces/IManagedToken.sol";
import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {ILiquidityHub} from "./interfaces/ILiquidityHub.sol";

/// @title A modified version of the ERC4626 vault with the following key differences:
/// 1. The shares token functionality is delegated to a dedicated token contract.
/// 2. The total assets variable cannot be increased by a donation, making inflation by users impossible.
/// 3. The total assets could be increased or decreased by an Adjuster role to modify the conversion rate.
/// 4. Has an admin controlled maximum total assets limit.
/// 5. Supports deposit with permit if the underlying asset supports permit as well.
/// 6. Underlying assets are deposited/withdrawn to/from a connected ILiquidityPool contract.
/// 7. To withdraw/redeem on behalf, owner has to approve spender on the shares contract instead of this one.
/// @notice Upgradeable.
/// @author Oleksii Matiiasevych <[email protected]>
contract LiquidityHub is ILiquidityHub, ERC4626Upgradeable, AccessControlUpgradeable {
using Math for uint256;

Expand Down
9 changes: 9 additions & 0 deletions contracts/LiquidityMining.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/// @title Staking contract locks the stake according to the configured/specified tier parameters.
/// Each tier specifies a lock period in seconds and a multiplier that is used to mint score to the staker.
/// The score is represented as an ERC20 token which this contract is itself.
/// Each stake, even by the same user, is treated independently.
/// Rewards are minted immediately upon staking because user cannot unstake early.
/// The multiplier is specified in units of 0.01, eg. 150 will result in a 1.5x of staked amount as score.
/// The owner can disable the future staking, without the ability to enable it again, which does not affect
/// existing stakes.
/// @author Oleksii Matiiasevych <[email protected]>
contract LiquidityMining is ERC20, Ownable {
using SafeERC20 for IERC20;

Expand Down
10 changes: 10 additions & 0 deletions contracts/LiquidityPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {IBorrower} from "./interfaces/IBorrower.sol";

/// @title Liquidity pool contract holds the liquidity asset and allows solvers to borrow
/// the asset from the pool and to perform an external function call upon providing the MPC signature.
/// It's possible to perform borrowing with swap by the solver (the solver gets the borrowed
/// assets from the pool, swaps them to fill tokens, and then the pool performs the target call).
/// Repayment is done by transferring the assets to the contract without calling any function.
/// Rebalancing is done by depositing and withdrawing assets from this pool by the LIQUIDITY_ADMIN_ROLE.
/// Profit from borrowing is accounted for and can be withdrawn by the WITHDRAW_PROFIT_ROLE.
/// Borrowing can be paused by the WITHDRAW_PROFIT_ROLE before withdrawing the profit.
/// The contract is pausable by the PAUSER_ROLE.
/// @author Tanya Bushenyova <[email protected]>
contract LiquidityPool is ILiquidityPool, AccessControl, EIP712, Pausable {
using SafeERC20 for IERC20;
using ECDSA for bytes32;
Expand Down
31 changes: 25 additions & 6 deletions contracts/LiquidityPoolAave.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ import {IAaveOracle} from "./interfaces/IAaveOracle.sol";
import {IAavePoolDataProvider} from "./interfaces/IAavePoolDataProvider.sol";
import {LiquidityPool} from "./LiquidityPool.sol";

/// @title A version of the liquidity pool contract that uses Aave pool.
/// Deposits of the liquidity token are supplied to Aave as collateral.
/// It's possible to borrow other tokens from Aave pool upon providing the MPC signature.
/// The contract verifies that the borrowing won't put it at risk of liquidation
/// by checking the custom LTV and health factor that should be configured with a safety margin.
/// Repayment to Aave is done by transferring the assets to the contract and calling the repay function.
/// Rebalancing is done by depositing and withdrawing assets from Aave pool by the liquidity admin role.
/// Profit from borrowing and accrued interest from supplying liquidity is accounted for
/// and can be withdrawn by the WITHDRAW_PROFIT_ROLE.
/// @author Tanya Bushenyova <[email protected]>
contract LiquidityPoolAave is LiquidityPool {
using SafeERC20 for IERC20;

Expand All @@ -23,14 +33,15 @@ contract LiquidityPoolAave is LiquidityPool {
uint256 public minHealthFactor;
uint256 public defaultLTV;

mapping(address token => uint256 ltv) public _borrowTokenLTV;
mapping(address token => uint256 ltv) public borrowTokenLTV;

error TokenLtvExceeded();
error NoCollateral();
error HealthFactorTooLow();
error NothingToRepay();
error CollateralNotSupported();
error CannotWithdrawAToken();
error InvalidLength();

event SuppliedToAave(uint256 amount);
event BorrowTokenLTVSet(address token, uint256 oldLTV, uint256 newLTV);
Expand Down Expand Up @@ -73,10 +84,18 @@ contract LiquidityPoolAave is LiquidityPool {

// Admin functions

function setBorrowTokenLTV(address token, uint256 ltv) external onlyRole(DEFAULT_ADMIN_ROLE) {
uint256 oldLTV = _borrowTokenLTV[token];
_borrowTokenLTV[token] = ltv;
emit BorrowTokenLTVSet(token, oldLTV, ltv);
function setBorrowTokenLTVs(
address[] calldata tokens,
uint256[] calldata ltvs
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(tokens.length == ltvs.length, InvalidLength());
for (uint256 i = 0; i < tokens.length; ++i) {
address token = tokens[i];
uint256 ltv = ltvs[i];
uint256 oldLTV = borrowTokenLTV[token];
borrowTokenLTV[token] = ltv;
emit BorrowTokenLTVSet(token, oldLTV, ltv);
}
}

function setDefaultLTV(uint256 defaultLTV_) external onlyRole(DEFAULT_ADMIN_ROLE) {
Expand All @@ -94,7 +113,7 @@ contract LiquidityPoolAave is LiquidityPool {
// Internal functions

function _checkTokenLTV(address borrowToken) private view {
uint256 ltv = _borrowTokenLTV[borrowToken];
uint256 ltv = borrowTokenLTV[borrowToken];
if (ltv == 0) ltv = defaultLTV;

uint256 totalCollateral = ATOKEN.balanceOf(address(this));
Expand Down
4 changes: 4 additions & 0 deletions contracts/ManagedToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ pragma solidity 0.8.28;
import {ERC20, ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {IManagedToken} from "./interfaces/IManagedToken.sol";

/// @title An ERC20 token with enabled Permit and mint/burn/spendAllowance functions exposed to
/// an immutable manager which is expected to be a contract too. This is meant to keep the token
/// logic simple enough so that it does not need to be upgradeable.
/// @author Oleksii Matiiasevych <[email protected]>
contract ManagedToken is IManagedToken, ERC20Permit {
address immutable public MANAGER;

Expand Down
6 changes: 6 additions & 0 deletions contracts/Rebalancer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {ILiquidityPool} from "./interfaces/ILiquidityPool.sol";
import {IRebalancer} from "./interfaces/IRebalancer.sol";
import {ICCTPTokenMessenger, ICCTPMessageTransmitter} from "./interfaces/ICCTP.sol";

/// @title Facilitates liquidity movement between Liquidity Pools on same/different chains.
/// Routes, which is a destination pool/domain and a bridging provider, have to be approved by admin.
/// Rebalancing only takes arbitrary input on the amount, then contract enforces correct processing.
/// REBALANCER_ROLE is needed to finalize/init rebalancing process.
/// @notice Upgradeable.
/// @author Oleksii Matiiasevych <[email protected]>
contract Rebalancer is IRebalancer, AccessControlUpgradeable {
using SafeERC20 for IERC20;
using BitMaps for BitMaps.BitMap;
Expand Down
6 changes: 6 additions & 0 deletions contracts/SprinterLiquidityMining.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {
import {ILiquidityHub} from "./interfaces/ILiquidityHub.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";

/// @title Modified version of the LiquidityMining contract, with the following differences:
/// 1. The score tokens can only be minted. No transfers or burns. Used for convinient show on explorers.
/// 2. Supports deposit into the connected liquidity hub with a subsequent stake in the same transaction.
/// 3. Supports above scenario with permit of liquidity hub's underlying asset.
/// 4. Supports unstake with a subsequent withdraw from the connected liquidity hub.
/// @author Oleksii Matiiasevych <[email protected]>
contract SprinterLiquidityMining is LiquidityMining {
using SafeERC20 for IERC20;

Expand Down
3 changes: 3 additions & 0 deletions contracts/SprinterUSDCLPShare.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ pragma solidity 0.8.28;

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

/// @title An ERC20 token that represents shares in the Sprinter USDC liquidity reserves.
/// Meant to be managed by LiquidityHub.
/// @author Oleksii Matiiasevych <[email protected]>
contract SprinterUSDCLPShare is ManagedToken {
constructor(address manager)
ManagedToken("Sprinter USDC LP Share", "sprUSDC-LP", manager)
Expand Down
153 changes: 153 additions & 0 deletions contracts/interfaces/ICreateX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.28;

/**
* @title CreateX Factory Interface Definition
* @author pcaversaccio (https://web.archive.org/web/20230921103111/https://pcaversaccio.com/)
* @custom:coauthor Matt Solomon (https://web.archive.org/web/20230921103335/https://mattsolomon.dev/)
*/
interface ICreateX {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* TYPES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

struct Values {
uint256 constructorAmount;
uint256 initCallAmount;
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

event ContractCreation(address indexed newContract, bytes32 indexed salt);
event ContractCreation(address indexed newContract);
event Create3ProxyContractCreation(address indexed newContract, bytes32 indexed salt);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

error FailedContractCreation(address emitter);
error FailedContractInitialisation(address emitter, bytes revertData);
error InvalidSalt(address emitter);
error InvalidNonceValue(address emitter);
error FailedEtherTransfer(address emitter, bytes revertData);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

function deployCreate(bytes memory initCode) external payable returns (address newContract);

function deployCreateAndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);

function deployCreateAndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);

function deployCreateClone(address implementation, bytes memory data) external payable returns (address proxy);

function computeCreateAddress(address deployer, uint256 nonce) external view returns (address computedAddress);

function computeCreateAddress(uint256 nonce) external view returns (address computedAddress);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE2 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address newContract);

function deployCreate2(bytes memory initCode) external payable returns (address newContract);

function deployCreate2AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);

function deployCreate2AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);

function deployCreate2AndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);

function deployCreate2AndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);

function deployCreate2Clone(
bytes32 salt,
address implementation,
bytes memory data
) external payable returns (address proxy);

function deployCreate2Clone(address implementation, bytes memory data) external payable returns (address proxy);

function computeCreate2Address(
bytes32 salt,
bytes32 initCodeHash,
address deployer
) external pure returns (address computedAddress);

function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address computedAddress);

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CREATE3 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract);

function deployCreate3(bytes memory initCode) external payable returns (address newContract);

function deployCreate3AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);

function deployCreate3AndInit(
bytes32 salt,
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);

function deployCreate3AndInit(
bytes memory initCode,
bytes memory data,
Values memory values,
address refundAddress
) external payable returns (address newContract);

function deployCreate3AndInit(
bytes memory initCode,
bytes memory data,
Values memory values
) external payable returns (address newContract);

function computeCreate3Address(bytes32 salt, address deployer) external pure returns (address computedAddress);

function computeCreate3Address(bytes32 salt) external view returns (address computedAddress);
}
Loading