Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Truffle tests fail on Solidity 0.4.23 but work on Remix #936

Closed
arnoudbevers opened this issue May 4, 2018 · 12 comments
Closed

Truffle tests fail on Solidity 0.4.23 but work on Remix #936

arnoudbevers opened this issue May 4, 2018 · 12 comments

Comments

@arnoudbevers
Copy link

arnoudbevers commented May 4, 2018

  • [N] I've asked for help in the Truffle Gitter before filing this issue.

Issue

Methods that worked in Solidity version 0.4.20 do not work in 0.4.23, while they do work in Remix.
Only thing that has been changed in a contract is constructor - rest stayed the same.

Steps to Reproduce

Run the ./testRootPerson.js test with both 0.4.20 and 0.4.23 compiler (changing pragma's in contracts).

Expected Behavior

Calling get functions on contracts work, all tests should run.

Actual Results

Tests fail, due to get methods returning 0x values instead of actual values.

  • When running the attached testRootPerson.js test, certain tests return null values when run with Solidity 0.4.23 compiler.
  • When running tests with 0.4.20 compiler (npm install solc@0.4.20 in truffle node_modules) they succeed.
  • Copying the contracts into Remix, selecting the 0.4.23 compiler, and calling the methods on the contracts, they work.
  • Truffle compiler gives a different bytecode output than Remix does using compiler 0.4.23+commit.124ca40d.Emscripten.clang
    Code supplied at bottom of issue

0.4.20:
afbeelding

0.4.23:
afbeelding

Environment

  • Operating System: Windows 10 x64 - Ubuntu 16.04 on Virtual Machine
  • Ethereum client: Local 3 node Quorum network on VM
  • Truffle version (truffle version): Truffle v4.1.7 (core: 4.1.7) - Solidity v0.4.23 (solc-js)
  • node version (node --version): v8.9.4
  • npm version (npm --version): 5.6.0

Person.sol

pragma solidity ^0.4.23;

contract Person {

    bytes32 public name;
    struct ContactInformation {
        bytes32 street;
        uint number;
        bytes32 postalCode;
        bytes32 city;
        bytes32 country;
        bytes32 phoneNumber;
    }

    ContactInformation public contactInfo;

    constructor(
        bytes32 _name, bytes32 _street, uint _number, bytes32 _postalCode,
        bytes32 _city, bytes32 _country, bytes32 _phoneNumber) public {
        name = _name;
        contactInfo = ContactInformation(_street, _number, _postalCode, _city, _country, _phoneNumber);
    }

    function getStreet() public view returns (bytes32) {
        return contactInfo.street;
    }

    function getNumber() public view returns (uint) {
        return contactInfo.number;
    }

    function getPostalCode() public view returns (bytes32) {
        return contactInfo.postalCode;
    }

    function getCity() public view returns (bytes32) {
        return contactInfo.city;
    }

    function getCountry() public view returns (bytes32) {
        return contactInfo.country;
    }

    function getPhoneNumber() public view returns (bytes32) {
        return contactInfo.phoneNumber;
    }
}

Root.sol

pragma solidity ^0.4.23;

import "./Person.sol";

contract Root {
    
    Person[] public persons;
    mapping(address => bytes32) public personAddressToName;
    
    
    function addPerson(address _personAddress) public {
        require(_personAddress != address(0));
        Person person = Person(_personAddress);
        persons.push(person);
    }

    function getPersonByAddress(address _personAddress) public view
    returns(address personAddress, bytes32 personName, bytes32 street, uint number, bytes32 postalCode,
    bytes32 city, bytes32 country, bytes32 phoneNumber) {
        Person p = Person(_personAddress);
        require(p != Person(0x0));
        personName = p.name();
        street = p.getStreet();
        number = p.getNumber();
        postalCode = p.getPostalCode();
        city = p.getCity();
        country = p.getCountry();
        phoneNumber = p.getPhoneNumber();
        return (_personAddress, personName, street, number, postalCode, city, country, phoneNumber);
    }

    function getAllPersons() public view returns (Person[]) {
        return persons;
    }

    function getPersonAddressesAndNames() public view returns (Person[] addresses, bytes32[] memory names) {
        names = new bytes32[](persons.length);
        for (uint i = 0; i < persons.length; i++) {
            Person p = Person(persons[i]);
            names[i] = p.name();
        }
        return (persons, names);
    }

}

testRootPerson.js

const Root = artifacts.require('Root');
const Person = artifacts.require('Person');
const Web3 = require('web3');

const develop = '/*address*/';
const web3 = new Web3();
let rootInstance;
let personInstance;

contract('Root + Person', () => {
  it('should correctly add Person to root contract', () =>
    // 1. Deploy new root contract
    Root.deployed()
      .then((deployedInstance) => {
        // 2.1 Check if contract has been deployed
        assert.notEqual(deployedInstance.contract.address, '0x0000000000000000000000000000000000000000', 'Root address should not be address(0) - incorrectly deployed');
        // 2.2 Save root instance
        rootInstance = deployedInstance.contract;
        // 3. Deploy new Person contract
        return Person.deployed();
      })
      .then((deployedInstance) => {
        // 4.1 Check if contract has been deployed
        assert.notEqual(deployedInstance.contract.address, '0x0000000000000000000000000000000000000000', 'Person address should not be address(0) - incorrectly deployed');
        // 4.2 Save Person instance
        personInstance = deployedInstance.contract;
        // 5. Call addPerson()-function on root contract - transaction function!
        return rootInstance.addPerson(personInstance.address, { from: develop });
      })
      // 6.1 Call getAllPerson()-function on root contract
      .then(() => rootInstance.getAllPersons.call({ from: develop }))
      .then((result) => {
        console.log('allPersons', result);
        // 6.2 Check if array length is 1 - only one Person has been added
        assert.equal(result.length, 1, 'Person should be added to array');
        // 6.3 Check if value in array is correct
        assert.equal(result[0], personInstance.address, 'Correct address should be added to the array');
        // 7. Call getPersonByAddress()
        return rootInstance.getPersonByAddress.call(result[0], { from: develop });
      })
      .then((result) => {
        console.log('getPersonByAddress', result);
        // 7.1 Check if values are correct
        assert.equal(result[0], personInstance.address, 'Person address should be correct');
        assert.equal(web3.utils.hexToUtf8(result[1]), 'John Doe', 'Person name should be correct');
        assert.equal(web3.utils.hexToUtf8(result[2]), 'Main Street', 'Person street should be correct');
        assert.equal(result[3], 1, 'Person street number should be correct');
        assert.equal(web3.utils.hexToUtf8(result[4]), '1234-56', 'Person postal code should be correct');
        assert.equal(web3.utils.hexToUtf8(result[5]), 'New York', 'Person city should be correct');
        assert.equal(web3.utils.hexToUtf8(result[6]), 'United States', 'Person country should be correct');
        assert.equal(web3.utils.hexToUtf8(result[7]), '+12-34-5678-90', 'Person phone number should be correct');
        return rootInstance.getPersonAddressesAndNames.call({ from: develop });
      })
      .then((result) => {
        console.log('getPersonAddressesAndNames', result);
        // 7. Check Person array, if filled and correct value
        assert.equal(result[0].length, 1, 'Person should be added to array');
        assert.equal(result[0][0], personInstance.address, 'Person address should be correct value');
        // 8. Check names array, if filled and correct value
        assert.equal(result[1].length, 1, 'Person should be added to array');
        assert.equal(web3.utils.hexToUtf8(result[1][0]), 'John Doe', 'Person name should be correct value');
      })
      .catch(error => console.log(error)));
});
@cgewecke
Copy link
Contributor

cgewecke commented May 4, 2018

@arnoudbevers Could you show the code where you are instantiating a Person e.g. running its constructor?

@arnoudbevers
Copy link
Author

arnoudbevers commented May 4, 2018

Truffle uses this to deploy a contract to run the tests, in my own project I have created a contract handler that uses the web3 methods, but this is not used in truffle. Is this what you mean?

2_person_migration.js

const Web3 = require('web3');
const Person= artifacts.require('./Person.sol');
const web3 = new Web3();

module.exports = (deployer) => {
  deployer.deploy(
    Person, web3.utils.utf8ToHex('John Doe'), web3.utils.utf8ToHex('Main Street'), 1, web3.utils.utf8ToHex('1234-56'), web3.utils.utf8ToHex('New York'), web3.utils.utf8ToHex('United States'), web3.utils.utf8ToHex('+12-34-5678-90'));
};

@cgewecke
Copy link
Contributor

cgewecke commented May 4, 2018

@arnoudbevers Yes. Will investigate this - thanks for such a detailed and clear report!

@arnoudbevers
Copy link
Author

@cgewecke Thank you! I've been scratching my head for the last two days... If you're not planning on doing so, I can maybe test it on a Ganache network? Or are you doing that as well?

@cgewecke
Copy link
Contributor

cgewecke commented May 4, 2018

@arnoudbevers Yes I will test on ganache, and if the problem isn't present there I'll test on geth dev as well and see if I can reproduce.

@cgewecke
Copy link
Contributor

cgewecke commented May 5, 2018

@arnoudbevers Hi. I couldn't find any variance around compiler version and was able to get the tests passing on geth dev. (I added deployer.deploy(Root) to your migrations).

In the tests, you are assigning a web3 contract instance to rootInstance and then calling its methods directly:

// 2.2 Save root instance
rootInstance = deployedInstance.contract; // <-- web3 instance, not TruffleContract instance
...
// 4.2 Save Person instance
personInstance = deployedInstance.contract; // <-- web3 instance, not TruffleContract instance
...
// 5. Call addPerson()-function on root contract - transaction function!
return rootInstance.addPerson(personInstance.address, { from: develop });

That's fine, but one thing truffle-contract does behind the scenes is wait for transactions to be mined and return a receipt. Web3 behaves differently, immediately returning a transaction hash. The next steps in the test are being run before addPerson has had a chance to finish execution on the chain. Could you see if changing 2.2 and 4.2 to the following fixes the test for you?

rootInstance = deployedInstance; 
personInstance = deployedInstance; 

@arnoudbevers
Copy link
Author

@cgewecke I changed my migrations, and changed the two instances in my tests, but the methods still do not return values. What do you mean by geth dev? Did you use geth CLI? How would I go about testing this myself? Since this is the only thing that I can think of, updating my geth version on my machine.

@arnoudbevers
Copy link
Author

arnoudbevers commented May 7, 2018

@cgewecke I've talked a bit with others, and we think the issue lies with Quorum. The network is running on Geth 1.7.2, but Quorum is not up to date with the latest geth version, so updating Geth is not an option I think - which is also the reason it works on Remix/Ganache, since that uses Ethereum instead of Quorum. Do you have any idea what I can do to remedy this?

@arnoudbevers
Copy link
Author

arnoudbevers commented May 7, 2018

@cgewecke I have also tried it on the Ropsten network to eliminate another possibility, and it worked as expected, so I think the issue really lies with Quorum, and that it is not up to date with the latest geth version/Solidity version.

@cgewecke
Copy link
Contributor

cgewecke commented May 7, 2018

Thanks @arnoudbevers, unfortunately I don't know of a remedy. You might raise this at Quorum in case they know of a work around.

Is it ok if we close this issue since it's specific to an older Geth version?

@cgewecke
Copy link
Contributor

cgewecke commented May 8, 2018

@arnoudbevers Just leaving a note here about geth dev since that wasn't clear. You can run a geth test client locally with very little setup by:

$ docker pull ethereum/client-go:latest  #`latest` is a version. See hub.docker for more.

$ docker run -it -p 8545:8545 \
                 -p 30303:30303 \
                 ethereum/client-go:latest \
                 --rpc --rpcaddr "0.0.0.0" \ 
                 --dev --dev.period 2

It comes with a single funded unlocked account. It's slower than ganache but can be quite helpful for debugging when you are getting different outcomes on different clients.

@arnoudbevers
Copy link
Author

arnoudbevers commented May 8, 2018

@cgewecke

Is it ok if we close this issue since it's specific to an older Geth version?

Yes, this is fine. I will try to check with Quorum when support for newer versions is coming.

Just leaving a note here about geth dev since that wasn't clear. You can run a geth test client locally with very little setup by...

Thank you! I will keep this in mind, will be really useful in my development.

Thanks for your help!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants