Skip to content
Open
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
1 change: 1 addition & 0 deletions .example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/demo
90 changes: 45 additions & 45 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Show Forge version
run: |
forge --version

- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
name: CI
on:
push:
pull_request:
workflow_dispatch:
env:
FOUNDRY_PROFILE: ci
jobs:
check:
strategy:
fail-fast: true
name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
- name: Show Forge version
run: |
forge --version
- name: Run Forge fmt
run: |
forge fmt --check
id: fmt
- name: Run Forge build
run: |
forge build --sizes
id: build
- name: Run Forge tests
run: |
forge test -vvv
id: test
28 changes: 14 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
# Compiler files
cache/
out/
# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/
# Docs
docs/
# Dotenv file
.env
12 changes: 6 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
### MicroStable

Most simple design of a stablecoin.
### MicroStable
Most simple design of a stablecoin.
12 changes: 6 additions & 6 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
159 changes: 84 additions & 75 deletions src/MicroStable.sol
Original file line number Diff line number Diff line change
@@ -1,75 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

interface Oracle { function latestAnswer() external view returns (uint); }

contract ShUSD is ERC20("Shafu USD", "shUSD", 18) {
address public manager;

constructor(address _manager) { manager = _manager; }

modifier onlyManager() {
require(manager == msg.sender);
_;
}

function mint(address to, uint amount) public onlyManager { _mint(to, amount); }
function burn(address from, uint amount) public onlyManager { _burn(from, amount); }
}

contract Manager {
uint public constant MIN_COLLAT_RATIO = 1.5e18;

ERC20 public weth;
ShUSD public shUSD;

Oracle public oracle;

mapping(address => uint) public address2deposit;
mapping(address => uint) public address2minted;

constructor(address _weth, address _shUSD, address _oracle) {
weth = ERC20(_weth);
shUSD = ShUSD(_shUSD);
oracle = Oracle(_oracle);
}

function deposit(uint amount) public {
weth.transferFrom(msg.sender, address(this), amount);
address2deposit[msg.sender] += amount;
}

function burn(uint amount) public {
address2minted[msg.sender] -= amount;
shUSD.burn(msg.sender, amount);
}

function mint(uint amount) public {
address2minted[msg.sender] += amount;
require(collatRatio(msg.sender) >= MIN_COLLAT_RATIO);
shUSD.mint(msg.sender, amount);
}

function withdraw(uint amount) public {
address2deposit[msg.sender] -= amount;
require(collatRatio(msg.sender) >= MIN_COLLAT_RATIO);
weth.transfer(msg.sender, amount);
}

function liquidate(address user) public {
require(collatRatio(user) < MIN_COLLAT_RATIO);
shUSD.burn(msg.sender, address2minted[user]);
weth.transfer(msg.sender, address2deposit[user]);
address2deposit[user] = 0;
address2minted[user] = 0;
}

function collatRatio(address user) public view returns (uint) {
uint minted = address2minted[user];
if (minted == 0) return type(uint256).max;
uint totalValue = address2deposit[user] * oracle.latestAnswer() / 1e18;
return totalValue / minted;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {ERC20} from "lib/solmate/src/tokens/ERC20.sol";

interface Oracle {
function latestAnswer() external view returns (uint256);
}

contract ShUSD is ERC20("Shafu USD", "shUSD", 18) {
address public manager;

constructor(address _manager) {
manager = _manager;
}

modifier onlyManager() {
require(manager == msg.sender);
_;
}

function mint(address to, uint256 amount) public onlyManager {
_mint(to, amount);
}

function burn(address from, uint256 amount) public onlyManager {
_burn(from, amount);
}
}

contract Manager {
uint256 public constant MIN_COLLAT_RATIO = 1.5e18;

ERC20 public weth;
ShUSD public shUSD;

Oracle public oracle;

mapping(address => uint256) public address2deposit;
mapping(address => uint256) public address2minted;

constructor(address _weth, address _shUSD, address _oracle) {
weth = ERC20(_weth);
shUSD = ShUSD(_shUSD);
oracle = Oracle(_oracle);
}

function deposit(uint256 amount) public {
weth.transferFrom(msg.sender, address(this), amount);
address2deposit[msg.sender] += amount;
}

function burn(uint256 amount) public {
address2minted[msg.sender] -= amount;
shUSD.burn(msg.sender, amount);
}

function mint(uint256 amount) public {
require(collatRatio(msg.sender) >= MIN_COLLAT_RATIO);
address2minted[msg.sender] += amount;
shUSD.mint(msg.sender, amount);
}

function withdraw(uint256 amount) public {
address2deposit[msg.sender] -= amount;
require(collatRatio(msg.sender) >= MIN_COLLAT_RATIO);
weth.transfer(msg.sender, amount);
}

function liquidate(address user) public {
require(collatRatio(user) < MIN_COLLAT_RATIO);
shUSD.burn(msg.sender, address2minted[user]);
weth.transfer(msg.sender, address2deposit[user]);
address2deposit[user] = 0;
address2minted[user] = 0;
}

function collatRatio(address user) public view returns (uint256) {
uint256 minted = address2minted[user];
if (minted == 0) return type(uint256).max;
uint256 totalValue = address2deposit[user] * (oracle.latestAnswer() * 1e10) / 1e18;
return totalValue / minted;
}
}
Loading