Error while interacting with my contract (Hackathon season 6 )

Good day all, I am learning Solidity, and I came up with this issue while deploying my contract yesterday.

So I can deploy it correctly, and the calls to the readonly function getVotes() seem to work ok.

When I deploy it on test net, and use tronBox (in a browser environment)


            const tronWeb =  window.tronWeb;

            const contractAddress = 'TFBP2xBhYTb2hNrLep5UpFW1a2DZ5AP4yY';
            let contract;
            try {
                contract = await tronWeb.contract().at(contractAddress);
            } catch(e) {
                console.error(`error loDING CONTRACT ${contractAddress}`);
                throw e
            }
            //
            //
            try {
                let votes = await contract.getVotes().call();
                console.log(`Loading All votes from TRON `, votes);
            } catch(e) {
                console.error(`error getting votes`, votes);
                throw e
            }

            try {
                const txId = await contract.voteBooks([35, 36], [3, 4]).send()
                console.log(`Check tx on the explorer: https://shasta.tronscan.org/#/transaction/${txId}`);
            } catch(e) {
                console.error(`error voting`, e);
                throw e
            }

The 3d try/catch block doesn’t throw any error, and the console log shows check tx on the explorer:

When I go check it out, the transaction failed, and I cannot find any debug information that could help me figure out what is going on, and why it failed. So I am turning to the TRON experts out there that eat solidity at breakfast, and can spot bugs effortlessly while doing cardio.

Here is the contract on Shasta: TRONSCAN | TRON BlockChain Explorer | 波场区块链浏览器

I cannot seem to explore why the transaction failed, and I am quite stuck.

Here is the contract source code:


pragma solidity >=0.7.0 <0.9.0;

uint8 constant ILLEGAL = 1;
uint8 constant POOR = 2;
uint8 constant OK = 3;
uint8 constant GREAT = 4;

struct BookVote {
    bool initialized;
    uint8 classification;
}

struct UserVotes {
    mapping(uint256 => BookVote) data;
    bool initialized;
}


contract BookVotes {

    // user Data
    address[] all_addresses;
    mapping(address => UserVotes) user_votes;

    // all Data
    uint256[] public ids;
    // map[id] index
    mapping(uint256 => uint256) public idsIndex;
    uint256[] public illegal;
    uint256[] public ok;
    uint256[] public poor;
    uint256[] public great;


    constructor() {
        ids.push(0);
    }

    function addGlobalVote(uint256 bookId, uint8 cl) public {
        if (idsIndex[bookId] == 0) {
            ids.push(bookId);
            illegal.push(0);
            ok.push(0);
            poor.push(0);
            great.push(0);
            idsIndex[bookId] = ids.length - 1;
        }

        uint index = idsIndex[bookId];

        if (cl == GREAT) {
            great[index]++;
        } else if (cl == OK) {
            ok[index]++;
        } else if (cl == POOR) {
            poor[index]++;
        } else if (cl == ILLEGAL) {
            illegal[index]++;
        }
    }

    function removeGlobalVote(uint256 bookId, uint8 cl) private {
        require(idsIndex[bookId] > 0, "cannot remove vote for book");

        uint index = idsIndex[bookId];

        if (cl == GREAT) {
            great[index]--;
        } else if (cl == OK) {
            ok[index]--;
        } else if (cl == POOR) {
            poor[index]--;
        } else if (cl == ILLEGAL) {
            illegal[index]--;
        }
    }

    function voteBooks(uint256[] memory bookIds, uint8[] memory classifications) public {
        if (user_votes[msg.sender].initialized == false) {
            all_addresses.push(msg.sender);
            user_votes[msg.sender].initialized = true;
            for (uint i = 0; i < bookIds.length; i++) {
                user_votes[msg.sender].data[bookIds[i]].initialized = true;
                user_votes[msg.sender].data[bookIds[i]].classification = classifications[i];
            }
        } else {
            // user is already in the mapping
            for (uint i = 0; i < bookIds.length; i++) {
                uint8 cl = classifications[i];
                uint256 book = bookIds[i];

                if (user_votes[msg.sender].data[book].initialized) {
                    // book has already been voted upon in the past
                    if (cl != user_votes[msg.sender].data[book].classification) {
                        // the book classification has been updated
                        // TODO: Runs allVotes
                        removeGlobalVote(book, cl);
                    }
                    user_votes[msg.sender].data[book].classification = cl;
                } else {
                    user_votes[msg.sender].data[book].classification = cl;
                    user_votes[msg.sender].data[book].initialized = true;
                }
                addGlobalVote(book, cl);
            }
        }
    }

    function getVotes() public view returns (uint256[] memory, uint256[] memory, uint256[] memory, uint256[] memory, uint256[] memory) {
        return (ids, illegal, poor, ok, great);

    }
}


1 Like

Hello @lawpond , let me go trough it tomorrow morning and will let you know my findings :slight_smile:

3 Likes

@EMerchant @OnChainVision @WindsOfChange92 :pray:

2 Likes

You can ask your developer questions in TRON developers telegram chat link Telegram: Contact @TronOfficialDevelopersGroupEn You may find your answers there faster.

2 Likes

The Boss at the top Can be of good help @Prince-Onscolo .

1 Like

Good day ! I hope you had a great weekend ! Any luck? I am going to look into it tomorrow.

1 Like

Thank you, just did it few hours ago :wink:

1 Like

looking at one of the failed transaction when you click on the RED “FAILED-TRANSACTION-REVERTED” a pop up will appear and wil show you the reaseon

check the contract logic for that particular failed

thanks,
onchaindev :white_heart:

2 Likes

