Skip to content

[Bug]: Failed to process contract due to instance assertion #2178

Open
@magurmach

Description

@magurmach

Describe the issue:

Contract source code. Failing on slither <file name>.sol

Check relevant log for full stacktrace.

Code example to reproduce the issue:

pragma solidity ^0.4.18;

/// @title Interface for contracts conforming to ERC-721: Deed Standard
/// @author William Entriken (https://phor.net), et. al.
/// @dev Specification at https://github.com/ethereum/eips/XXXFinalUrlXXX
interface ERC721 {

// COMPLIANCE WITH ERC-165 (DRAFT) /////////////////////////////////////////

/// @dev ERC-165 (draft) interface signature for itself
// bytes4 internal constant INTERFACE_SIGNATURE_ERC165 = // 0x01ffc9a7
//     bytes4(keccak256('supportsInterface(bytes4)'));

/// @dev ERC-165 (draft) interface signature for ERC721
// bytes4 internal constant INTERFACE_SIGNATURE_ERC721 = // 0xda671b9b
//     bytes4(keccak256('ownerOf(uint256)')) ^
//     bytes4(keccak256('countOfDeeds()')) ^
//     bytes4(keccak256('countOfDeedsByOwner(address)')) ^
//     bytes4(keccak256('deedOfOwnerByIndex(address,uint256)')) ^
//     bytes4(keccak256('approve(address,uint256)')) ^
//     bytes4(keccak256('takeOwnership(uint256)'));

/// @notice Query a contract to see if it supports a certain interface
/// @dev Returns `true` the interface is supported and `false` otherwise,
///  returns `true` for INTERFACE_SIGNATURE_ERC165 and
///  INTERFACE_SIGNATURE_ERC721, see ERC-165 for other interface signatures.
function supportsInterface(bytes4 _interfaceID) external pure returns (bool);

// PUBLIC QUERY FUNCTIONS //////////////////////////////////////////////////

/// @notice Find the owner of a deed
/// @param _deedId The identifier for a deed we are inspecting
/// @dev Deeds assigned to zero address are considered invalid, and
///  queries about them do throw.
/// @return The non-zero address of the owner of deed `_deedId`, or `throw`
///  if deed `_deedId` is not tracked by this contract
function ownerOf(uint256 _deedId) external view returns (address _owner);

/// @notice Count deeds tracked by this contract
/// @return A count of valid deeds tracked by this contract, where each one of
///  them has an assigned and queryable owner not equal to the zero address
function countOfDeeds() external view returns (uint256 _count);

/// @notice Count all deeds assigned to an owner
/// @dev Throws if `_owner` is the zero address, representing invalid deeds.
/// @param _owner An address where we are interested in deeds owned by them
/// @return The number of deeds owned by `_owner`, possibly zero
function countOfDeedsByOwner(address _owner) external view returns (uint256 _count);

/// @notice Enumerate deeds assigned to an owner
/// @dev Throws if `_index` >= `countOfDeedsByOwner(_owner)` or if
///  `_owner` is the zero address, representing invalid deeds.
/// @param _owner An address where we are interested in deeds owned by them
/// @param _index A counter less than `countOfDeedsByOwner(_owner)`
/// @return The identifier for the `_index`th deed assigned to `_owner`,
///   (sort order not specified)
function deedOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 _deedId);

// TRANSFER MECHANISM //////////////////////////////////////////////////////

/// @dev This event emits when ownership of any deed changes by any
///  mechanism. This event emits when deeds are created (`from` == 0) and
///  destroyed (`to` == 0). Exception: during contract creation, any
///  transfers may occur without emitting `Transfer`. At the time of any transfer,
///  the "approved taker" is implicitly reset to the zero address.
event Transfer(address indexed from, address indexed to, uint256 indexed deedId);

/// @dev The Approve event emits to log the "approved taker" for a deed -- whether
///  set for the first time, reaffirmed by setting the same value, or setting to
///  a new value. The "approved taker" is the zero address if nobody can take the
///  deed now or it is an address if that address can call `takeOwnership` to attempt
///  taking the deed. Any change to the "approved taker" for a deed SHALL cause
///  Approve to emit. However, an exception, the Approve event will not emit when
///  Transfer emits, this is because Transfer implicitly denotes the "approved taker"
///  is reset to the zero address.
event Approval(address indexed owner, address indexed approved, uint256 indexed deedId);

/// @notice Set the "approved taker" for your deed, or revoke approval by
///  setting the zero address. You may `approve` any number of times while
///  the deed is assigned to you, only the most recent approval matters. Emits
///  an Approval event.
/// @dev Throws if `msg.sender` does not own deed `_deedId` or if `_to` ==
///  `msg.sender` or if `_deedId` is not a valid deed.
/// @param _deedId The deed for which you are granting approval
function approve(address _to, uint256 _deedId) external payable;

/// @notice Become owner of a deed for which you are currently approved
/// @dev Throws if `msg.sender` is not approved to become the owner of
///  `deedId` or if `msg.sender` currently owns `_deedId` or if `_deedId is not a
///  valid deed.
/// @param _deedId The deed that is being transferred
function takeOwnership(uint256 _deedId) external payable;

}

