Drawing from our audit of Airswift's SCF, we discuss part of Soroban's security model and showcase common vulnerabilities. SCF, for "Supply Chain Financing", is the DeFi product developed by Airswift that "optimizes funds flow" between buyers and suppliers. It is developed on Stellar's smart contract platform: Soroban. Airswift mandated Quarkslab for an audit of their smart contracts, with support from the Stellar Development Foundation. In this blog post, we present the results of this audit, and share common pitfalls to avoid on Soroban.
Introduction
Airswift's SCF is a financing solution tackling supply chain inefficiencies. It enables buyers to optimize their working capital, while securing the funds of their suppliers.
Airswift developed two variations of their product: one limited to and for the Argentinian market, and a generic one for the rest of the world. The general idea is to tokenize funded buyers orders, so they can be lent and borrowed freely on-chain.
Airswift is responsible for emitting certificates in the form of (divisible) NFTs, that are then funded by the buyers. Depending on the implementation, it can be split into parts or not, then transferred like a regular NFT. Airswift also provides a lending and borrowing platform for these certificates.
The Stellar ecosystem provides the Soroban environment. It is a smart contract platform that was audited by Quarkslab and introduced as a new feature to the mainnet. This platform includes the smart contract environment, a Rust SDK, a CLI, and an RPC server.
The goal of this audit was to assess the security of the two Soroban implementations of Airswift's SCF solution. The full audit report is available on the Airswift website.
Scope
The scope of this audit included six smart contracts (2 utility, plus 2 for each implementation), available in the Airswiftio/SCF GitHub repository:
- the Soroban equivalent to an ERC-20,1 to be able to interact with stablecoins;
- a smart contract deployer;
- the NFT representing certificates; and
- a pool to lend and borrow the certificates.
Findings
The table below summarizes the findings of the audit. Although there is some overlap between implementations, we decided to duplicate issues when they were present in both smart contracts, so that we could track their resolution separately.
A total of 33 (27 unique) issues were found, including 3 unique with a critical severity rating and 4 unique rated high.
We found that Airswift's original codebase fell for common security pitfalls, which are discussed below.
ID | Title | Severity | Perimeter |
---|---|---|---|
CRIT-1 | Approvals are stored in Instance storage | Critical | argentina_pledge approval |
CRIT-2 | Approvals are not revoked upon regular transfer | Critical | argentina_pledge approval |
CRIT-3 | Approvals are stored in Instance storage | Critical | scf_soroban approval |
CRIT-4 | Approval is not reset during token transfer |
Critical | scf_soroban transfer |
CRIT-5 | Uncapped supply of token leads to loss of funds | Critical | scf_soroban split |
HIGH-1 | Borrower's TC may never be transferred back after payoff, leading to loss of funds | High | argentina_pledge loan |
HIGH-2 | Loan offer creation can be censored by front-running | High | argentina_pool loan |
HIGH-3 | Offer creation accepts untrusted pool_tokens |
High | scf_pool |
HIGH-4 | Tokenized certificate owner can split before accepting an offer | High | scf_pool |
MED-1 | Approvals cannot be revoked | Medium | argentina_pledge approval |
MED-2 | Untrusted contract call in accept_load_offer |
Medium | argentina_pool loan |
MED-3 | Token approval can't be deleted | Medium | scf_soroban approval |
MED-4 | Offer creation accepts non-existing tokenized certificate contracts and identifiers | Medium | scf_pool |
MED-5 | User may be censored through front-running | Medium | scf_pool |
LOW-1 | Unbounded storage of DataKey::FileHashes(i128) |
Low | argentina_pledge storage |
LOW-2 | Mismatched storage type of DataKey::Owner(i128) |
Low | argentina_pledge storage |
LOW-3 | Mismatched storage type of DataKey::Approval(ApprovalKey::ID(i128)) |
Low | argentina_pledge storage |
LOW-4 | Too small type for TC amount | Low | argentina_pledge mint |
LOW-5 | Redeem time's validity is not checked at mint time | Low | argentina_pledge mint |
LOW-6 | Split may be smaller than 10% of the root's total_amount |
Low | scf_soroban split |
LOW-7 | Uncapped number of verifiable credentials per token | Low | scf_soroban add_vc |
INFO-1 | Warnings emitted during the compilation | Info | argentina_pledge |
INFO-2 | Improper type for TC IDs | Info | argentina_pledge |
INFO-3 | Warnings emitted during the compilation | Info | argentina_pledge |
INFO-4 | Unused DataKey variants |
Info | argentina_pool |
INFO-5 | Fixed-point variable has limited resolution | Info | argentina_pool loan payoff |
INFO-6 | Bad public variable name | Info | argentina_pool |
INFO-7 | Superfluous field in Loan |
Info | argentina_pool storage |
INFO-8 | Superfluous liquidity token | Info | argentina_pool token |
INFO-9 | Storage keys are not standardized | Info | scf_soroban storage |
INFO-10 | Unused data key variants | Info | scf_soroban storage |
INFO-11 | The end_time can be configured to a past timestamp |
Info | scf_soroban initialize |
INFO-12 | Verifiable credential can be any format | Info | scf_soroban VC |
All vulnerabilities have been addressed, most by fixing them. MED-4 will be mitigated in the front-end, and 2 lower severity issues have been acknowledged without a planned fix.
Discussion
In light of these findings, we selected three issues that can commonly be found in other projects, and discuss them here.
Authorization model
The developed NFT contract lets users authorize a third party to transfer their tokens.
This is a common paradigm on EVM chains: when interacting with a DeFi protocol, the user needs to authorize the protocol to use their tokens. To do so, they first need to perform an "approve" transaction on the token before interacting with the protocol.
This flow is not necessary on Soroban, since the user signing the transaction can add an authorization for subcontract calls.2 In this case, the user approves the token transfer at the same time as they interact with the DeFi protocol.
Unless required by a more obscure use-case not solved by Soroban's authorization framework, we recommend not to include EVM style authorization methods to DeFi protocols. Indeed, while not inherently unsafe, they carry additional risk for end users.3 4 If approvals are still needed, developers should use Temporary storage, so they can expire when unused.
Airswift followed our recommendation and removed the superfluous mechanism.
Superfluous token
It is common for DeFi projects to emit their own tokens for their protocols. Reasons to do so are numerous, and are the subject of vivid debates on the utility of one or the other. We won't discuss this here, and only remind developers that creating a token is not always necessary.
During this audit, we found a clear example of such a superfluous token: it can be minted or burnt at a 1:1 rate with a configured stablecoin, is only necessary to interact with the protocol, and it cannot be upgraded. In this state, users can very well
- swap their stablecoin for the custom token,
- use it in the protocol, and
- swap back whenever they receive the custom token.
The users have no incentive to keep or use the token elsewhere, and its value is exactly tied to that of the chosen stablecoin.
From the protocol's point of view, using a custom token or the stablecoin directly changes nothing, so the custom token only acts as a transparent proxy for the stablecoin, and adds friction to interact with the protocol.
In such cases, we recommend removing the token entirely and interacting directly with the configured stablecoin.
Uncooperative users
SCF designed a flow for certificate loans, in which the loan's state advances as users call the corresponding functions. Such state-based operational flow can be seen in many decentralized applications, and developers need to make sure that some users cannot lock the process in a state that is detrimental to someone else, even if it does not benefit anyone.5
As an example, let's take the original argentinian_pledge
flow:
Original flow of loans in the Argentinian implementation
Here, a malicious or uncooperative creditor could for example choose not to call the close_loan
function,
preventing the borrower who paid their debt from getting the certificate.
The borrower ends up with neither the certificate nor the borrowed value as the loan is stuck in the Paid
state,
while the creditor keeps the certificate but does not get the money it lent back.
The solution implemented by Airswift was to
- make the smart contract own the certificate on behalf of the creditor, so that they cannot keep the certificate to themselves;
- allow the administrator to force default the loan, in order to give the creditor full ownership of the certificate;
- merge the operations of
payoff_loan
andclose_loan
.
Fixed flow of loans in the Argentinian implementation
Conclusion
We found several issues in our audit of Airswift's Supply Chain Financing implementations, that are now resolved. We also highlighted common pitfalls in smart contract design and development on Soroban.
Overall, working with the Airswift team was a great experience. They were very attentive to Quarkslab's proposed remediations, and committed to enhancing the project's security.
Going forward, we strive to continue pursuing our mission of advancing security in the Web3 ecosystem.
-
The code was taken from Stellar's
soroban-example
repository. ↩ -
See this Ledger article. ↩
-
As is the case with griefers, see for example An analysis of griefs and griefing factors, by William George. ↩