0

Edit: to be clear, the getMessage() function works fine in remix. It just doesnt seem to work with my front end.

I am trying to set up a front end for my contract but when trying to invoke a certain function that returns a string, I get the following error in my web console:

Error: ERROR: The returned value is not a convertible string: formatters.js:261

The error seems to occur when I invoke the getMessage function here (in App.js):

async componentDidMount() {
const host = await chatroom.methods.host().call();
const members = await chatroom.methods.getMembers().call();
const chatLoglength = await chatroom.methods.getMessagesLength().call();
const chatLog = [];

for (let i = 0; i < chatLoglength; i++) {
  const newMessage = await chatroom.methods.getMessage(i+1).call();
  chatLog.push(newMessage);
}

this.setState({ host, members, chatLog });
}

I am using web3 version 1.0.0-beta.35. I have confirmed that I instantiated the contract with the correct bytecode and ABI. I am not sure why I have been able to return strings from other functions but not this one. Any help would be greatly appreciated. Code below.

Contract:

pragma solidity ^0.4.25;

contract Chatroom {
address public host;
string private password;

address[] public members;
Message[] public chatLog;

mapping(uint => Message) msgIDPair;
mapping(address => bool) isMember;

struct Message {
    address author;
    string content;
}

// @notice Creates the chat-room. Host role is given to the address of the sender
// @dev The password could be inferred from the constructor argument, not strong security
// @param _password The password the host wishes to set for the chat-room
constructor(string _password) public {
    host = msg.sender;
    addMember(host); // adds host address to members array

    password = _password;
}

// @notice Send a message `(_message)` to the chat-room (must be a member)
// @param _message The content of the message to be sent
function sendMessage(string _message) external mustBeMember {
    uint msgID = chatLog.length + 1;

    msgIDPair[msgID] = Message(msg.sender, _message); // pairs message ID with Message struct object
    chatLog.push(msgIDPair[msgID]); // adds Message object to chatLog array
}

// @notice Retrieve a message via ID `(_ID)`
// @dev Returns struct of Message, use front-end to get specific properties
// @param _ID The ID assigned to the desired message
// @return The target message
function getMessage(uint _ID) public view mustBeMember returns(string) {
    return(msgIDPair[_ID].content);
}

// @notice Check if an address is a member
// @param _target The address to be checked
// @return true if the target address is a member, false otherwise
function checkMember(address _target) public view returns(bool) {
    if (isMember[_target] == true) { // returns true if address has a "true" value assigned in isMember mapping table
        return(true);
    }
    else { // returns false if address does not have a "true" value assigned in isMember mapping table
        return(false);
    }
}

modifier mustBeMember() {
    require(checkMember(msg.sender) == true);
    _;
}


// @notice Become a member the chat-room (requires `(password)`)
// @param _password The password to evaluate
function joinChat(string _password) public requirePassword(_password) {
    addMember(msg.sender);
}

// @notice Leave the chat-room (must be a member)
function leaveChat() public mustBeMember {
    require(msg.sender != host); // host cannot leave, must transfer role first

    for (uint i = 0; i < members.length; i++) { // loops through entire members array, deletes matching address
        if (members[i] == msg.sender) {
            swapReduceIndex(members, i);
        }
    }

    isMember[msg.sender] = false;
}

// @notice Add a new member address that is not already a member
// @dev This is a helper function
// @param _newMember The address to be granted membership
function addMember(address _newMember) private {
    if (isMember[_newMember] == true) { // does nothing if address is already a member
        return();
    }
    else { // adds address to isMember mapping table and pushes the address to the members array
        isMember[_newMember] = true;
        members.push(msg.sender);
    }
}

// @notice Retrieve a list of all members
// @return A list of all member addresses
function getMembers() public view returns(address[]) {
    return(members);
}

modifier requirePassword(string _password) {
    require(keccak256(password) == keccak256(_password));
    _;
}

modifier onlyHost {
    require(msg.sender == host);
    _;
}

// @notice Remove a member (requires 'host' status)
// @param _member Address of the member to be removed
function kickMember(address _member) external onlyHost {
    require(msg.sender != _member); // host cannot kick himself

    for (uint i = 0; i < members.length; i++) { // loops through entire members array, deletes matching address
        if (members[i] == _member) {
            swapReduceIndex(members, i);
        }
    }

    isMember[_member] = false;
}

// @notice Transfer 'Host' role to another member (requires 'host' status)
// @param newHost The address of the member to be granted the 'host' role.
function switchHost(address newHost) external onlyHost {
    require(checkMember(newHost));

    host = newHost;
}

// @notice Delete index of array, swap last index with deleted, remove last index
// @dev Only works with address arrays, inteded to be used as a helper function with removing member addresses
// @param array The array in which to modify
// @param _blankIndex The number of the index to be deleted
function swapReduceIndex(address[] storage array, uint _blankIndex) internal {
    delete array[_blankIndex];
    uint lastIndex = array.length-1;
    array[_blankIndex] = array[lastIndex];
    array.length--;
}

// @notice Get length of chatLog array
// @dev Useful for displaying messages on front-end
function getMessagesLength() external view returns (uint) {
  return(chatLog.length);
}

}

App.js (formatting problems, had to use github link): https://github.com/PresidentPorpoise/chatroom-react/blob/master/src/App.js

1 Answer 1

1

Would the mustBeMember modifier on getMessage() have something to do with this?

I have found that the way permission based modifiers work with view functions can be a bit strange. As in you wont see a throw but you will get back some other data.

In this case... the contract might be expecting a string returned no matter what because it is a view function, when it is in fact throwing due to not meeting modifier permission requirements... meaning it will be returning a throw instead of a string.

3
  • a little addition to this: 1. To be more specific, I am saying that maybe you are not calling that contract method as a member. 2. Is this modifier really needed? You know that anyone can see any storage on the contract (including your passwords): github.com/ethereum/wiki/wiki/… Oct 21, 2018 at 8:46
  • It's not really needed, but I added it for complexity because I'm mostly doing this for practice. However, if the function works in remix, and I've been successful at using the sendMessage() function from the front end (uses the same modifier), I don't think this is the issue. I'll try it though to make sure.
    – Bryce
    Oct 21, 2018 at 13:29
  • Removed a lot of the complexity, including the onlyMembers modifier, and it works now. Not sure why, but thanks a lot.
    – Bryce
    Oct 21, 2018 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.