contract Ownable {
address public owner;

/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
    owner = msg.sender;
}

/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
    require(msg.sender == owner);
    _;
}

/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
    if (newOwner != address(0)) {
        owner = newOwner;
    }
}

}

contract MonsterAccessControl {
event ContractUpgrade(address newContract);

 // The addresses of the accounts (or contracts) that can execute actions within each roles.
address public adminAddress;

/// @dev Access modifier for CEO-only functionality
modifier onlyAdmin() {
    require(msg.sender == adminAddress);
    _;
}

}

// This contract stores all data on the blockchain
// only our other contracts can interact with this
// the data here will be valid for all eternity even if other contracts get updated
// this way we can make sure that our Monsters have a hard-coded value attached to them
// that no one including us can change(!)
contract MonstersData {
address coreContract;

struct Monster {
    // timestamp of block when this monster was spawned/created
    uint64 birthTime;

    // generation number
    // gen0 is the very first generation - the later monster spawn the less likely they are to have
    // special attributes and stats
    uint16 generation;

    uint16 mID; // this id (from 1 to 151) is responsible for everything visually like showing the real deal!
    bool tradeable;

    // breeding
    bool female;

    // is this monster exceptionally rare?
    bool shiny;
}

// lv1 base stats
struct MonsterBaseStats {
    uint16 hp;
    uint16 attack;
    uint16 defense;
    uint16 spAttack;
    uint16 spDefense;
    uint16 speed;
}

struct Trainer {
    // timestamp of block when this player/trainer was created
    uint64 birthTime;

    // add username
    string username;

    // current area in the "world"
    uint16 currArea;

    address owner;
}

// take timestamp of block this game was created on the blockchain
uint64 creationBlock = uint64(now);

}

contract MonstersBase is MonsterAccessControl, MonstersData {
/// @dev Transfer event as defined in current draft of ERC721. Emitted every time a monster
/// ownership is assigned, including births.
event Transfer(address from, address to, uint256 tokenId);

bool lockedMonsterCreator = false;

MonsterAuction public monsterAuction;
MonsterCreatorInterface public monsterCreator;

function setMonsterCreatorAddress(address _address) external onlyAdmin {
    // only set this once so we (the devs) can't cheat!
    require(!lockedMonsterCreator);
    MonsterCreatorInterface candidateContract = MonsterCreatorInterface(_address);

    monsterCreator = candidateContract;
    lockedMonsterCreator = true;
}

// An approximation of currently how many seconds are in between blocks.
uint256 public secondsPerBlock = 15;

// array containing all monsters in existence
Monster[] monsters;

uint8[] areas;
uint8 areaIndex = 0;

mapping(address => Trainer) public addressToTrainer;
/// @dev A mapping from monster IDs to the address that owns them. All monster have
///  some valid owner address, even gen0 monster are created with a non-zero owner.
mapping (uint256 => address) public monsterIndexToOwner;
// @dev A mapping from owner address to count of tokens that address owns.
// Used internally inside balanceOf() to resolve ownership count.
mapping (address => uint256) ownershipTokenCount;
mapping (uint256 => address) public monsterIndexToApproved;
mapping (uint256 => string) public monsterIdToNickname;
mapping (uint256 => bool) public monsterIdToTradeable;
mapping (uint256 => uint256) public monsterIdToGeneration;

mapping (uint256 => uint8[7]) public monsterIdToIVs;

// adds new area to world
function _createArea() internal {
    areaIndex++;
    areas.push(areaIndex);
}

function _createMonster(uint256 _generation, address _owner, uint256 _mID, bool _tradeable,
    bool _female, bool _shiny) internal returns (uint)
{

    Monster memory _monster = Monster({
        generation: uint16(_generation),
        birthTime: uint64(now),
        mID: uint16(_mID),
        tradeable: _tradeable,
        female: _female,
        shiny: _shiny
    });

    uint256 newMonsterId = monsters.push(_monster) - 1;

    require(newMonsterId == uint256(uint32(newMonsterId)));

    monsterIdToNickname[newMonsterId] = "";

    _transfer(0, _owner, newMonsterId);

    return newMonsterId;
}

function _createTrainer(string _username, uint16 _starterId, address _owner) internal returns (uint mon) {
    Trainer memory _trainer = Trainer({
        birthTime: uint64(now),
        username: string(_username),
         // sets to first area!,
        currArea: uint16(1),
        owner: address(_owner)
    });

    addressToTrainer[_owner] = _trainer;

    bool gender = monsterCreator.getMonsterGender();

    // starters cannot be traded and are not shiny
    if (_starterId == 1) {
        mon = _createMonster(0, _owner, 1, false, gender, false);
    } else if (_starterId == 2) {
        mon = _createMonster(0, _owner, 4, false, gender, false);
    } else if (_starterId == 3) {
        mon = _createMonster(0, _owner, 7, false, gender, false);
    }
}

function _moveToArea(uint16 _newArea, address player) internal {
    addressToTrainer[player].currArea = _newArea;
}

// assigns ownership of monster to address
function _transfer(address _from, address _to, uint256 _tokenId) internal {
    ownershipTokenCount[_to]++;
    monsterIndexToOwner[_tokenId] = _to;

    if (_from != address(0)) {
        ownershipTokenCount[_from]--;

        // clear any previously approved ownership exchange
        delete monsterIndexToApproved[_tokenId];
    }

    // Emit Transfer event
    Transfer(_from, _to, _tokenId);
}

// Only admin can fix how many seconds per blocks are currently observed.
function setSecondsPerBlock(uint256 secs) external onlyAdmin {
    //require(secs < cooldowns[0]);
    secondsPerBlock = secs;
}

}

