I want to test a function which returns struct of array.
This is sample code.
struct Hoge {
uint id;
string text;
}
・・・
constructor() public {
hoges.push(Hoge(1, "Hogehoge"));
}
・・・
function hogehoge() external view returns(Hoge memory) {
return hoges[0];
}
And my test is this.
var Sample = artifacts.require('./Sample.sol');
contract('sample', function(accounts) {
it('facilitates number of place and check-in', function() {
return Sample.deployed().then(function(instance) {
sampleInstance = instance;
return sampleInstance.hogehoge()
}).then(function(result) {
hoges = result;
assert.equal(hoges.id, 1);
})
})
})
However, error shows
invalid solidity type!: tuple
The function which return struct of array needs ABIEncoderV2.
I heard that web3 is trying to support ABIEncoderV2, but I am not sure web3 supports ABIEncoderV2 now.
My version is this:
Truffle v4.1.15 (core: 4.1.15)
Solidity v0.4.25 (solc-js)
Could you give me any advice how to test my code, please?
Starting with truffle v5, ABIEncoderV2 is supported.
I would suggest upgrading from your current version to v5.
https://truffleframework.com/blog/truffle-v5-has-arrived
Related
I'm trying to test a factory contract using hardhat and waffle. I have a contract called Domain:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "hardhat/console.sol";
contract Domain {
string private publicKey;
address[] public children;
constructor(string memory _publicKey) {
console.log("Deploying a domain using public key: ", _publicKey);
publicKey = _publicKey;
}
function getChildren() public view returns (address[] memory){
return children;
}
}
And a factory for deploying this contract:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "./Domain.sol";
import "hardhat/console.sol";
contract DomainFactory {
Domain[] private _domains;
function createDomain(
string memory _publicKey
) public returns (address){
Domain domain = new Domain(
_publicKey
);
_domains.push(domain);
return address(domain);
}
function allDomains(uint256 limit, uint256 offset)
public
view
returns (Domain[] memory coll)
{
return coll;
}
}
I have the following tests defined, where this refers to a context object defined in a "world" file (using cucumber.js.
When('the holder of this public key creates a domain', async function () {
this.domain = await this.factory.createDomain('<public_key>');
});
Then('a child of this domain has the name name', async function () {
const children = this.domain.getChildren();
const childrenWithName = children.find((child:any) => {
return child.getNames().some((childName:any) => {
return childName === 'name';
})
})
expect(childrenWithName).to.be.an('array').that.is.not.empty;
});
Ideally in the when step, I could define this.domain as the result of deploying a contract, and thereafter test the methods of the contract I deploy:
// world.ts
import { setWorldConstructor, setDefaultTimeout } from '#cucumber/cucumber'
import {deployContract, MockProvider, solidity} from 'ethereum-waffle';
import {use} from "chai";
import DomainContract from "../../../artifacts/contracts/Domain.sol/Domain.json";
import DomainFactoryContract from "../../../artifacts/contracts/DomainFactory.sol/DomainFactory.json";
import { Domain, DomainFactory } from "../../../typechain-types";
import {Wallet} from "ethers";
use(solidity);
setDefaultTimeout(20 * 1000);
class DomainWorld {
public owner: string
public wallets: Wallet[]
public factory: DomainFactory | undefined
public domain: Domain | undefined
public ready: boolean = false
private _initialized: Promise<boolean>
async deployContractByAddress(address, ...args){
return await deployContract(this.wallets[0], address, ...args);
}
constructor() {
this.wallets = new MockProvider().getWallets();
this.owner = this.wallets[0].address
const that = this
this._initialized = new Promise(async (resolve, reject) => {
try {
that.factory = (await deployContract(that.wallets[0], DomainFactoryContract, [])) as DomainFactory;
that.ready = true
resolve(true)
}catch (err) {
reject(err)
}
})
}
}
setWorldConstructor(DomainWorld);
My problem is, hardhat's deployContract function isn't expecting a contract address, which is what is returned by my DomainFactory's create method. How can I test contracts deployed via my factory if the return value is an address?
I've made a quick hardhat project for you to test it.
Here are the highlights:
The easiest way I find to get this returned valued of the contract offchain (and by offchain in this case, I mean inside your test environment) is by emitting an Event. So, I made the following change to your code.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "./Domain.sol";
import "hardhat/console.sol";
contract DomainFactory {
Domain[] private _domains;
event CreatedDomain(address domainAddress);
function createDomain(string memory _publicKey) public returns (address) {
Domain domain = new Domain(_publicKey);
_domains.push(domain);
emit CreatedDomain(address(domain));
return address(domain);
}
function allDomains(uint256 limit, uint256 offset)
public
view
returns (Domain[] memory coll)
{
return coll;
}
}
I just simply emit a CreatedDomain() event containing the deployed Domain address.
One more thing: if I remember correctly, you can only retrieve values direct from function returns offchain, if your function is of type view. Otherwise, you will need to emit the event and then find it later.
In order to test the Domain deployed by the DomainFactory take a look at this test script:
import { expect } from "chai";
import { ethers } from "hardhat";
import { Domain, DomainFactory } from "../typechain";
describe("Domain", function () {
let domainFactory: DomainFactory;
let domain: Domain;
let domainAddress: string;
it("Should deploy a DomainFactory ", async () => {
const DomainFactory = await ethers.getContractFactory("DomainFactory");
domainFactory = await DomainFactory.deploy();
await domainFactory.deployed();
});
it("deploy a Domain using DomainFactory ", async () => {
const tx = await domainFactory.createDomain("public string here");
const rc = await tx.wait();
const event = rc.events?.find((event) => event.event === "CreatedDomain");
const args = event?.args;
if (args) domainAddress = args[0];
});
it("attach an abi interface to the deployed domain", async () => {
const Domain = await ethers.getContractFactory("Domain");
domain = await Domain.attach(domainAddress);
});
it("get data from Domain deployed by DomainFactory ", async () => {
const res = await domain.getChildren();
console.log(res);
});
});
It deploys a DomainFactory then uses the createDomain() method, fetches the deployed address from the functino events, then use it to attach the ABI to the deployed Domain.
Full code here:
https://github.com/pedrohba1/stackoverflow/tree/main/Domain
Anything else related to running it I will be adding in the comments.
I have a contract deployed onchain:
function getNonces(bytes32 _hashWalletSig, uint256[] calldata _dataTypes)
public
view
virtual
returns (uint8[] memory, bytes32[] memory)
{
...
}
If I point the remix editor to this contract and call the getNonce() function directly with the parameters:
0xa58d6abf78c9492df62daf0e251c6af49d5101eaadf2c4786e277a8da97f9f73 and [0]
I get my expected result.
However, I have a frontend application using Web3Js that attempts to call the getNonce function but I'm getting a completely different value (i.e. 0x00000000) which indicates that nothing is found:
export const getBlockchainStatus = async (walletSignature: string): Promise<boolean> => {
const MainContract = new web3.eth.Contract(
Ky0xMainAbi as AbiItem[],
CONTRACT_ADDRESS,
);
const hashwalletSig = ethers.utils.keccak256(walletSignature);
console.log('hashwalletSig:', hashwalletSig);
const response = await MainContract.methods.getNonces(hashwalletSig, ['0']).call();
console.log('response', response[1][0]);
return response[1][0] !== '0x0000000000000000000000000000000000000000000000000000000000000000';
};
The hashWalletSig matches the original value that I passed into the remix editor.
Can someone tell me what I'm doing wrong in Web3JS?
I want to add a struct to an array and get the first entry from a other function and its not working with web3, but If I add the struct to the array in the same function web3 is working and returns the entry as expected:
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
contract Payback {
struct Address {
uint256 id;
string name;
address _address;
}
Address[] addresses;
function addAddress() external {
Address memory newAddress = Address(
1,
"Test",
0xDEE7796E89C82C36BAdd1375076f39D69FafE252
);
addresses.push(newAddress);
}
function getAddress() external view returns (Address memory) {
return addresses[0];
}
}
My Test is working:
it('gets Address', async () => {
await paybackInstance.addAddress()
let value = await paybackInstance.getAddress()
assert.equal(value[0], "1")
assert.equal(value[1], "Test")
assert.equal(value[2], "0xDEE7796E89C82C36BAdd1375076f39D69FafE252")
});
But if I want to return it with web3 I get an error:
"VM Exception while processing transaction: invalid opcode"
let addresses = await contract.methods.getAddress().call()
console.log(addresses)
I guess I do something wrong with storage/memory but not really understanding it because my test is passing...
It looks like, with web3, you are not adding the struct to array and trying access value at index 0 which does not exist because the array's length is zero.
I'm trying to call a simple contract method that just returns a string of data. I've based my code on the example that can be found in the docs => https://developers.tron.network/reference#methodcall
tronWeb.trx.getContract("TFWbGYFVjUMKrHALdU4MnFWNYY9Uc5W9SZ").then(async contract => {
console.log(contract);
let abi = contract.abi;
console.log(abi);
let c = await tronWeb.contract({
abi
});
let result = await c.getBadgeOwner('something is up').call();
console.log(result);
});
The difference with what can be found in the docs, is that I'm loading the abi from my loaded contract, instead of hard coding it like in the example.
The error I get is index.js:105 Uncaught (in promise) TypeError: e.forEach is not a function which seems to refer to the abi somehow:
For anyone tripping over the same beginner mistake, here's how to solve it:
Use contract().at() instead of getContract()
let contract = await tronWeb
.contract()
.at("TFWbGYFVjUMKrHALdU4MnFWNYY9Uc5W9SZ")
After that, you can call your contract methods just fine
let currentValue = await contract.getBadgeOwner('something is up').call();
setTimeout(async () => {
this.myContractOb = await
this.tronWeb.contract(myContract).at(this.contractAddress);
},10000);
Using above code with myContract as ABI json object having same issue.
I was doing the same mistake before. This works for me
async function a (){
let contract = await tronWeb.contract().at("TFWbGYFVjUMKrHALdU4MnFWNYY9Uc5W9SZ")
//console.log(contract);
let currentValue = await contract.getBadgeOwner('something is up').call();
console.log(currentValue);
}
a()
I have a stupid smart contract like this:
pragma solidity ^0.4.24;
contract ProdottoFactory {
function foo() view returns(string nome){
return "foo";
}
}
And I want to test it with chai
var Prodotto = artifacts.require("ProdottoFactory");
expect = require("chai").expect;
contract("descrizione primo test", function () {
describe("test 2", function () {
it("blablabla", function () {
return Prodotto.new().then(
istance => {
prodottoContract = istance;
}
)
})
})
})
contract("descrizione primo test2", function () {
describe("test 2 2", function () {
it("blablabla2",function () {
return prodottoContract.foo().then(function (res) {
expect(res.toString()).to.be.equal("foo")
})
})
})
})
When I run the command
truffle test
I have this error
Error: Attempting to run transaction which calls a contract function, but recipient address 0xe8f29e5c4ca41c5b40ed989439ddeae4d9384984 is not a contract address
truffle.js
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545, // Ganache GUI
network_id: "*" // Match any network id
}
}
};
contracts/ProdottoFactory.sol
pragma solidity ^0.4.24;
contract ProdottoFactory {
function foo() pure public returns(string nome){
return "foo";
}
}
test/ProdottoFactory.js
var pf = artifacts.require("ProdottoFactory");
contract('ProdottoFactory', function(accounts) {
var pfInstance;
before(function() {
return pf.new()
.then(function(instance) {
pfInstance = instance;
});
});
it("should return foo", function() {
return pfInstance.foo.call()
.then(function(str) {
assert.equal(str, "foo");
});
});
});
I made 2 small changes in your contract:
I added public keyword. It's good practice to always define the visibility of your function.
I replaced view to pure. When you are not reading from blockchain/state variable, use pure. More info can be found inside the docs here.
FYI, you don't have to require chai or mocha library. It's already there when you init a Truffle project using truffle init command. The before keyword is part of Mocha library. You can read more about it here.
Lastly, if you want to know the differences between new and deployed keyword in Truffle, read my thread here.