- Total Prize Pool:
- HM Awards: $17,500
- Low Awards: $2,500
- Starts - December 27, 2023 Noon UTC
- Ends - January 10, 2024 Noon UTC
- nSLOC: 609
- Complexity Score: 698
- Dollars per Complexity: $28.65
- Dollars per nSLOC: $32.84
Secure your crypto assets, such as ETH, WBTC, ARB, LINK, & PAXG tokenized gold, in smart contracts that you control and no one else, then effortlessly borrow stablecoins with 0% interest loans and no time limit to pay back.
-
Borrowers: users creating Smart Vaults, depositing their collateral, borrowing EUROs stablecoins against it
-
Smart Vault Manager: contract managing vault deployments, controls admin data which dictates behaviour of Smart Vaults e.g. fee rates, collateral rates, dependency addresses, managed by The Standard
-
Stakers: users adding TST and/or EUROs to the Liquidation Pool, in order to gain rewards from borrowing fees and vault liquidations
-
Liquidation Pool Manager: contract managing liquidations and distribution of borrowing fees in the pool
All contracts at commit 7c9f84772eacb588c00a2add9f46aa93211a7132
- [SmartVaultV3]
- [SmartVaultManagerV5]
- [LiquidationPool]
- [LiquidationPoolManager]
The live version of these contracts (deployed to Arbitrum One) have some key external dependencies:
- WBTC
- ARB
- LINK
- PAXG
- Chainlink ETH / USD feed
- Chainlink WBTC / USD feed
- Chainlink ARB / USD feed
- Chainlink LINK / USD feed
- Chainlink PAXG / USD feed
- Chainlink EUR / USD feed
- Uniswap V3 Swap Router
- The Standard EURO
- The Standard Token
As well as administrative dependencies managed by us:
- A Token Manager, storing data about which tokens are accepted Smart Vault Collateral
- A Deployer, facilitating user creations of Smart Vaults
- An Index, storing vault owners' token IDs and vault addresses
- A Price Calculator, using Chainlink data feeds to calculate price conversions for vaults
- An NFT Metadata Generator, producing token URI metadata based on current vault state
For this test environment, collateral tokens have been replaced by a test ERC20 token. EUROs and TST have been replaced with mock versions. Chainlink feeds have been replaced by static price feeds. Uniswap swaps have been stubbed by a contract stub.
The administrative dependencies are managed by The Standard and are therefore not within the scope of this audit. They are replicated in the test environment.
Compatibilities:
Blockchains:
- Any EVM chains with live Chainlink data feeds and live Uniswap pools
Tokens:
- ETH
This project uses Hardhat.
To install the dependencies:
npm install
To run the test suite:
npx hardhat test
To start the default test environment, you can start a local Hardhat node:
npx hardhat node
And use the deploy script to build the environment on the running node:
npx hardhat run --network localhost scripts/deploy.js
You should see an output similar to:
SmartVaultManager: 0x...
LiquidationPoolManager: 0x...
LiquidationPool: 0x...
User 0x... has balance 9999.923234... ETH
User 0x... minted with 1000 test TST
User 0x... minted with 1000 test EUROs
User 0x... minted with 1000 test USDs
Use these addresses to interact with your locally deployed contracts.
SmartVaultManagerV5
- This is version 5 of an OpenZeppelin upgradeable contract. That is why there is no constructor setting the initial state variables
_safeMint
is a reentrancy risk, however this is mitigated by the fact that the contract will try to mint the same token ID again, and reverting- Dependencies on our administrative SmartVaultIndex, SmartVaultDeployer, NFTMetadataGenerator contracts
vaults
function array length is unchecked going into for loop. An abuse of NFT minting by a user could prevent them from being able to use thisvaults
functionmint
function is dependent on our SmartVaultDeployer and SmartVaultIndex contracts, however we have the administrative control over setting these addresses- Very important access control roles are granted to the Smart Vaults (
MINTER_ROLE
,BURNER_ROLE
), giving these contracts strong permissions over the supply of EUROs, however this is a necessary key feature in our project, as users must be able to borrow through their vaults. As such the SmartVaultManager must also have a admin role in EUROs access control - If a Smart Vault's
undercollateralised
function reverts, it cannot be liquidated - No zero address check for administrative functions
setWethAddress
,setSwapRouter2
,setNFTMetadataGenerator
,setSmartVaultDeployer
,setProtocolAddress
,setLiquidatorAddress
. However we have benefited from this. This can allow the blocking of certain Smart Vault features. e.g. we were previously able to block a vulnerability in the Smart Vault feature by setting the Swap Router to a zero address
SmartVaultV3
- Dependencies on our administrative contracts SmartVaultManager and PriceCalculator, but these addresses are set by our contracts on deployment. This is also the case with the EUROs contract. These addresses could be set to anything on deployment, but this Smart Vault contract only provides value to a user if it has EUROs minting and burning permissions, which are only granted when deployed via SmartVaultManager's
mint
- Length of accepted tokens array throughout contract is unchecked, but this Token Manager contract is managed by us and there is unlikely to be more than 5-10 tokens in this array
- Throughout contract, dependent on an accurate prices being produced by PriceCalculator
tokenToEurAvg
,tokenToEur
,eurToToken
functions, but this contract is managed by us, and uses Chainlink data feeds for reliable price data maxMintable
function will revert if SmartVaultManagercollateralRate
is 0, but this value is controlled by us and should never begetAssetBalance
function will revert if the token provided has an incorrect combination of symbol bytes array and token address, however this data is managed by our TokenManager contract- Contract requires the
protocol
address to be a payable address. This address will be set to the LiquidationPoolManager, which has areceive
function - Also requires
protocol
to be able to access the ERC20s sent. LiquidationPoolManager uses the same TokenManager list to handle assets. liquidateERC20
is dependent on the token addresses provided by our TokenManager being correctmint
function requires SmartVaultManager'sHUNDRED_PC
to not be 0, but the value is a constantswap
is dependent on Uniswap V3 Swap Router. The address must be correct for this swap to be safely completed. This swap router address is controlled by our administrative SmartVaultManager contractminimumAmountOut
can be set to 0 if there is no value required to keep collateral above required level. The user is likely to lose some value in the swap, especially when Uniswap fees are factored in, but this is at the user's discretionsetOwner
can change control of the vault, but this can only be completed by the SmartVaultManager contract, and is only called when completing an NFT transfer
LiquidationPoolManager
- Length of accepted tokens array is unchecked, but this TokenManager contract is managed by us and there is unlikely to be more than 5-10 tokens in this array
protocol
address must be payable, and able to handle ERC20s transferred. This address will be set to our Protocol's treasury wallet.
LiquidationPool
- No length check for number of stake
holders
. This could cause a problem throughout contract if there are a high number of stakers - TokenManager
getAcceptedTokens
array length unchecked, but uses an administrative contract which is managed by us. Unlikely to be more than 5-10 items position
function depends ongetTstTotal
not being 0. However, if current position has TST, thenTSTtotal
will never be 0distributeFees
function requires an approval of EUROs beforehand, but LiquidationPoolManager approves the amount before calling the functiondistributeAssets
function requiresstakeTotal
to be greater than 0, but this will always be the case if any_positionStake > 0
- Function is also dependent on Chainlink EUR / USD providing a price greater than 0
- Also dependent on accurate {Token} / USD prices being accurate, and greater than 0
- Dependent on
collateralRate
being greater than 0. This value is managed in our administrative SmartVaultManager contract, and the project is dependent on that value being correct LiquidationPool
requires EUROsBURNER_ROLE
permission, but this is an important function of the Liquidation Pool
Additional Issues
- Issues caught by Aderyn here