contract MonsterOwnership is MonstersBase, ERC721 {
function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
return monsterIndexToOwner[_tokenId] == _claimant;
}

function _isTradeable(uint256 _tokenId) public view returns (bool) {
    return monsterIdToTradeable[_tokenId];
}

/// @dev Checks if a given address currently has transferApproval for a particular monster.
/// @param _claimant the address we are confirming monster is approved for.
/// @param _tokenId monster id, only valid when > 0
function _approvedFor(address _claimant, uint256 _tokenId) internal view returns (bool) {
    return monsterIndexToApproved[_tokenId] == _claimant;
}

function balanceOf(address _owner) public view returns (uint256 count) {
    return ownershipTokenCount[_owner];
}

function transfer(address _to, uint256 _tokenId) public payable {
    transferFrom(msg.sender, _to, _tokenId);
}

function transferFrom(address _from, address _to, uint256 _tokenId) public payable {
    require(monsterIdToTradeable[_tokenId]);
    // Safety check to prevent against an unexpected 0x0 default.
    require(_to != address(0));
    // Disallow transfers to this contract to prevent accidental misuse.
    // The contract should never own any monsters (except very briefly
    // after a gen0 monster is created and before it goes on auction).
    require(_to != address(this));
    // Check for approval and valid ownership
    
    require(_owns(_from, _tokenId));
    // checks if _to was aproved
    require(_from == msg.sender || msg.sender == address(monsterAuction) || _approvedFor(_to, _tokenId));

    // Reassign ownership (also clears pending approvals and emits Transfer event).
    _transfer(_from, _to, _tokenId);
}

function totalSupply() public view returns (uint) {
    return monsters.length;
}

function tokensOfOwner(address _owner) public view returns (uint256[] ownerTokens) {
    uint256 tokenCount = balanceOf(_owner);

    if (tokenCount > 0) {
        uint256[] memory result = new uint256[](tokenCount);
        uint256 totalMonsters = totalSupply();
        uint256 resultIndex = 0;

        uint256 monsterId;

        for (monsterId = 0; monsterId <= totalMonsters; monsterId++) {
            if (monsterIndexToOwner[monsterId] == _owner) {
                result[resultIndex] = monsterId;
                resultIndex++;
            }
        }

        return result;
    }

    return new uint256[](0);
}

bytes4 internal constant INTERFACE_SIGNATURE_ERC165 =
    bytes4(keccak256("supportsInterface(bytes4)"));

bytes4 internal constant INTERFACE_SIGNATURE_ERC721 =
    bytes4(keccak256("ownerOf(uint256)")) ^
    bytes4(keccak256("countOfDeeds()")) ^
    bytes4(keccak256("countOfDeedsByOwner(address)")) ^
    bytes4(keccak256("deedOfOwnerByIndex(address,uint256)")) ^
    bytes4(keccak256("approve(address,uint256)")) ^
    bytes4(keccak256("takeOwnership(uint256)"));

function supportsInterface(bytes4 _interfaceID) external pure returns (bool) {
    return _interfaceID == INTERFACE_SIGNATURE_ERC165 || _interfaceID == INTERFACE_SIGNATURE_ERC721;
}

function ownerOf(uint256 _deedId) external view returns (address _owner) {
    var owner = monsterIndexToOwner[_deedId];
    require(owner != address(0));
    return owner;
}

function _approve(uint256 _tokenId, address _approved) internal {
    monsterIndexToApproved[_tokenId] = _approved;
}

function countOfDeeds() external view returns (uint256 _count) {
    return totalSupply();
}

function countOfDeedsByOwner(address _owner) external view returns (uint256 _count) {
    var arr = tokensOfOwner(_owner);
    return arr.length;
}

function deedOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256 _deedId) {
    return tokensOfOwner(_owner)[_index];
}

function approve(address _to, uint256 _tokenId) external payable {
    // Only an owner can grant transfer approval.
    require(_owns(msg.sender, _tokenId));

    // Register the approval (replacing any previous approval).
    monsterIndexToApproved[_tokenId] = _to;

    // Emit approval event.
    Approval(msg.sender, _to, _tokenId);
}

function takeOwnership(uint256 _deedId) external payable {
    transferFrom(this.ownerOf(_deedId), msg.sender, _deedId);
}

}

