CertiK reviewed EOS Network (EOS) on 12 Mar 2025: 7 findings, 0 critical; 6 marked fixed, 1 open.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 1 | 0 | 0 |
| Medium | 0 | 0 | 2 |
| Low | 0 | 0 | 4 |
The only currency in `statstable` is XYZ added by `system_contract::init()` with `issuer = get_self()`. The contract doesn't have `issue()` and `create()` actions. `system_contract::retire()` allows the issuer (contract itself) to burn `quantity` from the contract balance. This makes no sense and can potentially break the contract invariants. The `owner` argument is also unused.
The `system_contract::swap_after_forwarding()` function utilizes the `{account, "active"_n}` permission to transfer EOS tokens from the `account` to the contract itself. This is a private function that is called by the `system_contract::swapexcess()` action. The `system_contract::swapexcess()` action only uses `require_auth(get_self())`, which does not ensure that authorization from `account` is provided. The `swapexcess_action()` is invoked by multiple other actions: `bidrefund`, `sellram`, `powerup`, `refund`, and `claimrewards`. In every instance, the action is called without the `{account, "active"_n}` permission, as shown below: ```cpp swapexcess_action(get_self(), {{get_self(), "active"_n}}) ``` Consequently, the excesses will not be swapped.
```cpp=208 if (quantity.symbol == EOS) { // First swap the EOS to XYZ and credit it to the user transfer_action("eosio.token"_n, {{from, "active"_n}}).send(from, get_self(), asset(quantity.amount, EOS), memo); // Then transfer the swapped XYZ to the target account transfer_action(get_self(), {{from, "active"_n}}).send(from, to, asset(quantity.amount, get_token_symbol()), memo); } else if (quantity.symbol == get_token_symbol()) { // First swap the XYZ to EOS and credit it to the user transfer_action(get_self(), {{from, "active"_n}}).send(from, get_self(), asset(quantity.amount, get_token_symbol()), memo); // Then transfer the swapped EOS to the target account transfer_action("eosio.token"_n, {{from, "active"_n}}).send(from, to, asset(quantity.amount, EOS), memo); } ``` 1. `memo` argument can be passed as `std::cref(memo)` to avoid copying. 2. `asset(quantity.amount, EOS)` can be replaced with `quantity` in the `quantity.symbol == EOS` branch. 3. `asset(quantity.amount, get_token_symbol())` can be replaced with `quantity` in the `quantity.symbol == get_token_symbol()` branch. 4. `memo` is used for swapping operation. However, `system_contract::transfer()` ignores `memo`. Also, any non-empty `memo` is only meaningful for the `to` account. An empty string should be used for the first transfer operation.
```cpp=124 if (to == to_acnts.end()) { to_acnts.emplace(ram_payer == owner ? owner : get_self(), [&](auto& a) { a.balance = value; a.released = ram_payer == owner; }); ``` The function `system_contract::add_balance()` employs `self` as the `ram_payer` when the balance is not updated by the `owner`. For instance, in the `system_contract::transfer()` function, the `from` account is designated as the `ram_payer` when a new account is created. However, this behavior is not mirrored in the `add_balance()` function, leading to a lack of clarity in its execution.
```cpp=180 // The account owner or this contract can block or unblock an account. if (!has_auth(get_self())) { require_auth(account); } ``` When `system_contract::blockswapto()` is called by `self,` it still uses `account` as a `ram_payer` to insert the element into `_blocked`. However, since there's no authorization for an `account`, the action will fail. It is unclear when the action is executed by the contract itself.
`system_contract::buyrambytes()` calculates `current_eos_balance = get_eos_balance(payer) + asset(cost_plus_fee, EOS)` and then enforces the balance to be equal to `current_eos_balance - asset(cost_plus_fee, EOS)`. That practically means that the EOS balance doesn't change. The code can be simplified as: ```cpp=364 enforcebal_action( get_self(), {{payer, "active"_n}}).send(payer, get_eos_balance(payer)); ``` The code calculating `cost_plus_fee` mimics the "eosio" calculation. It's reasonable to move it to "include\system\oldsystem.hpp"
The report names a repository or files. Open the report to see its scope.
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us