Hello @lawpond , I’ve been testing your smart contract in my bench and have some clues already:

  1. First, I created a sandbox project using tronweb/tronbox: You can find it here in case you need to follow the same steps as I did :slight_smile:
  2. I have succesfully used the voteBooks function (please check in your contract for details and trasanctions), however I could only add one data point, as soon as I add 2 books IDs the execution is reverted. This might be due to a low MaxLimitFee or wrong logic implementation in your code. What I did is to only send one ID + 1 classification as follows(check line 21):

If you vote for books one by one it works, however the energy used per transaction is too big, thus I assume when you are trying to add more books the TRX needed for energy exceeds your contract maxLimitFee and thus it reverts. Please check into optimizing your logic and also increasing your maxFeeLimit. Let me know if this info works for you :slight_smile:

3 Likes

Legend !

I will study maxFeeLimit and also do some cost optimization !

1 Like

Anytime! Yes check for any loops and other time / data consuming operations in your logic to save some fees

1 Like

Wao this is amazing, keep on with the good work buddy

1 Like

OK, so I have made some progress, thanks to @SimbadMarino :wink:

I went and checked his code here and start experimenting with having one smart contract per Book.

I also didn’t see there was a docker image tronbox/tre that is cool for quick iterations.

So not a significant progress, beacause now, the new strategy sounds like it would cost me around 35.000$ to deploy 5000 smart contract (one per book)…

I don’t think I can optimize the code more than that, I simplified it to its maximum:

An example here (Git coming soon)

pragma solidity >=0.7.0 <0.9.0;

/**
Illegal = 1
Poor = 2
Good = 3
Great = 4

*/

contract BookVote {

    uint16 public nbGood = 0;
    uint16 public nbGreat = 0;
    uint16 public nbPoor = 0;
    uint16 public nbIllegal = 0;

    mapping(address =>uint8) public userVotes;

    uint16 public bookId;

    constructor(uint16 id ) {
        bookId = id;
        // register itself to Main contract
    }

    function voteGreat() public {
        uint8 current = userVotes[msg.sender];
        removeVote(current);
        userVotes[msg.sender] = 4;
        nbGreat++;
    }

    function removeVote(uint8   current) internal {
        if (current != 0) {
            // The user has already voted for this book, and changed its mind
            if (current==1) {
                nbIllegal--;
            } else if (current==2) {
                nbPoor--;
            } else if (current==3) {
                nbGood--;
            } else if (current==4) {
                nbGreat--;
            } else {

            }
        }
    }

    function voteGood() public {
        uint8 current = userVotes[msg.sender];
        removeVote(current);
        userVotes[msg.sender] = 3;
        nbGood++;
    }

    function votePoor() public {
        uint8 current = userVotes[msg.sender];
        removeVote(current);
        userVotes[msg.sender] = 2;
        nbPoor++;
    }
    function voteIllegal() public {
        uint8 current = userVotes[msg.sender];
        removeVote(current);
        userVotes[msg.sender] = 1;
        nbIllegal++;
    }
}


This contract costs about 7$ to deploy.

Then, I have this one:


pragma solidity >=0.7.0 <0.9.0;

import './BookVote.sol';

contract BookVoteFactory {

    BookVote[] public listOfBooks;

    function createBookVoteContract(uint index) public {
        listOfBooks.push(new BookVote(uint16(index)));
    }

    constructor() {
        for (uint i=0; i<5000; i++) {
            createBookVoteContract(i);
        }
    }
}


Money wise, anyone see a better approach? 5000 is the number of books I have in the library currently.

ok, so I went the really simplistic way.

I will however need to find a way to stop user for voting more than once for the same book…
But now, fees looks alright.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract BookVotes {

    // 5000 books, 4 classification per books => 4*5000 = 20.000
    uint16[20000] public votes ;
    
    constructor() {
    }

    function getVotes(uint16 bookId) public view returns (uint16, uint16, uint16, uint16) {
        return (votes[bookId*4], votes[bookId*4+1], votes[bookId*4+2], votes[bookId*4+3]);
    }

    function voteBook(uint16 bookId, uint8 cl) public {
        votes[bookId*4+cl]++;
    }

    function voteBooks(uint16[] memory bookIds, uint8[] memory classifications) public {
        for (uint i = 0; i < bookIds.length; i++) {
            voteBook(bookIds[i], classifications[i]);
        }
    }
}
1 Like

Hello again @lawpond ! glad you find out a more efficient way to deal with your contract :slight_smile:

What I would do is to save both the ID and voter address in a mapping, later on you can look for that address by simply getting getVoterAddress writing dow the bookID


    mapping (uint16 bookID => address voter  ) public getVoterAddress;

     //You will have to modify your functions to something like this:


 function voteBook(uint16 bookId, uint8 cl) public {
            require(msg.sender != getVoterAddress(bookId), "voter already voted for this book, try with another book");
            _; //Underscore and ; after the require means require will be executed before the called fuction is called.
            votes[bookId*4+cl]++;
            getVoterAddress[bookId] += msg.sender;     //This should link both the voter address with bookID
    }

Please consider I did not compile the provided code, so it might not work out of the box, this is just to give you an idea on how to achieve what you want to do, the require keyword is used to check wether certain conditions are met in order to execute the function or not to avoid users on spending fees if conditions are not going to be met anyways.

keywords to look for additional docs, info: require, mapping, msg.sender,

Wish you luck!
Feel free to ask if additional support is needed.

@SimbadMarino - thank you ! I will give that pattern a try later in the week. Right now, I am focused on redesigning the leveling system from the ground up.

I have released the MVP v1 yesterday, if you’d like to have a peek @ it :
Enter the Library - Start New Game and let me know your first impressions.

Good day and a good week :smiling_face:

1 Like

Good topic!! I follow it