Published on

Attempting to Return Structs in Solidity version 0.5.0

Authors

Today I was looking through changes in Solidity 0.5.0 (there are large number of changes!) and noticed that one of them was related to interfaces being able to return structs:

...
 * General: Allow structs in interfaces.
 ...

This is one of the features I have been keen for since I started working with Solidity so I tried see if I could get this to work with the 0.5.0 pragma.

Initial Setup

First I created a new folder and initialized a truffle project:

> mkdir solv5StructReturnTest && cd $_
> truffle init

✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box

Unbox successful. Sweet!

Commands:

 Compile:        truffle compile
 Migrate:        truffle migrate
 Test contracts: truffle test

This created the following directory structure:

<root>
       -> contracts
          -> Migrations.sol
       -> migrations
          -> 1_initial_migration.js
       -> test
       truffle-config.js

I uncommented the dev section under truffle-config.js to make truffle connect to my Ganache instance:

...
  networks: {
   // Useful for testing. The `development` name is special - truffle uses it by default
   // if it's defined here and no other network is specified at the command line.
   // You should run a client (like ganache-cli, geth or parity) in a separate terminal
   // tab if you use this network and you must also set the `host`, `port` and `network_id`
   // options below to some value.
   //
   development: {
    host: "127.0.0.1",     // Localhost (default: none)
    port: 8545,            // Standard Ethereum port (default: none)
    network_id: "*",       // Any network (default: none)
   },
...

In another shell I ran ganache:

> ganache-cli
Ganache CLI v6.2.5 (ganache-core: 2.3.3)

Available Accounts
==================
(0) 0x5b116b89c5551cb00de971f954c8e575dcb012fc (~100 ETH)
(1) 0x9986a3c5066bf08e5f6cecede51d314312cd19e5 (~100 ETH)
(2) 0xeb6b69d48da30546c6dcf884d2189c88ba215ad8 (~100 ETH)
(3) 0x207404e0dec2cff8e253bfa36f25b61283322045 (~100 ETH)
(4) 0xeb436a0c6ffed09d25c7ba9bbfe695b4839ec1ca (~100 ETH)
(5) 0xe9447da70b84bebc475f676cb88bc4196ea9b51f (~100 ETH)
(6) 0x99c9e6f5595e44c02c3a3146199026a9ebc0ea5d (~100 ETH)
(7) 0x2f144e3a6a45f3f34c238d5b46aa9900f7646a82 (~100 ETH)
(8) 0x0e903b56162a2a4ba2679c358acef6bf3865be03 (~100 ETH)
(9) 0xa04a2ab38e3998c1f6c1b33ff1939d8c7f4386b6 (~100 ETH)

Private Keys
==================
(0) 0xfd17a042db795340ea25468cd0d6be905debc5c144e8557237ea7419f7fe166a
(1) 0x8347d844c4e9d60a426e18b4917b7496da259a8065d4df37f1db5738828d13ce
(2) 0x339f6341b39bb2fc52247fd69aece734f4f053b1b46e7e857ba7514413de7418
(3) 0xd0467ee7905728f69b641247e0587244da0242c63cf5b0173432e25df6d75c3a
(4) 0x569e5485acb3da45c4d426711fa8112714ba3cf7c9a2a75c28402986066cf18d
(5) 0x581023534378f9f690874a8594f477b934328ee16d2aa65e0e47810c8aa7d566
(6) 0xa79f196f9a7abd23293cd24546e4724a8741766bd2e35f2f8b93128df2217824
(7) 0x1536410426dd7371448dc972cbb8ccfa70c3a95a3b511af27c4e1d0641d2fa0d
(8) 0x1781ced80e8c8ace560c85f8d57be1dee296e91eaa9f872cf4837655fce47443
(9) 0x60bc1b9fa313acabf2aa77ecdc1da7f16f20ef472f12aa564f947ec68af69366

HD Wallet
==================
Mnemonic:      kid sport disagree act coconut paper major issue sport miracle toilet blame
Base HD Path:  m/44'/60'/0'/0/{account_index}

Gas Price
==================
20000000000

Gas Limit
==================
6721975

Listening on 127.0.0.1:8545

Setting Up The Experiment

Now that I have the foundation up I created a contract to test returning a struct from a public method:

touch contracts/SolVFive.sol

And wrote the code:

pragma solidity ^0.5.0;

contract SolVFive {

   struct Person {
       uint age;
       string name;
   }

   Person private person = Person({age: 33, name: "John"});

   function getPerson() public view returns (Person memory) {
       return person;
   }

   function getPersonName() public view returns (string memory){
       return person.name;
   }
}

To check if it works I created a test file and migration script:

touch test/solVFive.js
touch 2_deploy_contracts.js

The deployment script looked as follows:

var SolVFive = artifacts.require('SolVFive')

module.exports = function (deployer) {
  deployer.deploy(SolVFive)
}

And the test script:

const SolVFive = artifacts.require('../contracts/SolVFive.sol')

contract('SolVFive', (accounts) => {
  it("should return person's name as 'John'", () =>
    SolVFive.deployed()
      .then((instance) => instance.getPersonName.call())
      .then((name) => {
        console.log(`------Name:[${JSON.stringify(name)}]`)
        assert.equal(name, 'John')
      }))

  it("should return person object with name a 'John' and age 33", () =>
    SolVFive.deployed()
      .then((instance) => instance.getPerson.call())
      .then((person) => {
        console.log(`------Person:[${JSON.stringify(person)}]`)
      }))
})

When running truffle test I got the following output:

> truffle test
Using network 'development'.

Compiling ./contracts/SolVFive.sol...

/home/ymr/code/sandbox/blockchain/solidity/contracts/SolVFive.sol:13:47: TypeError: This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature.
   function getPerson() public view returns (Person memory) {
                                             ^-----------^
Compilation failed. See above.
Truffle v5.0.2 (core: 5.0.2)
Node v8.10.0

It looks like this is not available yet in v 0.5.0 :'(

But I do see there is a suggestion of using: pragma experimental ABIEncoderV2;

After switching to this and runnin the tests again I get:

truffle test
Using network 'development'.

Compiling ./contracts/SolVFive.sol...

Compilation warnings encountered:

/home/ymr/code/sandbox/blockchain/solidity/contracts/SolVFive.sol:2:1: Warning: Experimental features are turned on. Do not use experimental features on live deployments.
pragma experimental ABIEncoderV2;
^-------------------------------^
,/home/ymr/code/sandbox/blockchain/solidity/contracts/SolVFive.sol:2:1: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.5.0;"
pragma experimental ABIEncoderV2;
^ (Relevant source part starts here and spans across multiple lines).



  Contract: SolVFive
------Name:["John"]
    ✓ should return person's name as 'John'
------Person:[["33","John"]]
    ✓ should return person object with name a 'John' and age 33


  2 passing

Conclusion

Despite Solidity version 0.5.0 stating in the changelog that structs are allowed in interfaces, it does not work in a straight contract. This change is under development and works with the experimental pragma where the struct is returned as an array on the JavaScript side. This is still not ideal as it is hard to work with on the JS side although array destructuring could be used in JS to make it easier to work with single object return types e.g.

const [age, name] = person

For multiple object return types it is still pretty tricky to work with. I would also need to test this with the newest version of Geth to confirm for sure if this change works.