Skip to content

Commit

Permalink
Merge pull request #3 from the-standard/cyfrin-audit/foundry
Browse files Browse the repository at this point in the history
Foundry test suite
  • Loading branch information
ewansheldon authored Oct 2, 2024
2 parents 2342302 + e5f2c8f commit 2f1066e
Show file tree
Hide file tree
Showing 93 changed files with 10,090 additions and 4,630 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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: Install npm dependencies
run: |
npm install
id: npmi

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

- name: Run Forge tests
run: |
forge test -vvv
id: test
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ cache
artifacts

.DS_Store
.idea/
.idea/

# crytic
crytic-export/
echidna/
medusa/

# python
venv/
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/chimera"]
path = lib/chimera
url = https://github.com/recon-fuzz/chimera
[submodule "lib/properties"]
path = lib/properties
url = https://github.com/crytic/properties
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,73 @@
# The Standard: Smart Vaults

## Installation
### Hardhat
If you have npm installed, you can install the required project dependencies
```
npm install
```

### Foundry
If you have Foundry installed, you can install the required project dependencies
```
forge install
```

## Testing
### Hardhat
If you are in root, you can run the project's full test suite
```
npx hardhat test
```
Since this project uses Hardhat, you can use its default ways to run tests, should you prefer it.

### Foundry
```
forge test
```

## Coverage
### Hardhat
Run the code coverage suite with
```
npx hardhat coverage
```

### Foundry
```
forge coverage
```

## Contract size benchmark
Run the contract sizer with
```
npx hardhat size-contracts
```

## Format
Format using the forge formatter with
```
forge fmt
```

## Gas Snapshots
Output gas usage snapshots with
```
forge snapshot
```

## Anvil
Run a local chain with
```
anvil
```

## Chisel
Open a Solidity REPL with
```
chisel
```

