Counterstake Audit
Smart Contract Security Assessment
Nov. 17, 2021
(updated Mar. 4, 2022)
SUMMARY
ABSTRACT
Dedaub was commissioned to perform a security audit on Counterstake’s Decentralized Cross-chain Bridge Protocol smart contracts at commit hash 2aa1275ed41d58219226253922ba36b59bb0658b. The code can be found in this Github repository.
SETTING & CAVEATS
The audit’s main target is security threats, i.e., what the community understanding would likely call "hacking", rather than regular use of the protocol. Functional correctness (i.e., issues in "regular use") is a secondary consideration. Typically it can only be covered if we are provided with unambiguous (i.e., full-detail) specifications of what is the expected, correct behavior. In terms of functional correctness, we often trusted the code’s calculations and interactions, in the absence of any other specification. Functional correctness relative to low-level calculations (including units, scaling, quantities returned from external protocols) is generally most effectively done through thorough testing rather than human auditing. The financial model and incentives for participation (e.g., bonding curves, for favoring early investors) are taken for granted, although protocol-level financial manipulation (e.g., actions that will affect the price of participating in an Assistant pool) has been considered extensively.
The scope of this audit included the contracts comprising the Counterstake decentralized protocol for cross-chain transfers as well as the implementation of the assistant contracts, aiming to speed up transfers for end-users by taking care of the interaction with the protocol for a given transfer and getting a reward in return.
The core functionality of the Counterstake protocol is exported using the Import.sol and Export.sol contracts, both of which inherit from the Counterstake.sol contract and make use of the CounterStakeLibrary.sol library. Both the Import and Export contracts make use of the same governance functionality using the Governance.sol and VotedValue*.sol contracts in order to decide on the new values for a set of parameters affecting the contracts in a decentralized manner.
In terms of functionality, the Export contract is used to export an asset out of its native chain using the transferToForeignChain(string memory foreign_address, string memory data, uint amount, int reward) method, as well as start a new claim for a repatriation of exported tokens from the foreign chain, getting the native tokens back. The Import contract is deployed on the foreign chain and is used to initiate a claim for the exported tokens. If a claim is successful, new foreign tokens are minted and sent to the recipient address. Additionally, users can use the transferToHomeChain(string memory home_address, string memory data, uint amount, uint reward) of the Import contract to burn the foreign tokens and return them to their native chain.
The claim functionality works with the claimant providing a stake worth more (currently 150%) than the claimed amount. For claims at the Export contract the stake token is the same as the native/exported token but for the Import contract this has to be a different token, creating a need for a price oracle in order to determine the adequate stake amount. The current price oracle is a centralized one, allowing the oracle’s owner to set the relative prices of different assets. Because of the system’s reliance on that oracle we suggest the use of widely-adopted decentralized oracle systems.
After a claim is initiated in the Import or Export contracts it initiates a challenge period where other participants of the Counterstake protocol can collectively pool their stake tokens against the claim, succeeding if they meet a specific amount higher than the claim. If the challenge is successful, another round in support of the claim begins, with an increased stake token amount. This process is repeated until a decision is reached, with the supporters of the winning side sharing the stakes of the losing side, proportionally to their contribution to the win.
When a transfer is initiated, the reward parameter is used to indicate whether the sender wants an assistant to aid with the cross-chain transfer, sending them the transfer amount minus the reward and taking over the claim process. There are currently 2 assistant contracts, ImportAssistant.sol and ExportAssistant.sol, each controlled by a manager who decides which claims and challenges the assistant pool will participate in. ExportAssistant is the simpler one, with the users pooling their native/to-be-exported tokens. The ImportAssistant keeps both the stake and the exported asset and uses an AMM style constant product formula to denote the relative prices of the two assets (also supports swapping between the two assets).
In addition to the issues of the two Assistant contracts (see M1, M2) in their current implementations, the rewards a user gets when withdrawing their shares are proportional to the balance the contract holds at the time (not considering the amount currently in claims and challenges). This means that a user should prefer to withdraw when the assistant pool is not utilized, while it should be expected that an assistant pool should participate in many claims and challenges to maximize its profit. This can create a situation where a manager chooses to participate in many claims, trapping the liquidity providers of an assistant pool that want to get an adequate reward for their shares.
VULNERABILITIES & FUNCTIONAL ISSUES
This section details issues that affect the functionality of the contract. Dedaub generally categorizes issues according to the following severities, but may also take other considerations into account such as impact or difficulty in exploitation:
- User or system funds can be lost when third-party systems misbehave.
- DoS, under specific conditions.
- Part of the functionality becomes unusable due to a programming error.
- Breaking important system invariants but without apparent consequences.
- Buggy functionality for trusted users where a workaround exists.
- Security issues which may manifest when the system evolves.
Issue resolution includes “dismissed”, by the client, or “resolved”, per the auditors.
CRITICAL SEVERITY
[No critical severity issues]
HIGH SEVERITY
[No high severity issues]
MEDIUM SEVERITY
Assistant Claim withdrawals are susceptible to sandwich/MEV attacks. More specifically:
- Attacker sees large withdrawal, that will yield a high stake asset profit to the assistant contract (after a series of challenges).
- Buys large amount of shares. The staked amounts are accounted for in the share price, but profits are not.
- Assistant withdrawal tx gets executed, Assistant contract now holds a large amount of stake asset.
- Assistant shares are now worth more - attacker withdraws his shares and makes profit.
Using a more pessimistic strategy in the assistant share purchase logic which accounts for future rewards should help prevent such scenarios.
The API of the swapping/AMM functionality (swapImage2Stake/swapStake2Image) of ImportAssistant does not specify a minimum number of output tokens. Due to this, swap operations are susceptible to sandwich/MEV attacks, and/or unexpected slippage. We suggest the addition of a minAmountOut argument to the swap functions to prevent such issues.