Repository: CryptoBlades/cryptoblades
File: contracts/cryptoblades.sol (Solidity)

Code before:
                .mul(PAYMENT_USING_STAKED_SKILL_COST_AFTER_DISCOUNT)
                .mul(ABDKMath64x64.fromUInt(chosenElementFee));
        _payContractStakedOnly(msg.sender, usdToSkill(discountedMintWeaponFee));

        _mintWeaponLogic(chosenElement, eventId);
    }

    function setSafeRandoms(SafeRandoms _safeRandoms) public restricted {
        safeRandoms = _safeRandoms;
    }

    function hasSeed(uint quantity) public view returns (bool) {
        return safeRandoms.hasSingleSeedRequest(tx.origin, getSeed(quantity));
    }

    function generateSeed(uint quantity) public {
        safeRandoms.requestSingleSeed(tx.origin, getSeed(quantity));
    }

    function getSeed(uint quantity) internal pure returns (uint256 seed) {
        seed = RandomUtil.combineSeeds(uint(WEAPON_SEED), quantity);
    }

    function _mintWeaponNLogic(uint32 num, uint8 chosenElement, uint256 eventId) internal {
        require(num > 0 && num <= 10);
        uint256 seed = safeRandoms.popSingleSeed(msg.sender, getSeed(num), true, true);
        if(eventId > 0) {
            specialWeaponsManager.addShards(msg.sender, eventId, num);
        }
        _updateWeaponMintFee(num);
        weapons.mintN(msg.sender, num, seed, chosenElement);
    }

    function _mintWeaponLogic(uint8 chosenElement, uint256 eventId) internal {
        uint256 seed = safeRandoms.popSingleSeed(msg.sender, getSeed(1), true, true);
        if(eventId > 0) {
            specialWeaponsManager.addShards(msg.sender, eventId, 1);
        }
        _updateWeaponMintFee(1);
        weapons.mint(msg.sender, seed, chosenElement);
    }

    function _updateWeaponMintFee(uint256 num) internal {
        mintWeaponFee = getMintWeaponFee() + ABDKMath64x64.divu(vars[VAR_WEAPON_FEE_INCREASE].mul(num), 1e18);
        vars[VAR_WEAPON_MINT_TIMESTAMP] = block.timestamp;
    }

    function _updateCharacterMintFee() internal {
        mintCharacterFee = getMintCharacterFee() + ABDKMath64x64.divu(vars[VAR_CHARACTER_FEE_INCREASE], 1e18);
        vars[VAR_CHARACTER_MINT_TIMESTAMP] = block.timestamp;
    }

Review comment (suggestion):
Generating a seed has to happen at the point of payment. This way the user cant generate a seed for free, see if the outcome is favorable, and only pay in the 2nd tx if they know the result will be good.
The minting logic needs to be split into two, one where it's paid for (& seed requested), another where it's claimed (and seed popped). The dynamic mint price should be updated on point of payment even tho the weapon isnt minted yet.

Code after:

    function mintWeapon() external onlyNonContract oncePerBlock(msg.sender) {
        uint8 chosenElement = uint8((userVars[msg.sender][USERVAR_CLAIM_WEAPON_DATA]) & 0xFF);
        uint32 quantity = uint32((userVars[msg.sender][USERVAR_CLAIM_WEAPON_DATA] >> 32) & 0xFFFFFFFF);
        require(quantity > 0);
        userVars[msg.sender][USERVAR_CLAIM_WEAPON_DATA] = 0;
        uint256 seed = SafeRandoms(links[LINK_SAFE_RANDOMS]).popSingleSeed(msg.sender, getSeed(uint(WEAPON_SEED), quantity, chosenElement), true, false);
        weapons.mintN(msg.sender, quantity, seed, chosenElement);
    }

    function getSeed(uint seedId, uint quantity, uint element) internal pure returns (uint256 seed) {
        uint[] memory seeds = new uint[](3);
        seeds[0] = seedId;
        seeds[1] = quantity;
        seeds[2] = element;
        seed = RandomUtil.combineSeeds(seeds);
    }

    function _updateWeaponMintFee(uint256 num) internal {
        mintWeaponFee = getMintWeaponFee() + ABDKMath64x64.divu(vars[VAR_WEAPON_FEE_INCREASE].mul(num), 1e18);
        vars[VAR_WEAPON_MINT_TIMESTAMP] = block.timestamp;
    }

    function _updateCharacterMintFee() internal {
        mintCharacterFee = getMintCharacterFee() + ABDKMath64x64.divu(vars[VAR_CHARACTER_FEE_INCREASE], 1e18);
        vars[VAR_CHARACTER_MINT_TIMESTAMP] = block.timestamp;
    }

    function migrateRandoms(IRandoms _newRandoms) external {
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender));
        randoms = _newRandoms;
    }

    modifier onlyNonContract() {
        _onlyNonContract();
        _;
    }

    function _onlyNonContract() internal view {
        require(tx.origin == msg.sender, "ONC");
    }

    modifier restricted() {
        _restricted();
        _;
    }

    function _restricted() internal view {
        require(hasRole(GAME_ADMIN, msg.sender), "NGA");
    }