Prepared by:
HALBORN
Last Updated Unknown date
Date of Engagement: August 20th, 2024 - August 21st, 2024
100% of all REPORTED Findings have been addressed
All findings
4
Critical
0
High
0
Medium
0
Low
0
Informational
4
Casper
engaged Halborn to conduct a security assessment of the Shiboo Token contract, as well as to investigate suspicious activity that developers had detected in Mainnet transactions, beginning April 11th, 2024 and ending April 26th, 2024.
Shiboo Token is a new digital asset with a total and max supply of 10 billion tokens. Within this allocation, 5% is securely held in the Treasury, while the remaining 95% is dedicated to the Initial Liquidity Pool. The project team is committed to providing initial liquidity, employing a 5% sell tax mechanism to enhance liquidity and potentially decrease the token supply through burns.
The team at Halborn assigned a full-time security engineer to verify the security of the smart contract. The security engineer is a blockchain and smart-contract security expert with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.
The purpose of this assessment is to:
Ensure that smart contract functions operate as intended.
Identify potential security issues with the smart contracts.
In summary, Halborn identified some improvements to reduce the likelihood and impact of risks, which have been acknowledged by the Shiboo team
. The main ones are the following:
The token has mint functionality disabled, so all code related to it can be removed to save gas.
The transfer_from function should emit the TransferFrom event, not the Transfer one.
The access control of the burn function is not necessary since the caller address is compared to with an input argument entered by the user.
The initial_admin key is initialized twice, remove one of them to save gas.
Halborn performed a combination of the manual view of the code and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of the smart contract assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation, automated testing techniques help enhance the coverage of smart contracts. They can quickly identify items that do not follow security best practices. The following phases and associated tools were used throughout the term of the assessment:
Research into architecture, purpose, and use of the platform.
Manual code read and walk through.
Manual Assessment of use and safety for the critical Rust variables and functions in scope to identify any arithmetic related vulnerability classes.
Cross contract call controls.
Architecture related logical controls.
Scanning of Rust files for vulnerabilities.(cargo audit
)
Deployment to testnet through casper-client
.
Comprehensive review of transactions through the CPSR live explorer.
The Shiboo token assessment was contracted to Halborn on special terms. Casper communicated that they found suspicious activity in relation to the Shiboo token, so there was an urgent need to assess the code and the current situation. The Shiboo team was concerned about a transaction where it appeared that tax on sell was being bypassed and also a situation where a user was running a script every few minutes to sell.
After evaluating the source code of the Shiboo token, Halborn concluded that there were no security issues with the token itself, but after analyzing transactions on the Mainnet, it was found a lot of activity related to the Shiboo token and the Friendly Market, as this token is listed on it, so Halborn strongly recommended analyzing the source code of the current version of Friendly Market deployed on Mainnet to validate the transactions between the two contracts.
However, the Friendly Market team was not able to provide the source code of the current commit deployed on mainnet. At this point, the analysis of suspicious activity has been based on the transactions tracked in the explorer and the source code of the Shiboo token.
The following sections will explain the investigation that has been done on the transactions that were flagged by the Shiboo team, some other suspicious transactions found on the blockchain and the corresponding conclusions.
The first transaction analyzed was suspected of evading the sales tax. The Shiboo token applies fees only when transferring to specific contracts, which seemed to be the concern.
Initial exploration suggested a simple token transfer, but further analysis showed that the transaction involved a Session Code, which operates like a script containing various contract calls. Although the full source code of this Session Code isn't available, the events logged indicate that the Friendly Market contract was used to execute a transaction.
Reviewing the transaction logs, the arguments suggested that it executed the Friendly Market entry point named swap_exact_tokens_for_cspr_supporting_fee_on_transfer_tokens
. Analyzing these arguments and the events logged, the following conclusions were drawn:
The session code executed a transaction at the Friendly Market entry point.
The transfer tax applied by the Shiboo token was correctly deducted, confirming that the transfer tax mechanism is functioning as expected.
Without access to the Friendly Market source code, it is impossible to confirm if there are issues with the transactions since the logic of the functions applied is unknown. The overall functionality of the swapping process seems adequate and correct.
The Shiboo development team was also concerned about frequent transactions from a single user, who appeared to be extracting liquidity at regular intervals.
Similar to the previous case, these transactions involved Session Code executions. The arguments used in these transactions indicated that the user was interacting with the Friendly Market’s remove_liquidity_cspr
entry point to burn LP tokens and retrieve the native CSPR token. The logs showed no evidence of malicious activity, though it was noted that an approval event occurred twice with the same details and an unusually high approval amount.
However, as mentioned above, without having access to the source code of the session code executed by the user and without knowing all the logic implemented by the Friendly Market functions, it cannot be stated that any malicious activity is occurring in such contracts since the frequency of execution cannot be evidence of malicious activity with the information available to us.
As with other blockchain technologies, due to the significant amounts of money involved, blockchain environments are attractive targets for hackers. Since transactions are public, attempts to exploit or bypass security mechanisms can sometimes be observed. An example related to the Shiboo token involved a transaction where a user attempted to modify the fee_recipient
address to their own.
Fortunately, the token code is robust enough to prevent such attacks, triggering an error that only allows the legitimate fee recipient to make such changes.
The investigation did not find any definitive evidence of security breaches or malicious activity in the analyzed transactions. However, due to the limitations in accessing the full source code of relevant contracts, some uncertainties remain. Further review of the Friendly Market’s code is recommended to fully ensure the integrity of the transactions.
EXPLOITABILITY METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Attack Origin (AO) | Arbitrary (AO:A) Specific (AO:S) | 1 0.2 |
Attack Cost (AC) | Low (AC:L) Medium (AC:M) High (AC:H) | 1 0.67 0.33 |
Attack Complexity (AX) | Low (AX:L) Medium (AX:M) High (AX:H) | 1 0.67 0.33 |
IMPACT METRIC () | METRIC VALUE | NUMERICAL VALUE |
---|---|---|
Confidentiality (C) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Integrity (I) | None (I:N) Low (I:L) Medium (I:M) High (I:H) Critical (I:C) | 0 0.25 0.5 0.75 1 |
Availability (A) | None (A:N) Low (A:L) Medium (A:M) High (A:H) Critical (A:C) | 0 0.25 0.5 0.75 1 |
Deposit (D) | None (D:N) Low (D:L) Medium (D:M) High (D:H) Critical (D:C) | 0 0.25 0.5 0.75 1 |
Yield (Y) | None (Y:N) Low (Y:L) Medium (Y:M) High (Y:H) Critical (Y:C) | 0 0.25 0.5 0.75 1 |
SEVERITY COEFFICIENT () | COEFFICIENT VALUE | NUMERICAL VALUE |
---|---|---|
Reversibility () | None (R:N) Partial (R:P) Full (R:F) | 1 0.5 0.25 |
Scope () | Changed (S:C) Unchanged (S:U) | 1.25 1 |
Severity | Score Value Range |
---|---|
Critical | 9 - 10 |
High | 7 - 8.9 |
Medium | 4.5 - 6.9 |
Low | 2 - 4.4 |
Informational | 0 - 1.9 |
Critical
0
High
0
Medium
0
Low
0
Informational
4
Security analysis | Risk level | Remediation Date |
---|---|---|
Unused functionalities could be removed to optimize gas | Informational | Acknowledged |
Useless access control in burn function | Informational | Acknowledged |
Repeated key initialization during deployment | Informational | Acknowledged |
Inappropriate event in transfer_from function | Informational | Acknowledged |
//
Since mint
functionality is not implemented in this contract, all the related code like mint
function, the entry point creation or the access control deployed by minter_list
could be removed in order to save some gas.
This also applies to the change_security
and sec_check
functions. The access control provided by these functions is only used in the mint
one. All other privileged functions of the contract such as update_fee
, update_fee_recipient
or update_trading_pair
make use of the only_fee_recipient
function for access control.
The mint
function from cep18/src/main.rs file:
pub extern "C" fn mint() {
if 0 == read_from::<u8>(ENABLE_MINT_BURN) {
revert(Cep18Error::MintBurnDisabled);
}
sec_check(vec![SecurityBadge::Admin, SecurityBadge::Minter]);
let owner: Key = runtime::get_named_arg(OWNER);
let amount: U256 = runtime::get_named_arg(AMOUNT);
let balances_uref = get_balances_uref();
let total_supply_uref = get_total_supply_uref();
let new_balance = {
let balance = read_balance_from(balances_uref, owner);
balance
.checked_add(amount)
.ok_or(Cep18Error::Overflow)
.unwrap_or_revert()
};
let new_total_supply = {
let total_supply: U256 = read_total_supply_from(total_supply_uref);
total_supply
.checked_add(amount)
.ok_or(Cep18Error::Overflow)
.unwrap_or_revert()
};
write_balance_to(balances_uref, owner, new_balance);
write_total_supply_to(total_supply_uref, new_total_supply);
events::record_event_dictionary(Event::Mint(Mint {
recipient: owner,
amount,
}))
}
Code snippet of init
function from cep18/src/main.rs file:
let admin_list: Option<Vec<Key>> =
utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList);
let minter_list: Option<Vec<Key>> =
utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList);
init_events();
if let Some(minter_list) = minter_list {
for minter in minter_list {
dictionary_put(
security_badges_dict,
&base64::encode(minter.to_bytes().unwrap_or_revert()),
SecurityBadge::Minter,
);
}
}
Code snippet of change_security
function from cep18/src/main.rs file:
pub extern "C" fn change_security() {
if 0 == read_from::<u8>(ENABLE_MINT_BURN) {
revert(Cep18Error::MintBurnDisabled);
}
sec_check(vec![SecurityBadge::Admin]);
let admin_list: Option<Vec<Key>> =
utils::get_optional_named_arg_with_user_errors(ADMIN_LIST, Cep18Error::InvalidAdminList);
let minter_list: Option<Vec<Key>> =
utils::get_optional_named_arg_with_user_errors(MINTER_LIST, Cep18Error::InvalidMinterList);
let none_list: Option<Vec<Key>> =
utils::get_optional_named_arg_with_user_errors(NONE_LIST, Cep18Error::InvalidNoneList);
let mut badge_map: BTreeMap<Key, SecurityBadge> = BTreeMap::new();
if let Some(minter_list) = minter_list {
for account_key in minter_list {
badge_map.insert(account_key, SecurityBadge::Minter);
}
}
if let Some(admin_list) = admin_list {
for account_key in admin_list {
badge_map.insert(account_key, SecurityBadge::Admin);
}
}
if let Some(none_list) = none_list {
for account_key in none_list {
badge_map.insert(account_key, SecurityBadge::None);
}
}
Code of sec_check
function from cep18/src/utils.rs file:
pub fn sec_check(allowed_badge_list: Vec<SecurityBadge>) {
let caller = get_immediate_caller_address()
.unwrap_or_revert()
.to_bytes()
.unwrap_or_revert();
if !allowed_badge_list.contains(
&dictionary_get::<SecurityBadge>(get_uref(SECURITY_BADGES), &base64::encode(caller))
.unwrap_or_revert()
.unwrap_or_revert_with(Cep18Error::InsufficientRights),
) {
revert(Cep18Error::InsufficientRights)
}
}
It is recommended to remove unused code to save some gas in the deployment.
ACKNOWLEDGED: The Shiboo team acknowledged this finding.
//
The access control of the burn
function is useless since the OWNER
parameter used in the condition is one of the input arguments, so it can be any value chosen by the user.
The burn
function from cep18/src/main.rs file:
pub extern "C" fn burn() {
if 0 == read_from::<u8>(ENABLE_MINT_BURN) {
revert(Cep18Error::MintBurnDisabled);
}
let owner: Key = runtime::get_named_arg(OWNER);
if owner != get_immediate_caller_address().unwrap_or_revert() {
revert(Cep18Error::InvalidBurnTarget);
}
let amount: U256 = runtime::get_named_arg(AMOUNT);
let balances_uref = get_balances_uref();
It is recommended to delete useless access control in order to save some gas.
ACKNOWLEDGED: The Shiboo team
acknowledged this finding.
//
During the execution of the install_contract
function, the initial_admin
variable is initialized from an input argument (if it exists) or it takes the caller's hash. After that, the init
entry point is called with some arguments, being initial_admin
one of them.
However, this variable is initialized again during the execution of the init
function , taking the input argument of the entry point if it exists or the caller's hash, which in this case is the contract itself.
Since the first initialization is performed before calling the init
function, entering the variable as input argument, and because the init
function is only executed once, the second initialization of the initial_admin
variable is not necessary.
Code snippet install_contract
function from cep18/src/main.rs file:
// Call contract to initialize it
let initial_admin: Key =
utils::get_optional_named_arg_with_user_errors("initial_admin", Cep18Error::InvalidContext)
.unwrap_or(get_caller().into());
let mut init_args = runtime_args! {TOTAL_SUPPLY => total_supply, PACKAGE_HASH => package_hash, FEE_RECIPIENT => fee_recipient, FEE_BIPS => fee_bips, TRADING_PAIR => pair_contract, INITIAL_ADMIN => initial_admin};
if let Some(admin_list) = admin_list {
init_args.insert(ADMIN_LIST, admin_list).unwrap_or_revert();
}
if let Some(minter_list) = minter_list {
init_args
.insert(MINTER_LIST, minter_list)
.unwrap_or_revert();
}
runtime::call_contract::<()>(contract_hash, INIT_ENTRY_POINT_NAME, init_args);
Code snippet init
function from cep18/src/main.rs file:
pub extern "C" fn init() {
if get_key(ALLOWANCES).is_some() {
revert(Cep18Error::AlreadyInitialized);
}
let package_hash = get_named_arg::<Key>(PACKAGE_HASH);
put_key(PACKAGE_HASH, package_hash);
storage::new_dictionary(ALLOWANCES).unwrap_or_revert();
let balances_uref = storage::new_dictionary(BALANCES).unwrap_or_revert();
let initial_supply = runtime::get_named_arg(TOTAL_SUPPLY);
let caller = get_caller();
let initial_admin: Key =
utils::get_optional_named_arg_with_user_errors("initial_admin", Cep18Error::InvalidContext)
.unwrap_or(get_caller().into());
write_balance_to(balances_uref, initial_admin, initial_supply);
It is recommended to remove the second initialization of the initial_admin
variable, in this case the argument is not going to be optional, so it is only necessary to assign it to the variable using the get_named_arg
function.
ACKNOWLEDGED: The Shiboo team
acknowledged this finding.
//
The event emitted in the transfer_from
function is not the appropriate one. The event used is Transfer
instead of TransferFrom
.
Code snippet install_contract
function from cep18/src/main.rs file:
let (amount_to_recipient, fee_amount) =
transfer_balance(owner, recipient, amount).unwrap_or_revert();
write_allowance_to(allowances_uref, owner, spender, new_spender_allowance);
events::record_event_dictionary(Event::Transfer(Transfer {
sender: owner,
recipient,
amount: amount_to_recipient,
}));
if fee_amount.gt(&0.into()) {
events::record_event_dictionary(Event::Transfer(Transfer {
sender: owner,
recipient: read_fee_recipient_internal(),
amount: fee_amount,
}));
}
}
Code snippet Event
type from cep18/src/events.rs file:
pub enum Event {
Mint(Mint),
Burn(Burn),
SetAllowance(SetAllowance),
IncreaseAllowance(IncreaseAllowance),
DecreaseAllowance(DecreaseAllowance),
Transfer(Transfer),
TransferFrom(TransferFrom),
ChangeSecurity(ChangeSecurity),
}
It is recommended to use the appropriate event type: TransferFrom
instead of Transfer
.
ACKNOWLEDGED: The Shiboo team
acknowledged this finding.
Halborn used automated security scanners to assist with detection of well-known security issues and vulnerabilities. Among the tools used was cargo audit
, a security scanner for vulnerabilities reported to the RustSec Advisory Database. All vulnerabilities published in https://crates.io
are stored in a repository named The RustSec Advisory Database. cargo audit
is a human-readable version of the advisory database which performs a scanning on Cargo.lock. Security Detections are only in scope. To better assist the developers maintaining this code, the auditors are including the output with the dependencies tree, and this is included in the cargo audit output to better know the dependencies affected by unmaintained and vulnerable crates.
ID | Package | Description |
---|---|---|
ed25519-dalek Version: 1.0.1 | Double Public Key Signing Function Oracle Attack on | |
lmdb Version: 0.8.0 | Unmaintained | |
parity-wasm Version: 0.42.2 | Unmaintained | |
wee_alloc Version: 0.4.5 | Unmaintained |
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
// Download the full report
Shiboo Token - Simplified
* Use Google Chrome for best results
** Check "Background Graphics" in the print settings if needed