I've run into a real head scratcher with defing struct records/values, which is most likely a misunderstanding of something, or perhaps an issue with my local?

I've reverted to a very simple contract for testing, and yet still see this problem.


solc = 0.8.10 (upto 0.8.12)
truffle-cli = 5.4.24
ganache = 2.5.4


Trying to create a struct record in a contract method, and return the same record in a seperate call.


Returned struct values are always the type equivilent of empty/null (i.e. 0 for uint, as expected from documentation if the values aren't explicitly set) This issue isn't occuring with any other varable types, only structs with every way I've tried defining the record/values.


The output does not produce any contract errors.

I've tried recreating the Ganache workspace, for when running truffle test with Ganache

Inital newCampaign()

Always returns the CORRECT value

BN {
  negative: 0,
  words: [ 222, <1 empty item> ],
  length: 1,
  red: null

Trying the get the value again

Always returns the default empty values:

campaign = BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }


Code samples below contain commented attempts, where I am working with different ways to define/call a struct record

const { expect, assert } = require('chai');

// Import utilities from Test Helpers
const { BN, expectEvent, expectRevert, constants } = require('@openzeppelin/test-helpers');

// Load compiled artifacts
const StructTest = artifacts.require('StructTest');

// Start test block
contract('StructTest', function ([ creator, other ]) {
  let contract;

  before(async function () {
    accounts = await web3.eth.getAccounts();
    contract = await StructTest.new({ from: creator });

  it('should return the wtf is going on', async function () {
    let expected, actual;

    // Call method to create struct record
    actual = await contract.newCampaign.call(
      {from: accounts[0]}

    // Try calling public campaign
    const campaign = await contract.campaigns(actual);
    console.log('campaign =', campaign);

    // Try return struct values, via method
    actual = await contract.getCampaign.call(
      {from: accounts[0]}


Based off of https://docs.soliditylang.org/en/latest/types.html#structs

Code samples below contain commented attempts, where I am working with different ways to define a struct record

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StructTest {

  // Structs can also be defined inside contracts, which makes them
  // visible only there and in derived contracts.
  struct Campaign {
      // address beneficiary;

      uint fundingGoal;

      // uint numFunders;
      // uint amount;
      // mapping (uint => address) funders;

  uint numCampaigns;
  mapping (uint => Campaign) public campaigns;

  // Campaign[] public campaigns;

  function newCampaign() public returns (
    uint campaignID
    ) {
      uint fundingGoal = 222;

      // ATTEMPT 1
      // Campaign memory campaign = Campaign(fundingGoal);
      // campaigns.push(campaign);

      // ATTEMPT 2
      Campaign storage c = campaigns[0];
      c.fundingGoal = fundingGoal;
      return c.fundingGoal;

  function getCampaign(uint campaignID) public returns (
    uint _fundingGoal
    ) {
      Campaign storage c = campaigns[campaignID];
      return c.fundingGoal;


I've tried solc versions


Ganache endpoints validated and working correctly, I have tried localhost

module.exports = {
  networks: {
    development: {
     host: "",     // Localhost (default: none)
     port: 7545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)

  // Configure your compilers
  compilers: {
    solc: {
      version: "0.8.12",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      settings: {          // See the solidity docs for advice about optimization and evmVersion
        optimizer: {
          enabled: true,
          runs: 200
      //  evmVersion: "byzantium"

1 Answer 1


The problem is that you are using call when creating the new campaign

actual = await contract.newCampaign.call(
  {from: accounts[0]}

any change to the contract's state is discarded.

To persist the changes in the contract remove the call. You won't be able to get the returned value because it is a transaction: What is the difference between a transaction and a call?.

await contract.newCampaign(
  {from: accounts[0]}

The recommended way to get a return value from a transaction is to use events: How to get values returned by non constant transaction functions?.

  • Events would be much better idea. Thank you!! I knew it would be something simple 😅
    – caedmon
    Feb 23, 2022 at 23:27

Your Answer

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

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