SpartanDev: 02/08/21–08/08/21

First week of August; addition of caps, global freeze and other new protocol safety features


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.

We still need your support

We still need your support. All donations help ensure the V2 Protocol contracts get a thorough review from many angles. We are very close to meeting our raise objectives. Please help kick it over the line.

If you can donate, big or small, every contribution helps this community project achieve its full goal of $96k as soon as possible to ensure that Spartan Protocol is in a prime position to release the V2 contracts and DApp onto mainnet.

Donate Here:

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

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)


  • 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


  • 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


  1. 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


Added require() checks to ensure proposal-related functions revert if there is a globalFreeze in place:

  • checkProposal()
  • voteProposal()
  • finaliseProposal()


Added require() check to ensure no-one can increase their weight in the DAOVault when there is a globalFreeze in place:

  • increaseWeight()


  1. onlyROUTER() modifier — renamed to onlyPROTOCOL() and added in the synthVault to the permissions
  2. 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


  1. Added globalFreeze variable (bool)
  2. 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


  1. added globalCAP() variable; soft cap on the synth supply vs tokenDepth of a pool (yet to be linked into the protocol properly)
  2. lastMonth — variable changed to ‘public’ for UI access
  3. 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


  1. Renamed LayerONE variable to TOKEN
  2. Removed DEPLOYER variable and assignment in constructor; wasn’t needed for anything
  3. Added ‘collateral’ variable
  4. 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()


  1. calcSwapOutput() — removed ‘two’ memory variable
  2. Refactored getPool(Synth.LayerONE) to Synth.Pool():
  • calcSwapValueInBaseWithSYNTH()
  • calcActualSynthUnits()


  1. Added ‘curatedPoolCount’ variable to increment/decrement count instead of looping
  2. 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)


  1. CreateSynth event — changed to emit the token, pool and synth addresses
  2. 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


  1. Changed Synth.LayerONE() calls to Synth.TOKEN()
  2. Refactored PoolFactory.getPool(Synth.LayerONE) to Synth.POOL()
  3. Removed the arg from Synth.realise() (arg is derived internally from synth now)

Project Information

Official Links

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.

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:


Incentivized liquidity and synthetic asset generation for Binance Smart Chain.