SpartanDev: 02/08/21 - 08/08/21
First week of August; addition of caps, global freeze and other new protocol safety features
Summary
With the codeArena feedback triaged, contributors have been working away on discussions, scenarios and implementing changes from warden-feedback across the SpartanProtocol ecosystem.
The iceberg analogy applies really well to this type of project. A lot of us only see the front end UI or the result of code that derives from hours of brainstorming, debate and war gaming. There is a lot of back end testing conducted to ensure improvements work and break nothing else. *round of applause for their efforts*
SpartanProtocol is really looking forward to unleashing the updated V2 code to testnet for the shield wall to assist with testing soon.
It's time to upgrade your tokens
No problems; upgrade today
You can use the Spartan Protocol Upgrade DApp to upgrade your SPARTAv1 tokens to SPARTAv2 yourself.
Upgrade DApp — https://dapp.spartanprotocol.org/upgrade
Bit-Rush Crypto has created a video guide on using the SpartanProtocol Upgrade DApp — timestamp 3:20. Nice work SPARTANS!!
feeBurn Update
Spartan Socials — Twitter
Top Impressions:
Top Engagement:
Top Community Mention:
SpartanSocials — Medium
SpartanSocials — Telegram
Contributor’s Focus
It has been a busy few months and devoted community members will have seen the contributors working hard through the previously listed focus points.
Now that the codeArena audit is complete, it seems a suitable time to clear completed items off the checklist. Remove the noise; enjoy.
CodeArena Contest
- COMPLETED — triage and prioritise the feedback submitted from the CodeArena wardens during the contest to prepare for the judges
- IN PROGRESS — work through the post-contest tasks with the C4 judges & team for the eventual allocation of awards to security wardens
- IN PROGRESS — communicate with security wardens to clarify/expand on feedback
SPARTA V2 (Token)
- COMPLETED & ONGOING — Work with DEXs & aggregators to ensure up-to-date information on the new SPARTA token (retiring the previous contract address) (1inch, PancakeSwap etc)
- COMPLETED & ONGOING — Work with token-tracking informational websites to ensure new token info is up-to-date (BSCscan, CoinGecko, CoinMarketCap etc)
SpartanContracts
- COMPLETED — Sort and prioritise all CodeArena submissions into contract scopes along with tags based on ‘actionable’ or ‘discussion points’
- IN PROGRESS — Implementing refinements to contracts to address C4 & contributor feedback since the C4 contest code-freeze
- Deploy updated V2 contracts to BSC testnet for at least 1 week of community stress testing
- Deploy completed V2 contracts to BSC mainnet
- COMPLETED & ONGOING — Continue the code review process within the community
DAppV2
- COMPLETED & ONGOING — Set up a reliable index of history scoped to contracts (use this for positions page etc)
- IN PROGRESS — Use the new testnet subgraph to build a more lightweight positions page for V2
After Mainnet
- Enable Bond allocations to replenish TVL into the V2 pools
- March onwards with our original goals of building the decentralised, yield-generating synthetics protocol on Binance Smart Chain
GitHub Activity — SpartanContractsV2
contracts/BondVault.sol
- Refactor increaseWeight() & decreaseWeight()
- Rename increaseWeight() to updateWeight()
- Remove decreaseWeight() function
- Update all instances of increaseWeight() and decreaseWeight() to updateWeight()
2. claimForMember() — added require to check if pool address is valid
contracts/Dao.sol
Added require() checks to ensure proposal-related functions revert if there is a globalFreeze in place:
- checkProposal()
- voteProposal()
- finaliseProposal()
contracts/DaoVault.sol
Added require() check to ensure no-one can increase their weight in the DAOVault when there is a globalFreeze in place:
- increaseWeight()
contracts/Pool.sol
- onlyROUTER() modifier — renamed to onlyPROTOCOL() and added in the synthVault to the permissions
- Added globalFreeze feature:
- added ‘oldRate’ variable
- added ‘period’ variable
- added ‘SPR’ variable; set at 30% by default (final details TBD)
- added ‘freeze’ bool variable
3. Added synthCap feature:
- added ‘poolCAP’ variable; set at 30% by default (final details TBD)
4. Added liquidityCap feature:
- added ‘baseCAP’ variable; set at 100,000 SPARTA by default (final details TBD)
5. Added onlySYNTH() modifier to permission functions only to the exact synth contract ontop of the relevant pool
6. _transfer() — block address(0) as the recipient on normal LP token transfers (ensure user uses the burn() function instead)
7. burn() — permissioned to onlySYNTH to ensure the external burn function can only be called by the specific synth contract ontop of the pool
8. Changed function permissions to onlyPROTOCOL:
- addForMember()
- removeForMember()
- swapTo()
- mintSynth()
- burnSynth()
9. addForMember() — added require() to check that the liquidity being added doesn't push the pool’s TVL to be above the maximum caps (V2 pools will have a cap on TVL that will be raised with community consensus, similar to Thorchain’s “raise-the-caps” approach)
10. addForMember() — Pool Rounding/Drain Protection Feature:
- This safety feature has been added into the conditional: If a pool is empty on one of the sides (SPARTA and/or TOKEN depth = 0)
- If the above is true; this theoretically will only be when someone creates a pool using the poolFactory function; in other words; the first person to add liquidity to the pool
- This ‘initial liquidity provider’ will pay a 1% ‘fee’ in LP units to the SPARTAv2 token contract, removing them from the supply permanently
- This means that someone can never fully drain the pool on either side via intended means; protecting the pools from 1-wei rounding attacks and infinite ratio bugs
- There was talk of using a standard dead/burn address for this; however, it instead makes sense to use a relevant contract to the protocol for accounting/transparency purposes; LP tokens sent to the SPARTAv2 token contract address will probably be the only tokens sent there and will mean it’s easier to verify the qty of each pool’s removed-supply
11. mintSynth() function changes:
- Added require() to ensure minting a synth doesn’t push the pool’s depth over the current protocol-enforce TVL caps (see above)
- Added require() to enforce a synthCap to prevent synths becoming top-heavy above the pools
12. Virtualized the pool’s depth to remove the synthSupply from the calculated tokenDepth:
- mintSynth()
- burnSynth()
- _swapBaseToToken()
- _swapTokenToBase()
13. Added safetyCheck() function to trigger a ‘freeze’ status if pools appear to have been recently manipulated:
- Get the pool’s ratio
- Compared to the 1hr pegged ‘previous pool ratio’
- If the ratio has changed by an unsafe percentage; trigger a ‘freeze’ which will suspend all suspected value-extraction attack vectors (synths, vaults, weights, harvest, proposals etc)
- Users will still be able to perform normal pool functions even in freeze status (swap, add liquidity, remove liquidity etc)
14. Call safetyCheck() whenever pool balances are adjusted:
- _incrementPoolBalances()
- _decrementPoolBalances()
15. Optimise gas costs of the archiveRevenue() function
16. Added setter functions for the new safety features:
- setCAP() — set new synth cap measured in tokenDepth
- RTC() — set new TVL/pool depth cap; maximum of double at a time
- setSPR() — set the safe-ratio used to trigger the freeze
- flipFreeze() — flip the freeze status of the pool and hand in a manually shifted period to help with entropy, predictability and therefore attack-ability of the 1hr periods
contracts/Reserve.sol
- Added globalFreeze variable (bool)
- setGlobalFreeze() — added manual setter of the globalFreeze; this will likely be made available to some sort of council of trusted contributors; pending discussion on how best to handle flipping the freeze status back
contracts/Router.sol
- added globalCAP() variable; soft cap on the synth supply vs tokenDepth of a pool (yet to be linked into the protocol properly)
- lastMonth — variable changed to ‘public’ for UI access
- addLiquidityForMember() — add a require() to ensure the pool address is valid
4. Added safetyTrigger() function:
- Ensure the handed-pool address is ‘curated’
- If the pool’s status is ‘freeze’ then set the global freeze status to true
5. Call the safetyTrigger() function whenever pool’s depths are affected:
- addLiquidityForMember()
- zapLiquidity()
- addLiquiditySingleForMember()
- removeLiquidityExact()
- removeLiquiditySingle()
- buyTo()
- sellTo()
- swapAssetToSynth()
- swapSynthToAsset()
6. zapLiquidity() changes:
- Added require() to ensure fromPool & toPool are not the same
- Added require() to ensure input is > 0
- Added require() to ensure fromPool and toPool are both valid
- Removed require() of input ≤ totalSupply
- Changed Pool.remove() to Pool.removeForMember()
7. addLiquiditySingleForMember() changes:
- Removed BNB/WBNB logic; it’s handled in _handleTransferIn()
- Added require() to ensure the pool’s address is valid
- Refactor Pool.addForMember() outside the conditionals
8. removeLiquidity() — removed require() of basisPoints ≤ 10000 as this is already required in the upstream function Utils.calcPart()
9. Added require() to ensure there is no globalFreeze in place before any value-extraction attack vectors:
- removeLiquidityExact()
- removeLiquiditySingle()
- swapAssetToSynth()
- swapSynthToAsset()
10. removeLiquidityExact() function changes:
- Added require() to ensure pool’s address is valid
- Changed Pool.remove() to Pool.removeForMember()
11. removeLiquiditySingle() function changes:
- Added require() to ensure pool’s address is valid
- Changed Pool.remove() to Pool.removeForMember()
- Fixed SPARTA transfer bug (missing logic to send all intended SPARTA to user)
- If ‘toBase’ the contract will now instead send all swapped SPARTA back to the ROUTER and then transfer the full amount to the user (extra hop but 1 final combined tsf event for easier troubleshooting)
- Change Pool.swap() to Pool.swapTo()
12. sellTo() — added require() to ensure pool address is valid
13. swapTo() function changes:
- Added require() to ensure pool address is valid
- Changed Pool.swap() to Pool.swapTo()
14. getsDividend() — added require() to bipass dividend logic if the txn that triggers it has a fee of less than 1 SPARTA (re-assess after we change dividend to be equal to the slipFee)
15. Optimize gas/compile in the _handleTransferIn() function:
- Removed the unused return
- Removed the unused ‘actual’ assignments
- Remove the startBal call & assignment
16. swapAssetToSynth() function changes:
- Refactor PoolFactory.getPool(Synth.layerOne) to Synth.Pool()
- Add require() to ensure the resulting pool address is valid
- Adjusted sellTo() to include a minAmount of 0
- Pool.mintSynth() remove the ‘toSynth’ arg; its now derived from the pool address being called from
17. swapSynthToAsset() function changes:
- Refactor PoolFactory.getPool(Synth.layerOne) to Synth.Pool()
- Add require() to ensure the resulting pool address is valid
- Pool.burnSynth() remove the ‘fromSynth’ arg; its now derived from the pool address being called from
- Change swap() to swapTo()
18. addTradeFee() — remove the loop; handle the totalTradeFees assignmenet inside the addFee() loop instead to save gas
19. addFee() — store and iterate feeArray in memory to save gas
20. Remove unused functions:
- stringToBytes()
- isEqual()
21. Add require() for input validation on setters:
- changeArrayFeeSize()
- changeMaxTrades()
- changeEraLength()
22. Created new setter functions:
- changeGlobalCap() — this will act as a cap on the amount of synths that can be minted vs the pool’s tokenDepth
- changePoolCap() — this will act as a slightly larger cap to allow synths to be minted via the synthVault after the other cap is reached
- RTC() — this will act as the limit for the pool’s TVL / spartaDepth to prevent liquidity being added past the current cap limit
contracts/Synth.sol
- Renamed LayerONE variable to TOKEN
- Removed DEPLOYER variable and assignment in constructor; wasn’t needed for anything
- Added ‘collateral’ variable
- Changed mappings to variables throughout:
- ‘mapSynth_LPBalance’ changed to new ‘collateral’ variable
- ‘mapSynth_LPDebt’ changed to pre-existing ‘totalSupply’ variable
5. Changed the way the Pool address is derived:
- Added POOL variable
- Removed _POOL() function
- Hand in pool address when synth is created and assign it during the constructor (this is a constant that should never change; pool must exist before synth, assinging it during construction makes more sense)
6. Remove onlyDAO() modifier
7. Updated onlyPool() modifier to use the POOL variable instead of calling a _POOL() function every time; also added a require() to ensure the pool is valid
8. _transfer() — added a require() to ensure recipient is not address(0) (only burn can be used for this)
9. burn() — left this is to kep BEP20 interface standard; however the function doesnt actually perform a burn; effectively removing the external burn function from the token (internal only)
10. burnSynth() — added require() to ensure input > 0
11. realise() — removed ‘pool’ argument; this is constant based on the synth contract being called from and no longer required
12. _handleTransferIn() — removed this unused function
13. Removed getters for the now-removed mappings:
- getmapAddress_LPBalance()
- getmapAddress_LPDebt()
contracts/Utils.sol
- calcSwapOutput() — removed ‘two’ memory variable
- Refactored getPool(Synth.LayerONE) to Synth.Pool():
- calcSwapValueInBaseWithSYNTH()
- calcActualSynthUnits()
contracts/poolFactory.sol
- Added ‘curatedPoolCount’ variable to increment/decrement count instead of looping
- Removed irrelevant bool from curation events:
- AddCuratePool()
- RemoveCuratePool()
3. createPoolADD() function changes:
- Added require() to ensure address(0) aka; BNB is only created via the non-public createPool() function
- Added require() to ensure there is not already a pool created for the selected token
- Changed refs of ‘_token’ to ‘token’ (BNB doesnt need to be handled in this function due to the above ‘require’)
4. createPool() — added require() to ensure token is not SPARTA and it’s decimals are 18
5. addCuratedPool() function changes:
- Added require() to ensure pool is *not* already curated
- Increment curatedPoolCount variable here (instead of looping to count like before)
6. removeCuratedPool() — decrement curatedPoolCount here (instead of looping to count like before)
contracts/synthFactory.sol
- CreateSynth event — changed to emit the token, pool and synth addresses
- createSynth() function changes:
- Added require() to ensure the pool address is valid
- Added poolAddress as arg for the new Synth deploy
- Adjusted the event emit accordingly
contracts/synthVault.sol
- Changed Synth.LayerONE() calls to Synth.TOKEN()
- Refactored PoolFactory.getPool(Synth.LayerONE) to Synth.POOL()
- Removed the arg from Synth.realise() (arg is derived internally from synth now)
Project Information
Official Links
- Website: https://spartanprotocol.org/
- DApp: https://dapp.spartanprotocol.org/
- GitBook (Docs): https://docs.spartanprotocol.org/
- GitHub: https://github.com/spartan-protocol
Community Contribution
Spartan Protocol is at its core, a community-driven and led project. In this vein, the more contributors the better. There is a great opportunity for community members to contribute by making LP reward analysis tools, etc.
Recently, community members have been graciously funnelling in to contribute to explainer articles, ideas and even $SPARTA donations to support the growth of the platform.
Engage with the community and contributors
Where to find out about all the latest updates or suggest improvements — get involved.
- Medium: https://medium.com/spartanprotocol
- Twitter: https://twitter.com/spartanprotocol
- Telegram Community: https://t.me/spartanprotocolorg
- Telegram Announcements: https://t.me/spartanprotocolann
- Community-Built Discord: https://discord.gg/wQggvntnGk
Community Bounty Wallet
Whilst there is no treasury nor contributor allocations, there was a public community bounty wallet set up a while ago to help handle donations from the community and other incentive programs (BNB from the Binance BUIDl program was sent here) which can be viewed here: