I want to test get my function TokenUri using hardhat.
Here it is:
function tokenURI(uint256 tokenId) public view virtual override returns (string memory){
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory currentBaseURI = _baseURI();
return bytes(currentBaseURI).length > 0 ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), ".json")) : "";
}
I want to query the URI of a token that does not exist. The test should fail and be reverted with "ERC721Metadata: URI query for nonexistent token"
Here is my test with hardhat (tokenId 1 does not exist):
it("should not work", async function () {
let uri = await this.deployedContract.tokenURI(1);
console.log(uri);
await expect(uri).to.be.revertedWith(
"ERC721Metadata: URI query for nonexistent token"
);
});
I npx hardhat test then I have this error "Error: missing revert data in call exception; Transaction reverted without a reason string" followed by a huge error block.
To let you understand what "This.deployedContract" is:
it("should deploy the smart contract", async function () {
const baseURI = "ipfs://cid/";
const Contract = await ethers.getContractFactory("test");
this.deployedContract = await Contract.deploy(baseURI);
});
Does someone encountered the same issue ?
This solution is working. I think the error came from the fact that I used "await" before "this.deployedContract.tokenURI(1)" which do not return a promise.
Related
I have a control that calls a service.
If the service returns an empty payload from the db I want to throw an exception.
at the moment I am doing that in the service:
this is the service I have at the moment with the exception.
async getPreferences(eUserId: string): Promise<UserPreferences> {
const userPreferences = await this.userPreferencesModel.findOne({
eUserId,
});
if (!userPreferences) {
throw new NotFoundException("We couldn't find your user preferences");
}
return userPreferences;
}
I want the controller to handle the exception, The issue is that the controller response is a Promise.
How can I handle that?
This is what I shave done:
#Get()
async getPreferences(
#Headers('x-e-user-id') eUserId: string,
): Promise<UserPreferences> {
const userPreferences = this.userPreferencesService.getPreferences(eUserId);
console.log('userPreferences: ', userPreferences);
// Here is what I am trying to monitor...
if (userPreferences) {
throw new NotFoundException("We couldn't find your user preferences");
}
return userPreferences;
}
Ther console.log in the controller returns:
userPreferences: Promise { <pending> }
Now, if the service response is empty no exception is thrown.
How can I monitor the service result in order to throw an exception
Multiple ways you can solve this. Here's one.
Don't throw an error in your service, just return the result or null.
async getPreferences(eUserId: string): Promise<UserPreferences | null> {
return this.userPreferencesModel.findOne({
eUserId,
});
}
Then in your controller await for the result, you forgot this. That's why you are seeing a pending promise. After the result has been resolved, check if any user preferences were returned and throw the NotFoundException if not.
#Get()
async getPreferences(#Headers('x-e-user-id') eUserId: string): Promise<UserPreferences> {
const userPreferences = await this.userPreferencesService.getPreferences(eUserId);
if (!userPreferences) {
throw new NotFoundException("We couldn't find your user preferences");
}
return userPreferences;
}
I would not throw NotFoundException or any other HTTP related error from your service. Leave that responsibility up to your controller, don't tie your service (logic) to HTTP error codes. Throw errors here that are not aware of the context (HTTP) they are being used in.
PS: You might also consider passing the user ID via the query string or as a route parameter instead of via the headers.
I have tried changing the values from 0.025 ether to 1 ether then also its showing the same error.
Also, I have tried with the rational number like 1/8 still not working.
LOOKED into some answers but they didn't resolve the error.
I have the same code in other project and it's working over there.
Error Which I received
Uncaught (in promise) Error: invalid BigNumber value (argument="value", value={"value":"25000000000000000"}, code=INVALID_ARGUMENT, version=bignumber/5.5.0)
Could not get the stack frames of error: TypeError: Cannot read properties of null (reading 'length')
Image of the Error
Here is my Code for the Listing Price
uint256 listingPrice = 0.025 ether ; // Here ether is denoting the MATIC
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
Here is the Code for fetching the value in UI
async function putItem(url) {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
const { royalty } = formInput;
//NFT Contract
let contract = new ethers.Contract(nftAddress, NFT.abi, signer);
//minting the certificate
let transaction = await contract.createToken(url);
//waiting for the minting transaction to finish
let tx = await transaction.wait();
let event = tx.events[0];
let value = event.args[2];
let tokenId = value.toNumber(); //Token Id Of the NFT
console.log(tokenId)
//NFT Market Contract
contract = new ethers.Contract(nftMarketAddress, NFTMarket.abi, signer);
//fetching listing price from the contract
let listingPrice = await contract.getListingPrice();
listingPrice = listingPrice.toString();
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
//waiting for the transaction to complete
await transaction.wait();
console.log("completed")
//navigate back to home page
}
If any more detail required, please comment.
It looks like you're trying to send an object as the parameter { value: (listingPrice) }
This should probably be written as either an array of parameters or just the listingPrice
//listing the certificate.
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
listingPrice,
royalty,
index
);
Source: https://docs.ethers.io/v5/api/contract/contract/#contract-functionsSend
For my case I needed to add .toString() to the BigNumber before passing it to the contract.
async changePayoutAmount_ether(amount_ether) {
let amount_wei = new BigNumber(amount_ether).shiftedBy(18).toString()
await this.state.pcrContract.methods.setPayoutAmount(amount_wei).send({from: this.state.account}).then(console.log)
}
Also for anyone troubleshooting, note that there are at least two BigNumber libraries: I believe this error comes from this one but be careful if you're reading docs from the ethers.js one because the syntax for the constructors is different.
I got this error as well. In my case, I forgot to update the ABI.
You can use the following module:
import converter form "ethereum-uint-converter"
And if you want to know more detail, click here.
I think this is the issue:
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
{ value: (listingPrice) },
royalty,
index
);
{ value: (listingPrice) }, is supposed to be object that represents the amount of money you are sending along side the transaction and it should be the last parameter in the function. Because looks like you are creating an NFT market item and you have to pay the price of listing.
Since you are creating an nft, looks like you have a const { royalty } = formInput. I believe you wanted to send the nft price instead of { value: (listingPrice) }. so your transaction should be like this
transaction = await contract.createMarketItem(
nftAddress,
tokenId,
// I assume you want to send the nft price here from form
priceFromForm,
royalty,
index,
// this should be the last parameter
{ value: (listingPrice) }
);
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 use web3.js to show revert reason to user, for example in the case of user trying to mint erc721 token that has already been minted. I am using try catch block and see the error message but I want to isolate the error message to show the user a meaningful reason. Thanks in advance.
The previous answer by #Petr Hejda didn't work for me, and neither did his suggestion in response to #Chakshu Jain's problem in the comments.
Instead, I removed some characters—from the start and the end, with slice()—that were causing the error when parsing the JSON, so I could handle the error message and get the error message.
if (err) {
var errorMessageInJson = JSON.parse(
err.message.slice(58, err.message.length - 2)
);
var errorMessageToShow = errorMessageInJson.data.data[Object.keys(errorMessageInJson.data.data)[0]].reason;
alert(errorMessageToShow);
return;
}
It's returned in the JS error object as data.<txHash>.reason.
This is a faulty Solidity code
pragma solidity ^0.8.0;
contract Test {
function foo() public {
revert('This is error message');
}
}
So a transaction calling the foo() function should revert with the message This is error message.
try {
await myContract.methods.foo().send();
} catch (e) {
const data = e.data;
const txHash = Object.keys(data)[0]; // TODO improve
const reason = data[txHash].reason;
console.log(reason); // prints "This is error message"
}
After trying out every solution on stackoverflow, random blogs, and even the officially documented "web3.eth.handleRevert = true", none is working for me.
I finally figured out after 25 failed attempts:
try {
await obj.methods.do_something().call({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
}
catch (err) {
const endIndex = err.message.search('{')
if (endIndex >= 0) {
throw err.message.substring(0, endIndex)
}
}
try {
const res = await obj.methods.do_something().send({
gasLimit: String(GAS_LIMIT),
to: CONTRACT_ADDRESS,
from: wallet,
value: String(PRICE),
})
return res.events.Transfer.returnValues.tokenId
}
catch (err) {
console.error(err)
throw err
}
The idea is to use call first. This method doesn't interact with your Metamask, but merely checks if your input arguments go through the contract method. If it can't go through, it will throw exception in the first catch block. If it does go through, we are safe to do use send. This method interacts with your Metamask for real. We have a second catch block in case there are wallet connection or gas fee issues
It is really perplexing why Solidity/Web3 don't have an easy way to extract the require/revert reason from the error object.
For me, the "require" reason is there in the message property of the error object, but it is surrounded by lot of other words which I don't need.
An example error message:
[ethjs-query] while formatting outputs from RPC '{"value":{"code":-32603,"data":{"message":"VM Exception while processing transaction: revert Voting is closed","code":-32000,"data":{"0xf901429f12096d3b5c23a80e56fd2230fa37411bb1f8d3cdbd5c8f91c2670771":{"error":"revert","program_counter":43,"return":"0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000165f5f5f566f74696e6720697320636c6f7365645f5f5f00000000000000000000","reason":"Voting is closed"},"stack":"RuntimeError: VM Exception while processing transaction: revert Voting is closed \n at Function.RuntimeError.fromResults (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n at BlockchainDouble.processBlock (/tmp/.mount_ganachreY1gT/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n at runMicrotasks (<anonymous>)\n at processTicksAndRejections (internal/process/task_queues.js:93:5)","name":"RuntimeError"}}}}'
You can see the reason Voting is closed stuck in between. Not that user-friendly to read.
I've seen answers that use regex to extract the error reason.
For those like me, who are not a big fan of the regex way, here is my approach.
In your solidity contract, wrap the require reason with a unique delimiter of sorts. In my case, it is "___" (3 underscores).
contract MyContract{
...
...
function vote(address _addr) public payable{
require(votingOpen, "___Voting closed___");
...
}
...
...
}
Declare a helper function to extract the error using JavaScript string utilities. Here's where your delimiter coes in handy.
export const extractErrorCode = (str) => {
const delimiter = '___'; //Replace it with the delimiter you used in the Solidity Contract.
const firstOccurence = str.indexOf(delimiter);
if(firstOccurence == -1) {
return "An error occured";
}
const secondOccurence = str.indexOf(delimiter, firstOccurence + 1);
if(secondOccurence == -1) {
return "An error occured";
}
//Okay so far
return str.substring(firstOccurence + delimiter.length, secondOccurence);
}
Use this function where you catch the error in your frontend
const vote = async (_addr) => {
setLoading(true);
try {
await contest.methods.vote(_addr).send({
from: accounts[0],
})
}
catch (e) {
console.log('Voting failed with error object => ', e)
console.log('Voting failed with the error => ', extractErrorCode(e.message))
}
setLoading(false);
}
Until Solidity & Web3.js (and ether.js) come out with a clean way to parse errors, we are stuck with workarounds like this.
I prefer this workaround over others because I am not that great with regex, and additionally, this one does not depend on a fixed starting position to extract the error code.
Did you try something like this?
error.toString()
It works for me just to show the revert error in the Smart Contract, and return it as a string message.
try {
//Do something
} catch (error) {
res.send({
'status': false,
'result': error.toString()
});
}
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()