Summary
If a bidder submits multiple bids the Map data structure will preserve his last bid against his address and all the earlier bids could not be tracked and hence when the auction is over his bids can not be withdrawn as they are not saved inside the contract.
Vulnerability Details
As you can see from the following test, the user bids 3 times but only his last bid is stored inside the map and his earlier bids are not saved.
#[test]
fn test_bids_lost() {
let (auction_dispatcher, auction_contract, erc20_contract_address, _) =
deploy_auction_contract();
auction_dispatcher.start(86400, 10);
let erc20_dispatcher = IMockERC20TokenDispatcher { contract_address: erc20_contract_address };
let first_bidder_address: ContractAddress = 123.try_into().unwrap();
start_cheat_caller_address_global(first_bidder_address);
erc20_dispatcher.mint(first_bidder_address, 60);
erc20_dispatcher.token_approve(auction_contract, 60);
auction_dispatcher.bid(15);
let values = load(
auction_contract,
map_entry_address(
selector!("bid_values"), array![first_bidder_address.try_into().unwrap()].span(),
),
3,
);
println!("Values for first_bidder {:?}", values.span());
auction_dispatcher.bid(20);
let values = load(
auction_contract,
map_entry_address(
selector!("bid_values"), array![first_bidder_address.try_into().unwrap()].span(),
),
3,
);
println!("Values for first_bidder {:?}", values.span());
auction_dispatcher.bid(25);
stop_cheat_caller_address_global();
let values = load(
auction_contract,
map_entry_address(
selector!("bid_values"), array![first_bidder_address.try_into().unwrap()].span(),
),
3,
);
println!("Values for first_bidder {:?}", values.span());
}
Output
Values for first_bidder [15, 0, 0]
Values for first_bidder [20, 0, 0]
Values for first_bidder [25, 0, 0]
[PASS] starknet_auction_integrationtest::test_contract::test_bids_lost (gas: ~1996)
Running 0 test(s) from src/
Impact
The bidder will not be able to withdraw his bids other than the last one when the auction is ended.
Tools Used
Tests
Recommendations
Instead of saving current bid against sender address in map, sum of current bid and previous bid should be saved inside map against sender. This way we can keep track of total amount that someone has bid and when the auction is ended we can return this amount for all the bidders and for the winner we can subtract highest bidding from this amount and return the remaining amount to winner in case if he has bidded multiple times.
Other functions in the contract need to be changed as well.
fn bid(ref self: ContractState, amount: u64) {
let time = get_block_timestamp();
let erc20_dispatcher = IERC20Dispatcher { contract_address: self.erc20_token.read() };
let sender = get_caller_address();
let receiver = get_contract_address();
let current_bid = self.highest_bid.read();
assert(self.started.read(), 'Auction is not started');
assert(time < self.bidding_end.read(), 'Auction ended');
assert(amount > current_bid, 'The bid is not sufficient');
+ let old_bid = self.bid_values.read(sender);
+ self.bid_values.entry(sender).write(amount + old_bid);
- self.bid_values.entry(sender).write(amount);
self.emit(NewHighestBid {amount: self.highest_bid.read(), sender: sender});
self.highest_bidder.write(sender);
self.highest_bid.write(amount);
erc20_dispatcher.transfer(receiver, amount.into());
}
Output of same test with obove modification.
Values for first_bidder [15, 0, 0]
Values for first_bidder [35, 0, 0]
Values for first_bidder [60, 0, 0]
[PASS] starknet_auction_integrationtest::test_contract::test_bids_lost (gas: ~2000)