Patrick Collins Brownie课程中Lottery合约单元测试断言失败求助
问题:Lottery合约单元测试断言失败,赢家地址不符合预期
在学习Patrick Collins的FreeCodeCamp/Chainlink Brownie/Solidity课程过程中,其余测试均正常通过,但test_can_pick_winner_correctly单元测试出现断言失败。预期第一个参与者(account[0])成为赢家(因STATIC_RNG=777,777%3=0),但实际合约返回的赢家地址不匹配。
错误信息
assert lottery.recentWinner() == account E AssertionError: assert '0x33A4622B82D4c04a53e170c638B944ce27cffce3' == <Account '0x66aB6D9362d4F35596279692F0251Db635165871'> E + where '0x33A4622B82D4c04a53e170c638B944ce27cffce3' = <ContractCall 'recentWinner()'>() E + where <ContractCall 'recentWinner()'> = <Lottery Contract '0x6951b5Bd815043E3F842c1b026b0Fa888Cc2DD85'>.recentWinner
错误位置:tests\test_lottery_unit.py:85: AssertionError
相关代码
单元测试代码
from brownie import Lottery, accounts, config, network, exceptions from scripts.helpful_scripts import ( LOCAL_BLOCKCHAIN_ENVIRONMENTS, get_account, fund_with_link, get_contract, ) from scripts.deploy_lottery import deploy_lottery from web3 import Web3 import pytest def test_can_pick_winner_correctly(): # Arrange if network.show_active() not in LOCAL_BLOCKCHAIN_ENVIRONMENTS: pytest.skip() lottery = deploy_lottery() account = get_account() lottery.startLottery({"from": account}) lottery.enter({"from": account, "value": lottery.getEntranceFee()}) lottery.enter({"from": get_account(index=1), "value": lottery.getEntranceFee()}) lottery.enter({"from": get_account(index=2), "value": lottery.getEntranceFee()}) fund_with_link(lottery) transaction = lottery.endLottery({"from": account}) request_id = transaction.events["RequestedRandomness"]["requestId"] STATIC_RNG = 777 get_contract("vrf_coordinator").callBackWithRandomness( request_id, STATIC_RNG, lottery.address, {"from": account} ) starting_balance_of_account = account.balance() balance_of_lottery = lottery.balance() # 777 % 3 = 0 assert lottery.recentWinner() == account assert lottery.balance() == 0 assert account.balance() == starting_balance_of_account + balance_of_lottery
部署脚本deploy_lottery.py
from dis import show_code from pkg_resources import ensure_directory from scripts.helpful_scripts import get_account, get_contract, fund_with_link from brownie import Lottery, network, config import time def deploy_lottery(): account = get_account() lottery = Lottery.deploy( get_contract("eth_usd_price_feed").address, get_contract("vrf_coordinator").address, get_contract("link_token").address, config["networks"][network.show_active()]["fee"], config["networks"][network.show_active()]["keyhash"], {"from": account}, publish_source=config["networks"][network.show_active()].get("verify", False), ) print("Deployed Lottery!") return lottery def start_lottery(): account = get_account() lottery = Lottery[-1] starting_tx = lottery.startLottery({"from": account}) starting_tx.wait(1) print("The lottery has started") def enter_lotter(): account = get_account() lottery = Lottery[-1] value = lottery.getEntranceFee() + 10000000 tx = lottery.enter({"from": account, "value": value}) tx.wait(1) print("You entered the lottery!") def end_lottery(): account = get_account() lottery = Lottery[-1] # fund the contract and then end the lottery tx = fund_with_link(lottery.address) tx.wait(1) ending_transaction = lottery.endLottery({"from": account}) ending_transaction.wait(1) time.sleep(60) print(f"{lottery.recentWinner()} is the new winner!") def main(): deploy_lottery() start_lottery() enter_lotter() end_lottery()
Solidity合约代码
pragma solidity >=0.7.0 <0.8.7; import "@chainlink/contracts/src/v0.7/interfaces/AggregatorV3Interface.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@chainlink/contracts/src/v0.7/VRFConsumerBase.sol"; contract Lottery is VRFConsumerBase, Ownable { address payable[] public players; uint256 public usdEntryFee; AggregatorV3Interface internal ethUsdPriceFeed; enum LOTTERY_STATE { OPEN, CLOSED, CALCULATING_WINNER } LOTTERY_STATE public lottery_state; uint256 public fee; bytes32 public keyhash; event RequestedRandomness(bytes32 requestId); address payable public recentWinner; uint256 public randomness; constructor( address _priceFeedAddress, address _vrfCoordinator, address _link, uint256 _fee, bytes32 _keyhash ) public VRFConsumerBase(_vrfCoordinator, _link) { usdEntryFee = 50 * (10**18); ethUsdPriceFeed = AggregatorV3Interface(_priceFeedAddress); lottery_state = LOTTERY_STATE.CLOSED; fee = _fee; keyhash = _keyhash; } function enter() public payable { require(lottery_state == LOTTERY_STATE.OPEN); require(msg.value >= getEntranceFee(), "Not enough ETH!"); players.push(msg.sender); } function getEntranceFee() public view returns (uint256) { (, int256 price, , , ) = ethUsdPriceFeed.latestRoundData(); uint256 adjustedPrice = uint256(price) * (10**10); //18 decimals uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice; return costToEnter; } function startLottery() public onlyOwner { require( lottery_state == LOTTERY_STATE.CLOSED, "Can't start a new lottery yet!" ); lottery_state = LOTTERY_STATE.OPEN; } function endLottery() public onlyOwner { lottery_state = LOTTERY_STATE.CALCULATING_WINNER; bytes32 requestId = requestRandomness(keyhash, fee); emit RequestedRandomness(requestId); } function fulfillRandomness(bytes32 _requestId, uint256 _randomness) internal override { require( lottery_state == LOTTERY_STATE.CALCULATING_WINNER, "You aren't there yet!" ); require(_randomness > 0, "random-not-found"); uint256 indexOfWinner = _randomness & players.length; recentWinner = players[indexOfWinner]; recentWinner.transfer(address(this).balance); players = new address payable[](0); lottery_state = LOTTERY_STATE.CLOSED; randomness = _randomness; } }
问题排查与修复
问题出在Solidity合约的fulfillRandomness函数中,计算赢家索引时误用了位运算符&而非取余运算符%:
- 预期计算:
777 % 3 = 0,对应第一个参与者 - 实际计算:
777 & 3 = 1,对应第二个参与者
修改fulfillRandomness函数中的索引计算代码,将&替换为%即可修复:
function fulfillRandomness(bytes32 _requestId, uint256 _randomness) internal override { require( lottery_state == LOTTERY_STATE.CALCULATING_WINNER, "You aren't there yet!" ); require(_randomness > 0, "random-not-found"); uint256 indexOfWinner = _randomness % players.length; // 替换为取余运算符 recentWinner = players[indexOfWinner]; recentWinner.transfer(address(this).balance); players = new address payable[](0); lottery_state = LOTTERY_STATE.CLOSED; randomness = _randomness; }
内容的提问来源于stack exchange,提问作者USCMigs
相关产品推荐
相关产品推荐