contract MonsterAuctionBase {

// Reference to contract tracking NFT ownership
MonsterOwnership public nonFungibleContract;
ChainMonstersCore public core;

struct Auction {
    // current owner
    address seller;
    // price in wei
    uint256 price;
    // time when auction started
    uint64 startedAt;
    uint256 id;
}

// Cut owner takes on each auction, measured in basis points (1/100 of a percent).
// Values 0-10,000 map to 0%-100%
uint256 public ownerCut;

// Map from token ID to their corresponding auction.
mapping(uint256 => Auction) tokenIdToAuction;
mapping(uint256 => address) public auctionIdToSeller;
mapping (address => uint256) public ownershipAuctionCount;

event AuctionCreated(uint256 tokenId, uint256 price, uint256 uID, address seller);
event AuctionSuccessful(uint256 tokenId, uint256 price, address newOwner, uint256 uID);
event AuctionCancelled(uint256 tokenId, uint256 uID);

function _transfer(address _receiver, uint256 _tokenId) internal {
    // it will throw if transfer fails
    nonFungibleContract.transfer(_receiver, _tokenId);
}

function _addAuction(uint256 _tokenId, Auction _auction) internal {
    tokenIdToAuction[_tokenId] = _auction;

    AuctionCreated(
        uint256(_tokenId),
        uint256(_auction.price),
        uint256(_auction.id),
        address(_auction.seller)
    );
}

function _cancelAuction(uint256 _tokenId, address _seller) internal {
    Auction storage _auction = tokenIdToAuction[_tokenId];

    uint256 uID = _auction.id;

    _removeAuction(_tokenId);
    ownershipAuctionCount[_seller]--;
    _transfer(_seller, _tokenId);

    AuctionCancelled(_tokenId, uID);
}

function _buy(uint256 _tokenId, uint256 _bidAmount) internal returns (uint256) {
    Auction storage auction = tokenIdToAuction[_tokenId];

    require(_isOnAuction(auction));

    uint256 price = auction.price;
    require(_bidAmount >= price);

    address seller = auction.seller;
    uint256 uID = auction.id;

    // Auction Bid looks fine! so remove
    _removeAuction(_tokenId);

    ownershipAuctionCount[seller]--;

    if (price > 0) {
        uint256 auctioneerCut = _computeCut(price);
        uint256 sellerProceeds = price - auctioneerCut;

        // NOTE: Doing a transfer() in the middle of a complex
        // method like this is generally discouraged because of
        // reentrancy attacks and DoS attacks if the seller is
        // a contract with an invalid fallback function. We explicitly
        // guard against reentrancy attacks by removing the auction
        // before calling transfer(), and the only thing the seller
        // can DoS is the sale of their own asset! (And if it's an
        // accident, they can call cancelAuction(). )
        if (seller != address(core)) {
            seller.transfer(sellerProceeds);
        }
    }

    // Calculate any excess funds included with the bid. If the excess
    // is anything worth worrying about, transfer it back to bidder.
    // NOTE: We checked above that the bid amount is greater than or
    // equal to the price so this cannot underflow.
    uint256 bidExcess = _bidAmount - price;

    // Return the funds. Similar to the previous transfer, this is
    // not susceptible to a re-entry attack because the auction is
    // removed before any transfers occur.
    msg.sender.transfer(bidExcess);

    // Tell the world!
    AuctionSuccessful(_tokenId, price, msg.sender, uID);

    return price;
}

function _removeAuction(uint256 _tokenId) internal {
    delete tokenIdToAuction[_tokenId];
}

function _isOnAuction(Auction storage _auction) internal view returns (bool) {
    return (_auction.startedAt > 0);
}

 function _computeCut(uint256 _price) internal view returns (uint256) {
    // NOTE: We don't use SafeMath (or similar) in this function because
    //  all of our entry functions carefully cap the maximum values for
    //  currency (at 128-bits), and ownerCut <= 10000 (see the require()
    //  statement in the ClockAuction constructor). The result of this
    //  function is always guaranteed to be <= _price.
    return _price * ownerCut / 10000;
}

}

contract MonsterAuction is MonsterAuctionBase, Ownable {
bool public isMonsterAuction = true;
uint256 public auctionIndex = 0;

function MonsterAuction(address _nftAddress, uint256 _cut) public {
    require(_cut <= 10000);
    ownerCut = _cut;

    var candidateContract = MonsterOwnership(_nftAddress);

    nonFungibleContract = candidateContract;
    ChainMonstersCore candidateCoreContract = ChainMonstersCore(_nftAddress);
    core = candidateCoreContract;
}

// only possible to decrease ownerCut!
function setOwnerCut(uint256 _cut) external onlyOwner {
    require(_cut <= ownerCut);
    ownerCut = _cut;
}

function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
    return (nonFungibleContract.ownerOf(_tokenId) == _claimant);
}

function _escrow(address _owner, uint256 _tokenId) internal {
    // it will throw if transfer fails
    nonFungibleContract.transferFrom(_owner, this, _tokenId);
}

function withdrawBalance() external onlyOwner {
    uint256 balance = this.balance;
    owner.transfer(balance);
}

function tokensInAuctionsOfOwner(address _owner) external view returns(uint256[] auctionTokens) {
    uint256 numAuctions = ownershipAuctionCount[_owner];

    uint256[] memory result = new uint256[](numAuctions);
    uint256 totalAuctions = core.totalSupply();
    uint256 resultIndex = 0;

    uint256 auctionId;

    for (auctionId = 0; auctionId <= totalAuctions; auctionId++) {
        Auction storage auction = tokenIdToAuction[auctionId];
        if (auction.seller == _owner) {
            result[resultIndex] = auctionId;
            resultIndex++;
        }
    }

    return result;
}

