I have the following smart contract function:
function safeMint(address to, uint256 tokenId) public onlyOwner payable {
require(msg.value >= mintPrice, "Not enough ETH to purchase NFT; check price!");
_safeMint(to, tokenId);
}
and the following test function in chai to test it.
describe("mint", () => {
it("should return true when 0.5 ethers are sent with transaction", async function () {
await contract.deployed();
const cost = ethers.utils.parseEther("0.1");
await contract.safeMint("0x65.....",1,cost
});
However the test function is not working and gives me an error on cost.
Error: "Type 'BigNumber' has no properties in common with type 'Overrides & { from?: PromiseOrValue; }'." I fail to understand where the error lies.
Try this, it's the valid syntax to send value with the call:
await contract.safeMint("0x65.....", 1, {value: cost});
I had a similar problem while testing a payable function, it kept saying 'object is not an instanceof of BigInt'. How I solved this problem and ensured testing ran smoothly was to test the balance of the receiver (in your case, the 'to' address), to make sure the balance has been updated. That is, if the safeMint function was successful during test, the 'to' address should be increased by 1.
You can test by:
const balOfToAdress = await contract.balanceOf(to.address)
expect(balOfToAddress).to.equal(1)
Note: the 'expect' above is gotten by requiring chai at the top of your code i.e.
const {expect} = require('chai')
and, you have to test specifically for the balance of 'to' address (Here, I just assumed the initial balance of mints on 'to' address is 0).
I'm working on an app that allows the user to create NFTs and list them on a market place. When I try to create the token on the UI using metamask, a createToken function is being called. The resolved promise of createToken is an object for which I expect an events key with 2 events (I'm able to confirm this by running npx hardhat test. However I don't actually see these events emitted to the UI... I need these events to get the tokenId. If someone knows an alternative way to get the tokenId I'm open to that as well.
createToken:
contract NFT is ERC721URIStorage {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
address contractAddress;
constructor(address marketplaceAddress) ERC721("Metaverse", "METT") {
contractAddress = marketplaceAddress;
}
function createToken(string memory tokenURI) public returns (uint256) {
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
// Not emitting this event to the UI but it works in the test
setApprovalForAll(contractAddress, true);
return newItemId;
}
}
The function on the UI looks like below where createSale creates a listing on the marketplace:
async function createSale(url) {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
/* next, create the item */
let contract = new ethers.Contract(nftAddress, NFT.abi, signer);
let transaction = await contract.createToken(url);
let tx = await transaction.wait();
// Seeing a successful transaction
console.log("tx ==> ", tx);
let event = tx.events[0];
// Breaks here sine event[0] is `undefined`
let value = event.args[2];
let tokenId = value.toNumber();
const price = ethers.utils.parseUnits(formInput.price, "ether");
/* then list the item for sale on the marketplace */
contract = new ethers.Contract(nftMarketAddress, Market.abi, signer);
let listingPrice = await contract.getListingPrice();
listingPrice = listingPrice.toString();
transaction = await contract.createMarketItem(nftAddress, tokenId, price, {
value: listingPrice,
});
await transaction.wait();
router.push("/");
}
Below is a screenshot of the resolved promise with the empty events array:
The nftAddress is empty - it doesn't hold the NFT contract.
The blockNumber property has value 1, which means this transaction was mined in the first block. Hardhat network automines by default - creates a new block for each transaction. Which makes the attached transaction the first one on this network instance, and rules out any possible previous transactions deploying the contract (to this network instance).
When you send a valid transaction to an empty address, it passes through as there's no contract to revert it - but also there's no contract to emit event logs. So it results in an empty events array - just like on the attached screenshot.
I expect an events key with 2 events (I'm able to confirm this by running npx hardhat test
When you run a hardhat test, it creates a network instance, deploys the contracts, runs the test scripts, and then destroys this network instance.
Same goes for when you run npx hardhat node - it creates a new network instance, and when you stop running the node, it destroys its data (along with deployed contracts).
Hardhat network doesn't seem to have a way to save its state and load it later. (Anyone please correct me if I'm mistaken - I just couldn't find anything related in the docs). So you'll might have to redeploy the contract each time you run npx hardhat node. Or to use a different network that supports this feature (e.g. Ganache and its Workspaces) - you'll still be able to use the Hardhat library for the tests, it will just connect to this other network instead of the default Hardhat network.
I had this exact same issue. I solved mine by using the ropsten test network instead.
I am getting the below error on calling function setRouterAddress with router address 0xD99D1c33F9fC3444f8101754aBC46c52416550D1 in testnet.
Gas estimation failed error
Gas estimation errored with the following message (see below). The transaction execution will likely fail. Do you want to force sending?
execution reverted
this is message is coming for this line of code in the below function. IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
declarations:
IUniswapV2Router02 public unswapV2Router;
address public unswapV2Pair;
Function:
function setRouterAddress(address newRouter) external onlyOwner() returns(address, address){
IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(newRouter);
// Create a uniswap pair for this new token
uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
// set the rest of the contract variables
uniswapV2Router = _uniswapV2Router;
return (newRouter, uniswapV2Pair);
}
Could someone guide what is the problem ?
I'm starting with contracts in solidity and a question arose, is there any way I can store addresses in the wallet so that only these stored can make the withdrawal of coins that I make available? Let me explain better, I want to airdrop for 1 month for example and after this period the people who got my coins can withdraw from my contract, but so that only the people who complied with the airdrop rules. I want to do this so they pay transaction fees. But I want only addresses authorized by the owner of the contract to be able to withdraw the currency
Another way of doing this, in order to avoid gas, is to either use merkle trees or signatures. I have implemented signatures in two NFT projects.
Basically, for every address you wish to whitelist, you sign a message offchain, by using a wallet in your possession (it can be empty, you only need to sign messages).
Example call for signing via JavaScript (by using the web3js lib):
web3.eth.accounts.sign(message, privateKey);
By using the privateKey, this allows you to sign multiple messages at once (without having to confirm in MetaMask. It is clear that: please keep that private key as safe as possible! Only execute this code locally on your machine! Do not store the privateKey in any text file, especially not in the project folder, that might get synched to github!
The other way of doing it via MetaMask:
web3.eth.personal.sign(message, from);
(where "from" is the address of the signer).
On the contract side, there are some functions that can be used to retrieve the signer of a message, by reconstructing the message and passing the signature you have generated.
Some reference code:
// used to keep track of issuer addresses, that can issue whitelists
mapping(address => bool) internal issuers;
// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns(bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
// get the signer address by message and signature
function recoverSigner(bytes32 _message, bytes memory sig) internal pure returns(address){
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
return ecrecover(_message, v, r, s);
}
function signerIsIssuer(bytes memory message, bytes memory _sig, uint256 nonce) public view returns(bool){
// check the issuer
if (!issuers[recoverSigner(message, sig)]) {
return false;
} else {
return true;
}
}
// splits the signature into its components
function splitSignature(bytes memory sig) internal pure returns(uint8, bytes32, bytes32){
require(sig.length == 65);
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r:= mload(add(sig, 32))
// second 32 bytes
s:= mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v:= byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
// this has to hash the same parameters as on the offchain side
// do not reuse nonces
function hashMessage(address authorizedWallet, uint256 nonce) public returns(bytes memory){
// create the message hash
bytes32 message = prefixed(keccak256(abi.encodePacked(authorizedWallet, nonce)));
if (signerIsIssuer(message, _sig, _nonce)){
// authorizedWallet really is authorized by signature
}
}
If you want to be able to remove authorization, then you either need to invalidate the nonce, or work with expiration timestamps or keep an address blacklist. Please note, that once a signature is emitted, if the contract has no ways to be told, that that signature is not valid anymore, the wallet address will be authorized forever.
Hope this helps!
code:
contract test{
address owner;
mapping(address=>bool) authorizedAddresses;
mapping(address=>bool) isRegistered;
mapping(uint256=>address) users;
uint256 userCounter;
constructor(){
owner = msg.sender;
}
modifier isOwner(){
require(msg.sender == owner, "Access denied!");
_;
}
function changeOwner(address _to) public isOwner{
owner = _to;
}
function authorizeAddress(address _address) public isOwner{
if(isRegistered[_address]){
authorizedAddresses[_address] = true;
}else{
users[userCounter] = _address;
isRegistered[_address] = true;
authorizedAddresses[_address] = true;
userCounter ++;
}
}
function unauthorizeAddress(address _address) public isOwner{
require(authorizedAddresses[_address], "User is not authorized already");
authorizedAddresses[_address] = false;
}
modifier isAuthorizedAddress(){
require(authorizedAddresses[_address], "You are not authorized to call this function");
_;
}
I should note I did not test this code and may throw error, so if it throws any error let me know and modify it or you can do it let me know how to fix it and edit the code for others.
If you did not understand any part, simply ask to describe :)
Best regards
You can use ERC20 token standart. Just define allowance for those addresses who complied with the airdrop rules.
As others wrote, you can store addresses in a mapping like mapping(address=>bool) authorizedAddresses;
However, it is expensive and inefficient to store hundreds of addresses on the blockchain. Think about that, probably you upload a bunch of addresses you don't even need to, because people will forget that they can get the airdrop and they won't even claim it.
A better approach is to give them signed coupons. When they claim the airdrop on your website, the transaction submits a coupon and your contract will verify if the coupon is:
signed by you
belongs to the sender address
allowed to claim the amount
It all happens in the background, so users won't notice anything.
Generate coupons in JS:
const {
keccak256,
toBuffer,
ecsign,
bufferToHex
} = require("ethereumjs-util")
const { ethers } = require('ethers')
const PRIV_KEY = "YOUR_SIGNER_PRIVATE_KEY"
//utils
function generateHashBuffer(typesArray, valueArray) {
return keccak256(
toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray,
valueArray))
)
}
function createCoupon(hash, signerPvtKey) {
return ecsign(hash, signerPvtKey)
}
function fromHexString(hexString) {
return Uint8Array.from(Buffer.from(hexString, 'hex'))
}
function serializeCoupon(coupon) {
return {
r: bufferToHex(coupon.r),
s: bufferToHex(coupon.s),
v: coupon.v,
}
}
//generate coupon
function generate(claimAmount, address) {
const userAddress = ethers.utils.getAddress(address) // get checksum-correct address
const hashBuffer = generateHashBuffer(
["uint256", "address"],
[claimAmount, userAddress]
)
const coupon = createCoupon(hashBuffer, fromHexString(PRIV_KEY))
console.log({
address,
claimAmount,
coupon: serializeCoupon(coupon)
})
}
Now you have the coupon, you can build an API, so when the user click Claim on your website, he can submit the amount and the signature to the blockchain in the claim transaction.
Verify signature in your contract:
contract Airdrop is
Ownable,
ReentrancyGuard
{
address private COUPON_SIGNER = 0xYOUR_SIGNER_ADDRESS;
mapping(address => bool) public CLAIMED_ADDRESSES;
struct Coupon {
bytes32 r;
bytes32 s;
uint8 v;
}
// check that the coupon sent was signed by the admin signer address
function _isVerifiedCoupon(bytes32 digest, Coupon memory coupon)
internal
view
returns (bool)
{
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s);
require(signer != address(0), "ECDSA: Invalid whitelist signature.");
return signer == COUPON_SIGNER;
}
// Claim Airdrop
function claim(uint256 amountToClaim, Coupon memory coupon) external nonReentrant {
require(
CLAIMED_ADDRESSES[msg.sender] == false,
"This address already claimed a refund."
);
require(
address(this).balance >= amountToClaim,
"Contract balance is too low."
);
bytes32 digest = keccak256(abi.encode(amountToClaim, msg.sender));
require(
_isVerifiedCoupon(digest, coupon),
"Unexcepted error: unable to verify coupon."
);
CLAIMED_ADDRESSES[msg.sender] = true;
payable(msg.sender).transfer(amountToClaim); // Send ETH
}
}
PLEASE PLEASE don't just copy this code: TEST, TEST, and TEST. Always write unit tests.
This is my code in Solidity
pragma solidity ^0.4.17;
contract WithdrawalContract {
mapping(address => uint) buyers;
function buy()public payable {
require(msg.value > 0);
buyers[msg.sender] = msg.value;
}
function withdraw()public {
uint amount = buyers[msg.sender];
require(amount > 0);
buyers[msg.sender] = 0;
require(msg.sender.send(amount));
}
}
Below is the error i am getting. Not sure why it is asking for constructor
transact to WithdrawalContract.buy errored: VM error: revert.
revert The transaction has been reverted to the initial state.
Note: The constructor should be payable if you send value. Debug the transaction to get more information.
That's just part of the error message Remix shows when a transaction reverts, presumably because that's a common mistake people make.
In your case, that's not the issue. My guess would be that you're calling buy and not attaching any ether, so the require(msg.value > 0) is causing the transaction to revert. In Remix, make sure you have a non-zero "value" in the upper right.