-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Liquidity Hub #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
556d896
Add basic LiquidityHub
lastperson 7eeb197
Add tests
lastperson 2116b6a
Update actions/cache
lastperson e530b7d
TS-ify
lastperson 8348919
Fix types
lastperson 6511a98
Fix lint job
lastperson 91c5950
Fix lint
lastperson 95d0694
Add deploy script
lastperson 2313a48
Fix lint
lastperson 894c808
Add usage instructions
lastperson d19fa60
Cleanup github workflows
lastperson dd16fbc
Add local dev info to Readme
lastperson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Contracts admin. | ||
| ADMIN= | ||
| # USDC token address. Leave empty to deploy a test one instead. | ||
| USDC= | ||
| BASETEST_PRIVATE_KEY= | ||
| VERIFY=false |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,25 +7,30 @@ jobs: | |
| name: Lint Solidity | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/[email protected] | ||
| - uses: actions/[email protected] | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Install Node.js 22 | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: [22.x] | ||
|
|
||
| - run: npm ci | ||
|
|
||
| node-version: 22 | ||
| cache: 'npm' | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Run linter | ||
| run: npm run lint:solidity | ||
|
|
||
| lint-ts: | ||
| name: Lint TS | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/[email protected] | ||
| - uses: actions/[email protected] | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| - name: Install Node.js 22 | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: [22.x] | ||
|
|
||
| - run: npm ci | ||
| node-version: 22 | ||
| cache: 'npm' | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Run linter | ||
| run: npm run lint:ts | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,25 +8,19 @@ jobs: | |
| test: | ||
| name: Test | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| node-version: [22.x] | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/[email protected] | ||
| - uses: actions/[email protected] | ||
| uses: actions/checkout@v4 | ||
| - name: Install Node.js 22 | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| path: ~/.npm | ||
| key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-node- | ||
| - name: Install Node.js ${{ matrix.node-version }} | ||
| uses: actions/[email protected] | ||
| with: | ||
| node-version: ${{ matrix.node-version }} | ||
| node-version: 22 | ||
| cache: 'npm' | ||
| - name: Install dependencies | ||
| run: npm ci | ||
| - name: Compile contracts | ||
| run: npm run compile | ||
| - name: Hardhat Tests | ||
| run: npm run test | ||
| - name: Deploy Tests | ||
| run: npm run test:deploy | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,33 @@ | ||
| # sprinter-liquidity-contracts | ||
| # sprinter-liquidity-contracts | ||
|
|
||
| Solidity contracts that facilitate Sprinter Liquidity logic | ||
|
|
||
| ### Install | ||
|
|
||
| node 22.x is required | ||
| nvm use | ||
| npm install | ||
| npm run compile | ||
|
|
||
| ### Test | ||
|
|
||
| npm run test | ||
|
|
||
| ### Deployment | ||
|
|
||
| For local development you need to run a local hardhat node and deploy to it: | ||
|
|
||
| npm run node | ||
| npm run deploy-local | ||
|
|
||
| Local deployment wallet private key is: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | ||
|
|
||
| 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. | ||
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import { TransparentUpgradeableProxy } from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import { | ||
| IERC20, | ||
| IERC20Metadata, | ||
| ERC20Upgradeable, | ||
| ERC4626Upgradeable, | ||
| Math | ||
| } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; | ||
| import {AccessControlUpgradeable} from '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol'; | ||
| import {ERC7201Helper} from './utils/ERC7201Helper.sol'; | ||
| import {IManagedToken} from './interfaces/IManagedToken.sol'; | ||
|
|
||
| contract LiquidityHub is ERC4626Upgradeable, AccessControlUpgradeable { | ||
| using Math for uint256; | ||
|
|
||
| IManagedToken immutable public SHARES; | ||
| bytes32 public constant ASSETS_UPDATE_ROLE = "ASSETS_UPDATE_ROLE"; | ||
|
|
||
| error ZeroAddress(); | ||
| error NotImplemented(); | ||
| error IncompatibleAssetsAndShares(); | ||
|
|
||
| /// @custom:storage-location erc7201:sprinter.storage.LiquidityHub | ||
| struct LiquidityHubStorage { | ||
| uint256 totalAssets; | ||
| } | ||
|
|
||
| bytes32 private constant StorageLocation = 0xb877bfaae1674461dd1960c90f24075e3de3265a91f6906fe128ab8da6ba1700; | ||
|
|
||
| constructor(address shares) { | ||
| ERC7201Helper.validateStorageLocation( | ||
| StorageLocation, | ||
| 'sprinter.storage.LiquidityHub' | ||
| ); | ||
| if (shares == address(0)) revert ZeroAddress(); | ||
| SHARES = IManagedToken(shares); | ||
| _disableInitializers(); | ||
| } | ||
|
|
||
| function initialize(IERC20 asset_, address admin) external initializer() { | ||
| ERC4626Upgradeable.__ERC4626_init(asset_); | ||
| require( | ||
| IERC20Metadata(address(asset_)).decimals() <= IERC20Metadata(address(SHARES)).decimals(), | ||
| IncompatibleAssetsAndShares() | ||
| ); | ||
| // Deliberately not initializing ERC20Upgradable because its | ||
| // functionality is delegated to SHARES. | ||
| _grantRole(DEFAULT_ADMIN_ROLE, admin); | ||
| } | ||
|
|
||
| function name() public pure override(IERC20Metadata, ERC20Upgradeable) returns (string memory) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function symbol() public pure override(IERC20Metadata, ERC20Upgradeable) returns (string memory) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function decimals() public pure override returns (uint8) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function totalSupply() public view virtual override(IERC20, ERC20Upgradeable) returns (uint256) { | ||
| return IERC20(address(SHARES)).totalSupply(); | ||
| } | ||
|
|
||
| function balanceOf(address owner) public view virtual override(IERC20, ERC20Upgradeable) returns (uint256) { | ||
| return IERC20(address(SHARES)).balanceOf(owner); | ||
| } | ||
|
|
||
| function transfer(address, uint256) public pure override(IERC20, ERC20Upgradeable) returns (bool) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function allowance(address, address) public pure override(IERC20, ERC20Upgradeable) returns (uint256) { | ||
| // Silences the unreachable code warning from ERC20Upgradeable._spendAllowance(). | ||
| return 0; | ||
| } | ||
|
|
||
| function approve(address, uint256) public pure override(IERC20, ERC20Upgradeable) returns (bool) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function transferFrom(address, address, uint256) public pure override(IERC20, ERC20Upgradeable) returns (bool) { | ||
| revert NotImplemented(); | ||
| } | ||
|
|
||
| function totalAssets() public view virtual override returns (uint256) { | ||
| return _getStorage().totalAssets; | ||
| } | ||
|
|
||
| function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual override returns (uint256) { | ||
| (uint256 supplyShares, uint256 supplyAssets) = _getTotals(); | ||
| return assets.mulDiv(supplyShares, supplyAssets, rounding); | ||
| } | ||
|
|
||
| function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual override returns (uint256) { | ||
| (uint256 supplyShares, uint256 supplyAssets) = _getTotals(); | ||
| return shares.mulDiv(supplyAssets, supplyShares, rounding); | ||
| } | ||
|
|
||
| function _getTotals() internal view returns (uint256 supply, uint256 assets) { | ||
| supply = totalSupply(); | ||
| if (supply == 0) { | ||
| supply = 10 ** _decimalsOffset(); | ||
| } | ||
| assets = totalAssets(); | ||
| if (assets == 0) { | ||
| assets = 1; | ||
| } | ||
| return (supply, assets); | ||
| } | ||
|
|
||
| function _update(address from, address to, uint256 value) internal virtual override { | ||
| if (from == address(0)) { | ||
| SHARES.mint(to, value); | ||
| } else if (to == address(0)) { | ||
| SHARES.burn(from, value); | ||
| } else { | ||
| revert NotImplemented(); | ||
| } | ||
| } | ||
|
|
||
| function _spendAllowance(address owner, address spender, uint256 value) internal virtual override { | ||
| SHARES.spendAllowance(owner, spender, value); | ||
| } | ||
|
|
||
| function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { | ||
| super._deposit(caller, receiver, assets, shares); | ||
| _getStorage().totalAssets += assets; | ||
| } | ||
|
|
||
| function _withdraw( | ||
| address caller, | ||
| address receiver, | ||
| address owner, | ||
| uint256 assets, | ||
| uint256 shares | ||
| ) internal virtual override { | ||
| _getStorage().totalAssets -= assets; | ||
| super._withdraw(caller, receiver, owner, assets, shares); | ||
| } | ||
|
|
||
| function _decimalsOffset() internal view virtual override returns (uint8) { | ||
| return IERC20Metadata(address(SHARES)).decimals() - IERC20Metadata(asset()).decimals(); | ||
| } | ||
|
|
||
| function _getStorage() private pure returns (LiquidityHubStorage storage $) { | ||
| assembly { | ||
| $.slot := StorageLocation | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity 0.8.28; | ||
|
|
||
| interface IManagedToken { | ||
| function MANAGER() external view returns (address); | ||
|
|
||
| function mint(address to, uint256 amount) external; | ||
|
|
||
| function burn(address from, uint256 amount) external; | ||
|
|
||
| function spendAllowance(address owner, address spender, uint256 value) external; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity 0.8.28; | ||
|
|
||
| import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
|
|
||
| contract TestUSDC is ERC20 { | ||
| constructor() ERC20("Circle USD", "USDC") { | ||
| _mint(msg.sender, 1000 * 10 ** decimals()); | ||
| } | ||
|
|
||
| function decimals() public pure override returns (uint8) { | ||
| return 6; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
| pragma solidity 0.8.28; | ||
|
|
||
| library ERC7201Helper { | ||
| error InvalidStorageSlot(string namespace); | ||
|
|
||
| function validateStorageLocation(bytes32 actual, string memory namespace) internal pure { | ||
| bytes32 expected = getStorageLocation(bytes(namespace)); | ||
| require(actual == expected, InvalidStorageSlot(namespace)); | ||
| } | ||
|
|
||
| function getStorageLocation(bytes memory namespace) internal pure returns(bytes32) { | ||
| return keccak256(abi.encode(uint256(keccak256(namespace)) - 1)) & ~bytes32(uint256(0xff)); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.