Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The code contains an off-by-one error in the index boundary check, which will deliver mistake revert code.
Vulnerability Details
/// Returns a whitelisted strategy at the index #[storage(read)]fnget_whitelisted_strategy(index:u64) ->Option<ContractId> {let len = storage.strategies.len();require(len !=0, ExecutionManagerErrors::ZeroLengthVec);require(index <= len, ExecutionManagerErrors::IndexOutOfBound); storage.strategies.get(index).unwrap().try_read() }
the second require checking should be index<len instead of index<=len.
Impact Details
While this bug does not result in direct financial losses or value reduction, it does impact the contract's ability to deliver its promised functionality accurately. This type of issue falls within the scope of "Failing to deliver promised returns" in terms of functional expectations, even if not in a financial sense. The severity is moderate, as it affects usability and reliability without compromising funds or causing direct losses.
References
none
Proof of concept
contract;
use interfaces::{
thunder_exchange_interface::{ThunderExchange},
royalty_manager_interface::*,
asset_manager_interface::*,
execution_manager_interface::ExecutionManager,
execution_strategy_interface::*,
pool_interface::Pool,
};
use libraries::{
msg_sender_address::*,
constants::*,
order_types::*,
ownable::*,
};
use std::{
block::timestamp,
auth::*,
call_frames::*,
context::*,
contract_id::ContractId,
logging::log,
revert::require,
storage::storage_map::*,
asset::*
};
#[test(should_revert="18446744073709486080")]
fn test1() {
let execution_manager = abi(ExecutionManager, execution_manager::CONTRACT_ID);
execution_manager.initialize();
execution_manager.add_strategy(ZERO_CONTRACT_ID);
execution_manager.add_strategy(ContractId::from(0x0000000000000000000000000000000000000000000000000000000000000001));
let cnt = execution_manager.get_whitelisted_strategy(3).unwrap();
log(cnt);
}
#[test(should_revert="18446744073709486080")]
fn test2() {
let execution_manager = abi(ExecutionManager, execution_manager::CONTRACT_ID);
execution_manager.initialize();
execution_manager.add_strategy(ZERO_CONTRACT_ID);
execution_manager.add_strategy(ContractId::from(0x0000000000000000000000000000000000000000000000000000000000000001));
let cnt = execution_manager.get_whitelisted_strategy(2).unwrap();
log(cnt);
}
result:
tested -- test_contract
test test1 ... ok (1.044537ms, 27069 gas)
[{"LogData":{"data":"0000000000000000bddc268719f6787cc71b7ccccf2ef91acce6c6e27404e1a1fa87684f824d17ff","digest":"534de284afb4ea471d255e0be03e2de718ebe38881daee6cabd076bd2d786b19","id":"3d3a7ea92ca438366572654a4770969bf874859aa16d5e6fbe46e21f4fe828c7","is":20600,"len":40,"pc":43192,"ptr":67103232,"ra":0,"rb":9517900813706399297}},{"LogData":{"data":"0000000000000005","digest":"5dee4dd60ff8d0ba9900fe91e90e0dcf65f0570d42c431f727d0300dd70dc431","id":"3d3a7ea92ca438366572654a4770969bf874859aa16d5e6fbe46e21f4fe828c7","is":21120,"len":8,"pc":33164,"ptr":67088000,"ra":0,"rb":10980104167348192313}}]
test test2 ... FAILED (1.095737ms, 27346 gas)
[{"LogData":{"data":"0000000000000000bddc268719f6787cc71b7ccccf2ef91acce6c6e27404e1a1fa87684f824d17ff","digest":"534de284afb4ea471d255e0be03e2de718ebe38881daee6cabd076bd2d786b19","id":"3d3a7ea92ca438366572654a4770969bf874859aa16d5e6fbe46e21f4fe828c7","is":20600,"len":40,"pc":43192,"ptr":67103232,"ra":0,"rb":9517900813706399297}}]
failures:
- test test2, "/data/smart-contracts/contracts-v1/tests/src/main.sw":45
revert code: 0
Logs: [{"LogData":{"data":"0000000000000000bddc268719f6787cc71b7ccccf2ef91acce6c6e27404e1a1fa87684f824d17ff","digest":"534de284afb4ea471d255e0be03e2de718ebe38881daee6cabd076bd2d786b19","id":"3d3a7ea92ca438366572654a4770969bf874859aa16d5e6fbe46e21f4fe828c7","is":20600,"len":40,"pc":43192,"ptr":67103232,"ra":0,"rb":9517900813706399297}}]
test result: FAILED. 1 passed; 1 failed; finished in 2.140274ms
tested -- NFT-contract
test result: OK. 0 passed; 0 failed; finished in 0ns
As you can see, test1 passed, but test2 failed. but they should share same revert code.