## Documentation
- [Testnet guide](docs/TESTNET)
- [Deployed addresses](docs/addresses.json)
24 changes: 14 additions & 10 deletions contracts/PriceCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ contract PriceCalculator is IPriceCalculator, Ownable {
error SequencerDown();
error GracePeriodNotOver();

constructor (bytes32 _native, address _USDCToUSDAddr, address _sequencerUptimeFeed) {
constructor(bytes32 _native, address _USDCToUSDAddr, address _sequencerUptimeFeed) {
NATIVE = _native;
USDCToUSDAddr = _USDCToUSDAddr;
sequencerUptimeFeed = Chainlink.AggregatorV3Interface(_sequencerUptimeFeed);
}

function validateSequencerUp() private view {
(,int256 answer,uint256 startedAt,,) = sequencerUptimeFeed.latestRoundData();
(, int256 answer, uint256 startedAt,,) = sequencerUptimeFeed.latestRoundData();
bool isSequencerUp = answer == 0;
if (!isSequencerUp) {
revert SequencerDown();
Expand All @@ -38,7 +38,11 @@ contract PriceCalculator is IPriceCalculator, Ownable {
}
}

function overscaledCollateral(ITokenManager.Token memory _token, uint256 _tokenValue) private view returns (uint256 _scaledValue) {
function overscaledCollateral(ITokenManager.Token memory _token, uint256 _tokenValue)
private
view
returns (uint256 _scaledValue)
{
uint8 _dec = _token.symbol == NATIVE ? 18 : ERC20(_token.addr).decimals();
return _tokenValue * 10 ** (36 - _dec);
}
Expand All @@ -50,27 +54,27 @@ contract PriceCalculator is IPriceCalculator, Ownable {

function validateData(uint80 _roundId, int256 _answer, uint256 _updatedAt, address _dataFeed) private view {
validateSequencerUp();
if(_roundId == 0) revert InvalidRoundId();
if(_answer == 0) revert InvalidPrice();
if(_updatedAt == 0 || _updatedAt > block.timestamp) revert InvalidUpdate();
if(block.timestamp - _updatedAt > getTimeout(_dataFeed)) revert StalePrice();
if (_roundId == 0) revert InvalidRoundId();
if (_answer == 0) revert InvalidPrice();
if (_updatedAt == 0 || _updatedAt > block.timestamp) revert InvalidUpdate();
if (block.timestamp - _updatedAt > getTimeout(_dataFeed)) revert StalePrice();
}

function tokenToUSD(ITokenManager.Token memory _token, uint256 _tokenValue) external view returns (uint256) {
Chainlink.AggregatorV3Interface tokenUsdClFeed = Chainlink.AggregatorV3Interface(_token.clAddr);
(uint80 _roundId, int256 _tokenUsdPrice, , uint256 _updatedAt, ) = tokenUsdClFeed.latestRoundData();
(uint80 _roundId, int256 _tokenUsdPrice,, uint256 _updatedAt,) = tokenUsdClFeed.latestRoundData();
validateData(_roundId, _tokenUsdPrice, _updatedAt, _token.clAddr);
return overscaledCollateral(_token, _tokenValue) * uint256(_tokenUsdPrice) / 10 ** _token.clDec / 1e18;
}

function USDCToUSD(uint256 _amount, uint8 _dec) external view returns (uint256) {
Chainlink.AggregatorV3Interface _clUSDCToUSD = Chainlink.AggregatorV3Interface(USDCToUSDAddr);
(uint80 _roundId, int256 _USDCToUSDPrice, , uint256 _updatedAt, ) = _clUSDCToUSD.latestRoundData();
(uint80 _roundId, int256 _USDCToUSDPrice,, uint256 _updatedAt,) = _clUSDCToUSD.latestRoundData();
validateData(_roundId, _USDCToUSDPrice, _updatedAt, USDCToUSDAddr);
return _amount * uint256(_USDCToUSDPrice) * 10 ** (18 - _dec) / 10 ** _clUSDCToUSD.decimals();
}

function setDataFeedTimeout(address _dataFeed, uint256 _timeout) external onlyOwner {
dataFeedTimeouts[_dataFeed] = _timeout;
}
}
}
4 changes: 2 additions & 2 deletions contracts/SmartVaultDeployerV4.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import "contracts/SmartVaultV4.sol";
import "contracts/PriceCalculator.sol";
import "contracts/interfaces/ISmartVaultDeployer.sol";

contract SmartVaultDeployerV4 is ISmartVaultDeployer {
contract SmartVaultDeployerV4 is ISmartVaultDeployer {
bytes32 private immutable NATIVE;
address private immutable priceCalculator;

constructor(bytes32 _native, address _priceCalculator) {
NATIVE = _native;
priceCalculator = _priceCalculator;
}

function deploy(address _manager, address _owner, address _usds) external returns (address) {
return address(new SmartVaultV4(NATIVE, _manager, _owner, _usds, priceCalculator));
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/SmartVaultIndex.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ contract SmartVaultIndex is ISmartVaultIndex, Ownable {
mapping(address => uint256[]) private tokenIds;
mapping(uint256 => address payable) private vaultAddresses;

modifier onlyManager {
modifier onlyManager() {
require(msg.sender == manager, "err-unauthorised");
_;
}
Expand Down
61 changes: 33 additions & 28 deletions contracts/SmartVaultManagerV6.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ import "contracts/interfaces/IUSDs.sol";
// TODO describe changes
// TODO upgraded zz/zz/zz
//
contract SmartVaultManagerV6 is ISmartVaultManager, ISmartVaultManagerV2, Initializable, ERC721Upgradeable, OwnableUpgradeable {
contract SmartVaultManagerV6 is
ISmartVaultManager,
ISmartVaultManagerV2,
Initializable,
ERC721Upgradeable,
OwnableUpgradeable
{
using SafeERC20 for IERC20;

uint256 public constant HUNDRED_PC = 1e5;

address public protocol;
address public liquidator;
address public usds;
uint256 public collateralRate;
address public tokenManager;
Expand All @@ -43,15 +48,25 @@ contract SmartVaultManagerV6 is ISmartVaultManager, ISmartVaultManagerV2, Initia
event VaultLiquidated(address indexed vaultAddress);
event VaultTransferred(uint256 indexed tokenId, address from, address to);

struct SmartVaultData {
uint256 tokenId; uint256 collateralRate; uint256 mintFeeRate;
uint256 burnFeeRate; ISmartVault.Status status;
struct SmartVaultData {
uint256 tokenId;
uint256 collateralRate;
uint256 mintFeeRate;
uint256 burnFeeRate;
ISmartVault.Status status;
}

function initialize(
uint256 _collateralRate, uint256 _feeRate, address _usds, address _protocol, address _liquidator, address _tokenManager,
address _smartVaultDeployer, address _smartVaultIndex, address _nftMetadataGenerator, uint16 _userVaultLimit
) initializer public {
uint256 _collateralRate,
uint256 _feeRate,
address _usds,
address _protocol,
address _tokenManager,
address _smartVaultDeployer,
address _smartVaultIndex,
address _nftMetadataGenerator,
uint16 _userVaultLimit
) public initializer {
__ERC721_init("The Standard Smart Vault Manager (USDs)", "TS-VAULTMAN-USDs");
__Ownable_init();
collateralRate = _collateralRate;
Expand All @@ -60,19 +75,13 @@ contract SmartVaultManagerV6 is ISmartVaultManager, ISmartVaultManagerV2, Initia
burnFeeRate = _feeRate;
swapFeeRate = _feeRate;
protocol = _protocol;
liquidator = _liquidator;
tokenManager = _tokenManager;
smartVaultDeployer = _smartVaultDeployer;
smartVaultIndex = ISmartVaultIndex(_smartVaultIndex);
nftMetadataGenerator = _nftMetadataGenerator;
userVaultLimit = _userVaultLimit;
}

modifier onlyLiquidator {
require(msg.sender == liquidator, "err-invalid-liquidator");
_;
}

function vaultIDs(address _holder) public view returns (uint256[] memory) {
return smartVaultIndex.getTokenIds(_holder);
}
Expand Down Expand Up @@ -123,42 +132,38 @@ contract SmartVaultManagerV6 is ISmartVaultManager, ISmartVaultManagerV2, Initia
}

function setBurnFeeRate(uint256 _rate) external onlyOwner {
burnFeeRate = _rate;
burnFeeRate = _rate;
}

function setSwapFeeRate(uint256 _rate) external onlyOwner {
swapFeeRate = _rate;
}

function setWethAddress(address _weth) external onlyOwner() {
function setWethAddress(address _weth) external onlyOwner {
weth = _weth;
}

function setSwapRouter(address _swapRouter) external onlyOwner() {
function setSwapRouter(address _swapRouter) external onlyOwner {
swapRouter = _swapRouter;
}

function setNFTMetadataGenerator(address _nftMetadataGenerator) external onlyOwner() {
function setNFTMetadataGenerator(address _nftMetadataGenerator) external onlyOwner {
nftMetadataGenerator = _nftMetadataGenerator;
}

function setSmartVaultDeployer(address _smartVaultDeployer) external onlyOwner() {
function setSmartVaultDeployer(address _smartVaultDeployer) external onlyOwner {
smartVaultDeployer = _smartVaultDeployer;
}

function setProtocolAddress(address _protocol) external onlyOwner() {
function setProtocolAddress(address _protocol) external onlyOwner {
protocol = _protocol;
}

function setLiquidatorAddress(address _liquidator) external onlyOwner() {
liquidator = _liquidator;
}

function setUserVaultLimit(uint16 _userVaultLimit) external onlyOwner() {
function setUserVaultLimit(uint16 _userVaultLimit) external onlyOwner {
userVaultLimit = _userVaultLimit;
}

function setYieldManager(address _yieldManager) external onlyOwner() {
function setYieldManager(address _yieldManager) external onlyOwner {
yieldManager = _yieldManager;
}

Expand All @@ -169,4 +174,4 @@ contract SmartVaultManagerV6 is ISmartVaultManager, ISmartVaultManagerV2, Initia
if (address(_from) != address(0)) ISmartVault(smartVaultIndex.getVaultAddress(_tokenId)).setOwner(_to);
emit VaultTransferred(_tokenId, _from, _to);
}
}
}
Loading

0 comments on commit 2f1066e

Please sign in to comment.