function createAuction(uint256 _tokenId, uint256 _price, address _seller) external {
    require(_seller != address(0));
    require(_price == uint256(_price));
    require(core._isTradeable(_tokenId));
    require(_owns(msg.sender, _tokenId));

    
    _escrow(msg.sender, _tokenId);

    Auction memory auction = Auction(
        _seller,
        uint256(_price),
        uint64(now),
        uint256(auctionIndex)
    );

    auctionIdToSeller[auctionIndex] = _seller;
    ownershipAuctionCount[_seller]++;

    auctionIndex++;
    _addAuction(_tokenId, auction);
}

function buy(uint256 _tokenId) external payable {
    //delete auctionIdToSeller[_tokenId];
    // buy will throw if the bid or funds transfer fails
    _buy (_tokenId, msg.value);
    _transfer(msg.sender, _tokenId);
}

function cancelAuction(uint256 _tokenId) external {
    Auction storage auction = tokenIdToAuction[_tokenId];
    require(_isOnAuction(auction));

    address seller = auction.seller;
    require(msg.sender == seller);

    _cancelAuction(_tokenId, seller);
}

function getAuction(uint256 _tokenId) external view returns (address seller, uint256 price, uint256 startedAt) {
    Auction storage auction = tokenIdToAuction[_tokenId];
    require(_isOnAuction(auction));

    return (
        auction.seller,
        auction.price,
        auction.startedAt
    );
}

function getPrice(uint256 _tokenId) external view returns (uint256) {
    Auction storage auction = tokenIdToAuction[_tokenId];
    require(_isOnAuction(auction));
    return auction.price;
}

}

contract ChainMonstersAuction is MonsterOwnership {
bool lockedMonsterAuction = false;

function setMonsterAuctionAddress(address _address) external onlyAdmin {
    require(!lockedMonsterAuction);
    MonsterAuction candidateContract = MonsterAuction(_address);

    require(candidateContract.isMonsterAuction());

    monsterAuction = candidateContract;
    lockedMonsterAuction = true;
}

uint256 public constant PROMO_CREATION_LIMIT = 5000;
uint256 public constant GEN0_CREATION_LIMIT = 5000;

// Counts the number of monster the contract owner has created.
uint256 public promoCreatedCount;
uint256 public gen0CreatedCount;

// its stats are completely dependent on the spawn alghorithm
function createPromoMonster(uint256 _mId, address _owner) external onlyAdmin {
    // during generation we have to keep in mind that we have only 10,000 tokens available
    // which have to be divided by 151 monsters, some rarer than others
    // see WhitePaper for gen0/promo monster plan
    
    // sanity check that this monster ID is actually in game yet
    require(monsterCreator.baseStats(_mId, 1) > 0);
    
    require(promoCreatedCount < PROMO_CREATION_LIMIT);

    promoCreatedCount++;

    uint8[7] memory ivs = uint8[7](monsterCreator.getGen0IVs());

    bool gender = monsterCreator.getMonsterGender();
    
    bool shiny = false;
    if (ivs[6] == 1) {
        shiny = true;
    }
    uint256 monsterId = _createMonster(0, _owner, _mId, true, gender, shiny);
    monsterIdToTradeable[monsterId] = true;

    monsterIdToIVs[monsterId] = ivs;
}

function createGen0Auction(uint256 _mId, uint256 price) external onlyAdmin {
     // sanity check that this monster ID is actually in game yet
    require(monsterCreator.baseStats(_mId, 1) > 0);
    
    require(gen0CreatedCount < GEN0_CREATION_LIMIT);

    uint8[7] memory ivs = uint8[7](monsterCreator.getGen0IVs());

    bool gender = monsterCreator.getMonsterGender();
    
    bool shiny = false;
    if (ivs[6] == 1) {
        shiny = true;
    }
    
    uint256 monsterId = _createMonster(0, this, _mId, true, gender, shiny);
    monsterIdToTradeable[monsterId] = true;

    _approve(monsterId, monsterAuction);

    monsterIdToIVs[monsterId] = ivs;

    monsterAuction.createAuction(monsterId, price, address(this));

    gen0CreatedCount++;
}

}

// used during launch for world championship
// can and will be upgraded during development with new battle system!
// this is just to give players something to do and test their monsters
// also demonstrates how we can build up more mechanics on top of our locked core contract!
contract MonsterChampionship is Ownable {

bool public isMonsterChampionship = true;

ChainMonstersCore core;

// list of top ten
address[10] topTen;

// holds the address current "world" champion
address public currChampion;

mapping (address => uint256) public addressToPowerlevel;
mapping (uint256 => address) public rankToAddress;

// try to beat every other player in the top10 with your strongest monster!
// effectively looping through all top10 players, beating them one by one
// and if strong enough placing your in the top10 as well
function contestChampion(uint256 _tokenId) external {
    uint maxIndex = 9;

    // fail tx if player is already champion!
    // in theory players could increase their powerlevel by contesting themselves but
    // this check stops that from happening so other players have the chance to
    // become the temporary champion!
    if (currChampion == msg.sender) {
        revert();
    }

    require(core.isTrainer(msg.sender));
    require(core.monsterIndexToOwner(_tokenId) == msg.sender);

    uint myPowerlevel = 10; // todo add calculation method to this contract!

    // checks if this transaction is useless
    // since we can't fight against ourself!
    // also stops reentrancy attacks
    require(myPowerlevel > addressToPowerlevel[msg.sender]);

    uint myRank = 0;

    for (uint i = 0; i <= maxIndex; i++) {
        if (myPowerlevel > addressToPowerlevel[topTen[i]]) {
            // you have beaten this one so increase temporary rank
            myRank = i;

            if (myRank == maxIndex) {
                currChampion = msg.sender;
            }
        }
    }

    addressToPowerlevel[msg.sender] = myPowerlevel;

    address[10] storage newTopTen = topTen;

    if (currChampion == msg.sender) {
        for (uint j = 0; j < maxIndex; j++) {
            // remove ourselves from this list in case
            if (newTopTen[j] == msg.sender) {
                newTopTen[j] = 0x0;
                break;
            }
        }
    }

    for (uint x = 0; x <= myRank; x++) {
        if (x == myRank) {
            newTopTen[x] = msg.sender;
        } else {
            if (x < maxIndex)
                newTopTen[x] = topTen[x+1];
        }
    }

    topTen = newTopTen;
}

function getTopPlayers() external view returns (address[10] players) {
    players = topTen;
}

function MonsterChampionship(address coreContract) public {
    core = ChainMonstersCore(coreContract);
}

function withdrawBalance() external onlyOwner {
    uint256 balance = this.balance;
    owner.transfer(balance);
}

}

// where the not-so-much "hidden" magic happens
contract MonsterCreatorInterface is Ownable {
uint8 public lockedMonsterStatsCount = 0;
uint nonce = 0;

function rand(uint16 min, uint16 max) public returns (uint16) {
    nonce++;
    uint16 result = (uint16(keccak256(block.blockhash(block.number-1), nonce))%max);

    if (result < min) {
        result = result+min;
    }

    return result;
}

mapping(uint256 => uint8[8]) public baseStats;

function addBaseStats(uint256 _mId, uint8[8] data) external onlyOwner {
    // lock" the stats down forever
    // since hp is never going to be 0 this is a valid check
    // so we have to be extra careful when adding new baseStats!
    require(data[0] > 0);
    require(baseStats[_mId][0] == 0);
    baseStats[_mId] = data;
}

function _addBaseStats(uint256 _mId, uint8[8] data) internal {
    baseStats[_mId] = data;
    lockedMonsterStatsCount++;
}

function MonsterCreatorInterface() public {
   // these monsters are already down and "locked" down stats/design wise
    _addBaseStats(1, [45, 49, 49, 65, 65, 45, 12, 4]);
    _addBaseStats(2, [60, 62, 63, 80, 80, 60, 12, 4]);
    _addBaseStats(3, [80, 82, 83, 100, 100, 80, 12, 4]);
    _addBaseStats(4, [39, 52, 43, 60, 50, 65, 10, 6]);
    _addBaseStats(5, [58, 64, 58, 80, 65, 80, 10, 6]);
    _addBaseStats(6, [78, 84, 78, 109, 85, 100, 10, 6]);
    _addBaseStats(7, [44, 48, 65, 50, 64, 43, 11, 14]);
    _addBaseStats(8, [59, 63, 80, 65, 80, 58, 11, 14]);
    _addBaseStats(9, [79, 83, 100, 85, 105, 78, 11, 14]);
    _addBaseStats(10, [40, 35, 30, 20, 20, 50, 7, 4]);

    _addBaseStats(149, [55, 50, 45, 135, 95, 120, 8, 14]);
    _addBaseStats(150, [91, 134, 95, 100, 100, 80, 2, 5]);
    _addBaseStats(151, [100, 100, 100, 100, 100, 100, 5, 19]);
}

// this serves as a lookup for new monsters to be generated since all monsters
// of the same id share the base stats
// also makes it possible to only store the monsterId on core and change this one
// during evolution process to save gas and additional transactions
function getMonsterStats( uint256 _mID) external constant returns(uint8[8] stats) {
    stats[0] = baseStats[_mID][0];
    stats[1] = baseStats[_mID][1];
    stats[2] = baseStats[_mID][2];
    stats[3] = baseStats[_mID][3];
    stats[4] = baseStats[_mID][4];
    stats[5] = baseStats[_mID][5];
    stats[6] = baseStats[_mID][6];
    stats[7] = baseStats[_mID][7];
}

function getMonsterGender () external returns(bool female) {
    uint16 femaleChance = rand(0, 100);

    if (femaleChance >= 50) {
        female = true;
    }
}

// generates randomized IVs for a new monster
function getMonsterIVs() external returns(uint8[7] ivs) {
    bool shiny = false;

    uint16 chance = rand(1, 8192);

    if (chance == 42) {
        shiny = true;
    }

    // IVs range between 0 and 31
    // stat range modified for shiny monsters!
    if (shiny) {
        ivs[0] = uint8(rand(10, 31));
        ivs[1] = uint8(rand(10, 31));
        ivs[2] = uint8(rand(10, 31));
        ivs[3] = uint8(rand(10, 31));
        ivs[4] = uint8(rand(10, 31));
        ivs[5] = uint8(rand(10, 31));
        ivs[6] = 1;

    } else {
        ivs[0] = uint8(rand(0, 31));
        ivs[1] = uint8(rand(0, 31));
        ivs[2] = uint8(rand(0, 31));
        ivs[3] = uint8(rand(0, 31));
        ivs[4] = uint8(rand(0, 31));
        ivs[5] = uint8(rand(0, 31));
        ivs[6] = 0;
    }
}

// gen0 monsters profit from shiny boost while shiny gen0s have potentially even higher IVs!
// further increasing the rarity by also doubling the shiny chance!
function getGen0IVs() external returns (uint8[7] ivs) {
    bool shiny = false;

    uint16 chance = rand(1, 4096);

    if (chance == 42) {
        shiny = true;
    }

    if (shiny) {
        ivs[0] = uint8(rand(15, 31));
        ivs[1] = uint8(rand(15, 31));
        ivs[2] = uint8(rand(15, 31));
        ivs[3] = uint8(rand(15, 31));
        ivs[4] = uint8(rand(15, 31));
        ivs[5] = uint8(rand(15, 31));
        ivs[6] = 1;
    } else {
        ivs[0] = uint8(rand(10, 31));
        ivs[1] = uint8(rand(10, 31));
        ivs[2] = uint8(rand(10, 31));
        ivs[3] = uint8(rand(10, 31));
        ivs[4] = uint8(rand(10, 31));
        ivs[5] = uint8(rand(10, 31));
        ivs[6] = 0;
    }
}

function withdrawBalance() external onlyOwner {
    uint256 balance = this.balance;
    owner.transfer(balance);
}

}

contract GameLogicContract {
bool public isGameLogicContract = true;

function GameLogicContract() public {

}

}

contract OmegaContract {
bool public isOmegaContract = true;

function OmegaContract() public {

}

}

contract ChainMonstersCore is ChainMonstersAuction, Ownable {
// using a bool to enable us to prepare the game
bool hasLaunched = false;

// this address will hold future gamelogic in place
address gameContract;

// this contract
address omegaContract;

function ChainMonstersCore() public {
    adminAddress = msg.sender;

    _createArea(); // area 1
    _createArea(); // area 2
}

// we don't know the exact interfaces yet so use the lockedMonsterStats value to determine if the game is "ready"
// see WhitePaper for explaination for our upgrade and development roadmap
function setGameLogicContract(address _candidateContract) external onlyOwner {
    require(monsterCreator.lockedMonsterStatsCount() == 151);

    require(GameLogicContract(_candidateContract).isGameLogicContract());

    gameContract = _candidateContract;
}

function setOmegaContract(address _candidateContract) external onlyOwner {
    require(OmegaContract(_candidateContract).isOmegaContract());
    omegaContract = _candidateContract;
}

// omega contract takes care of all neccessary checks so assume that this is correct(!)
function evolveMonster(uint256 _tokenId, uint16 _toMonsterId) external {
    require(msg.sender == omegaContract);

    // retrieve current monster struct
    Monster storage mon = monsters[_tokenId];

    // evolving only changes monster ID since this is responsible for base Stats
    // an evolved monster keeps its gender, generation, IVs and EVs
    mon.mID = _toMonsterId;
}

// only callable by gameContract after the full game is launched
// since all additional monsters after the promo/gen0 ones need to use this coreContract
// contract as well we have to prepare this core for our future updates where
// players can freely roam the world and hunt ChainMonsters thus generating more
function spawnMonster(uint256 _mId, address _owner) external {
    require(msg.sender == gameContract);

    uint8[7] memory ivs = uint8[7](monsterCreator.getMonsterIVs());

    bool gender = monsterCreator.getMonsterGender();

    bool shiny = false;
    if (ivs[6] == 1) {
        shiny = true;
    }
    
    // important to note that the IV generators do not use Gen0 methods and are Generation 1
    // this means there won't be more than the 10,000 Gen0 monsters sold during the development through the marketplace
    uint256 monsterId = _createMonster(1, _owner, _mId, false, gender, shiny);
    monsterIdToTradeable[monsterId] = true;

    monsterIdToIVs[monsterId] = ivs;
}

// used to add playable content to the game
// monsters will only spawn in certain areas so some are locked on release
// due to the game being in active development on "launch"
// each monster has a maximum number of 3 areas where it can appear
function createArea() public onlyAdmin {
    _createArea();
}

function createTrainer(string _username, uint16 _starterId) public {
    require(hasLaunched);

    // only one trainer/account per ethereum address
    require(addressToTrainer[msg.sender].owner == 0);

    // valid input check
    require(_starterId == 1 || _starterId == 2 || _starterId == 3);

    uint256 mon = _createTrainer(_username, _starterId, msg.sender);

    // due to stack limitations we have to assign the IVs here:
    monsterIdToIVs[mon] = monsterCreator.getMonsterIVs();
}

function changeUsername(string _name) public {
    require(addressToTrainer[msg.sender].owner == msg.sender);
    addressToTrainer[msg.sender].username = _name;
}

function changeMonsterNickname(uint256 _tokenId, string _name) public {
    // users won't be able to rename a monster that is part of an auction
    require(_owns(msg.sender, _tokenId));

    // some string checks...?
    monsterIdToNickname[_tokenId] = _name;
}

function moveToArea(uint16 _newArea) public {
    require(addressToTrainer[msg.sender].currArea > 0);

    // never allow anyone to move to area 0 or below since this is used
    // to determine if a trainer profile exists in another method!
    require(_newArea > 0);

    // make sure that this area exists yet!
    require(areas.length >= _newArea);

    // when player is not stuck doing something else he can move freely!
    _moveToArea(_newArea, msg.sender);
}

// to be changed to retrieve current stats!
function getMonster(uint256 _id) external view returns (
    uint256 birthTime, uint256 generation, uint8[8] stats,
    uint256 mID, bool tradeable, uint256 uID)
{
    Monster storage mon = monsters[_id];
    birthTime = uint256(mon.birthTime);
    generation = mon.generation; // hardcoding due to stack too deep error
    mID = uint256(mon.mID);
    tradeable = bool(mon.tradeable);

    // these values are retrieved from monsterCreator
    stats = uint8[8](monsterCreator.getMonsterStats(uint256(mon.mID)));

    // hack to overcome solidity's stack limitation in monster struct....
    uID = _id;
}

function isTrainer(address _check) external view returns (bool isTrainer) {
    Trainer storage trainer = addressToTrainer[_check];

    return (trainer.currArea > 0);
}

function withdrawBalance() external onlyOwner {
    uint256 balance = this.balance;

    owner.transfer(balance);
}

// after we have setup everything we can unlock the game
// for public
function launchGame() external onlyOwner {
    hasLaunched = true;
}

}

Version:

0.9.6

Relevant log output:

'solc --version' running
'solc 0xfe53d681d34cf379782d25af69990f6a707d30d8.sol --combined-json abi,ast,bin,bin-runtime,srcmap,srcmap-runtime,userdoc,devdoc,hashes,compact-format --allow-paths .,/Users/shakib/projects/RISE_lab/genai_for_solidity/raw_data/ArbitrarySend' running
ERROR:SlitherSolcParsing:
Failed to generate IR for ChainMonstersAuction.createPromoMonster. Please open an issue https://github.com/crytic/slither/issues.
ChainMonstersAuction.createPromoMonster (0xfe53d681d34cf379782d25af69990f6a707d30d8.sol#696-720):
        require(bool)(monsterCreator.baseStats(_mId,1) > 0)
        require(bool)(promoCreatedCount < PROMO_CREATION_LIMIT)
        promoCreatedCount ++
        ivs = uint8[1](monsterCreator.getGen0IVs())
        gender = monsterCreator.getMonsterGender()
        shiny = false
        ivs[6] == 1
        shiny = true
        monsterId = _createMonster(0,_owner,_mId,true,gender,shiny)
        monsterIdToTradeable[monsterId] = true
        monsterIdToIVs[monsterId] = ivs
        onlyAdmin()
Traceback (most recent call last):
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 814, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 102, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 80, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 541, in analyze_contracts
    self._convert_to_slithir()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 767, in _convert_to_slithir
    raise e
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 752, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/core/declarations/function.py", line 1760, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/core/cfg/node.py", line 706, in slithir_generation
    self._irs = convert_expression(expression, self)  # type:ignore
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slithir/convert.py", line 115, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 162, in __init__
    self._visit_expression(self.expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 43, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 99, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 93, in _visit_expression
    self._post_visit(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 306, in _post_visit
    self._post_type_conversion(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 575, in _post_type_conversion
    assert isinstance(expression_type, (TypeAlias, UserDefinedType, ElementaryType))
AssertionError
ERROR:root:Error in 0xfe53d681d34cf379782d25af69990f6a707d30d8.sol
ERROR:root:Traceback (most recent call last):
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 814, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 102, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/__main__.py", line 80, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 135, in __init__
    self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 155, in _init_parsing_and_analyses
    raise e
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slither.py", line 151, in _init_parsing_and_analyses
    parser.analyze_contracts()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 541, in analyze_contracts
    self._convert_to_slithir()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 767, in _convert_to_slithir
    raise e
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 752, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/core/declarations/function.py", line 1760, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/core/cfg/node.py", line 706, in slithir_generation
    self._irs = convert_expression(expression, self)  # type:ignore
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/slithir/convert.py", line 115, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 162, in __init__
    self._visit_expression(self.expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 43, in _visit_expression
    self._visit_assignement_operation(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 99, in _visit_assignement_operation
    self._visit_expression(expression.expression_right)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 93, in _visit_expression
    self._post_visit(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/expression/expression.py", line 306, in _post_visit
    self._post_type_conversion(expression)
  File "/Users/shakib/anaconda3/envs/smartcontract/lib/python3.10/site-packages/slither/visitors/slithir/expression_to_slithir.py", line 575, in _post_type_conversion
    assert isinstance(expression_type, (TypeAlias, UserDefinedType, ElementaryType))
AssertionError

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions