Can I make my contract be available to any account? - ethereum

I am developing a dapp where users can upload photographs and mint them to NFTs. My application uses web3( Alchemy web3), Alchemy, Ropsten and Metamask. I also use Hardhat in order to deploy my contract with the command:
npx hardhat run --network ropsten scripts/deploy.js
First of all I have installed Metamask in my browser(Firefox) and I have created my wallet. I have also used a Ropsten Faucet in order to get some ether. In order to deploy my contract I use my account's private key in hardhat.config.js. The problem is that in this way only my account is able to use the contract. My application is supposed to accept multiple users, each with their own Metamask wallet or configuration, performing their own transactions.
Therefore, can I deploy or change my contract so that it can be used by any user and not just the one who deployed it?
Here is my contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "#openzeppelin/contracts/token/ERC721/ERC721.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
import "#openzeppelin/contracts/utils/Counters.sol";
contract NFTminter is ERC721, Ownable {
using Counters for Counters.Counter;
mapping (uint256 => string) private _tokenURIs;
mapping(string => uint256) private hashes;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("MyToken", "PcI") {
}
function _baseURI() internal pure override returns (string memory) {
return "ipfs://";
}
function safeMint(address to, string memory metadataURI) public onlyOwner returns (uint256) {
require(hashes[metadataURI] != 1 , "This metadataURI already exists.");
hashes[metadataURI] = 1;
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
_setTokenURI(tokenId, metadataURI);
return tokenId;
}
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
_tokenURIs[tokenId] = _tokenURI;
}
}
Here is my hardhat.config.js:
require("#nomiclabs/hardhat-waffle");
const { MNEMONIC, ALCHEMY_HTTP } = require("./alchemy_secrets.json");
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
module.exports = {
networks: {
ropsten: {
url: ALCHEMY_HTTP,
accounts: ['PRIVATEKEYGOESHERE']
},
},
solidity: "0.8.4",
};
And here is the code which calls the contract and sends the transaction:
import Web3 from "web3";
import CONTRACT_ABI from "../ethContractABI";
import { AbiItem } from "web3-utils";
import {
ROPSTEN_CONTRACT_ADDRESS,
NFT_STORAGE_KEY,
ALCHEMY_API_KEY,
} from "../constants";
import { NFTStorage } from "nft.storage";
import { Contract } from "web3-eth-Contract";
import { AlchemyWeb3, createAlchemyWeb3 } from "#alch/alchemy-web3";
declare global {
interface Window {
ethereum: any;
web3: Web3;
}
}
interface postcardReturn {
ipfsLink?: string | undefined;
tokenID?: number | undefined;
errorMessage?: string | undefined;
}
async function convertToNft(
imageToUpload: File,
etherAddress: string,
privateKey: string
): Promise<postcardReturn> {
try {
await CreateWeb3Object();
const web3 = createAlchemyWeb3(ALCHEMY_API_KEY);
const metadata = await GetNFTmetadata(imageToUpload);
const NFTminter = createNftContract(web3, etherAddress);
//CheckIfTokenExists(NFTminter, metadata, etherAddress);
const receipt = await mintToken(
NFTminter,
metadata,
etherAddress,
web3,
privateKey
);
return {
ipfsLink: metadata.data.image.href,
tokenID: receipt!.events!.Transfer.returnValues.tokenId,
};
} catch (error: any) {
return returnError(error);
}
}
async function GetNFTmetadata(imageToUpload: File) {
const client = new NFTStorage({ token: NFT_STORAGE_KEY });
const metadata = await client.store({
name: "From: User",
description: "IMage to be converted to nft",
image: imageToUpload,
});
return metadata;
}
async function CreateWeb3Object() {
if (window.ethereum) {
try {
const enable = window.ethereum.enable();
return;
} catch (error) {
console.log(error);
}
}
}
async function CheckIfTokenExists(
NFTminter: Contract,
metadata: any,
etherAddress: string
) {
const check = await NFTminter.methods
.safeMint(etherAddress, metadata.url)
.estimateGas((error: any, gasAmount: any) => {
if (error) {
console.error(error);
return "An error has occured";
}
});
}
function createNftContract(web3: any, etherAddress: string) {
const NFTminter = new web3.eth.Contract(
CONTRACT_ABI,
ROPSTEN_CONTRACT_ADDRESS
);
return NFTminter;
}
async function mintToken(
NFTminter: Contract,
metadata: any,
etherAddress: string,
web3: AlchemyWeb3,
privateKey: string
) {
const nonce = await web3.eth.getTransactionCount(etherAddress, "latest");
const tx = {
from: etherAddress,
to: ROPSTEN_CONTRACT_ADDRESS,
nonce: nonce,
gas: 2000000,
maxPriorityFeePerGas: 1999999987,
data: NFTminter.methods
.safeMint(etherAddress, metadata.data.image.href)
.encodeABI(),
};
const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
web3.eth.sendSignedTransaction(
signedTx.rawTransaction!
).then(console.log)
.catch(console.log);
const transactionReceipt = await web3.eth.sendSignedTransaction(
signedTx.rawTransaction!
);
console.log(transactionReceipt);
return transactionReceipt;
}
function returnError(error: any) {
if (error.message.includes("Internal JSON-RPC error."))
return {
errorMessage: "Internal JSON-RPC error.",
};
return {
errorMessage: error.message,
};
}
export default convertToNft;
The privateKey variable is given by the user before he clicks the upload button. If I change the account that I use, the exception is thrown in this line:
const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
The exception says that the Caller is not the contract owner. I think that what I described as the problem is the issue, because I redeployed my contract with the Metamask account that I use now and I can normally send and sign transactions. However, If I change the account I receive an error.

Your safeMint function has onlyOwner modifier. That's why only contract deployer can use it.

Related

Not able to bridge deployed ERC20 token through POS Bridge using Maticjs, getting this error "execution reverted: ERC20: approve to the zero address"

Here is my code, I have also added the options as well in the approve function which contains the from, to, gasLimit but that also doesn't work for me
const { POSClient, use } = require('#maticnetwork/maticjs');
const { Web3ClientPlugin } = require('#maticnetwork/maticjs-web3');
const HDWalletProvider = require('#truffle/hdwallet-provider');
require('dotenv').config();
use(Web3ClientPlugin);
async function getPOSClient() {
const posClient = new POSClient();
return await posClient.init({
network: 'testnet',
version: 'mumbai',
parent: {
provider: new HDWalletProvider(
process.env.PVT_KEY,
process.env.GOERLI_RPC
),
defaultConfig: {
from: process.env.FROM_ADDRESS,
},
},
child: {
provider: new HDWalletProvider(
process.env.PVT_KEY,
process.env.MUMBAI_RPC
),
defaultConfig: {
from: process.env.FROM_ADDRESS,
},
},
});
}
async function approveToken() {
const posClient = await getPOSClient();
// console.log('🚀 ~ file: index.js:36 ~ approveToken ~ posClient', posClient);
const erc20Token = posClient.erc20(process.env.ROOT_TOKEN, true);
const result = await erc20Token.approve('1000');
const txHash = await result.getTransactionHash();
console.log('txHash', txHash);
const receipt = await result.getReceipt();
console.log('receipt', receipt);
}
approveToken();
The error which I'm getting while approving the token,
Error: execution reverted: ERC20: approve to the zero address
0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002245524332303a20617070726f766520746f20746865207a65726f2061646472657373000000000000000000000000000000000000000000000000000000000000
I have also mapped the token from polygon token mapper portal also. Please give your feedback
This error occurs when you are trying to approve an ERC20 token to the zero address.
Assuming the approve interface is approve(spender, amount)
I believe that if you add the approved address to this call const result = await erc20Token.approve(ADDRESS,'1000'); this issue will be resolved.

hardhat & waffle - deploy a contract from an address

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.

Error: missing argument: in Contract constructor

Error message: Error: missing argument: in Contract constructor (count=0, expectedCount=1, code=MISSING_ARGUMENT, version=contracts/5.6.2)
contract:
contract KBMarket is ReentrancyGuard {
using Counters for Counters.Counter;
constructor() {
owner = payable(msg.sender);
}
and here is hardhat test.js:
describe("KBMarket", function () {
it("Should Mint And Trade NFTs", async function () {
const Market = await ethers.getContractFactory('KBMarket')
const market = await Market.deploy()
await market.deployed()
const marketAddress = market.adderss
}
}
Thanks in advance.
Your constructor should have an argument:
constructor(uint _feePercent)
In deploy.js,
const market = await Market.deploy(1)
you need to pass an argument here because the marketplace's constructor accepts the _feePercent as an argument.

getting error when i deploy the NFT with ETH

I am new in NFT, i am trying to create test NFT, when i am trying to deploy that NFT, i am getting this error,insufficient funds for intrinsic transaction cost, even though in my account have 1 ETH balance here i have attached my whole code of it, can anyone please help me, how to resolve this issue ?
MyNFT.sol
//Contract based on [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "#openzeppelin/contracts/token/ERC721/ERC721.sol";
import "#openzeppelin/contracts/utils/Counters.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
import "#openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyNFT is ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
constructor() ERC721("MyNFT", "NFT") {}
function mintNFT(address recipient, string memory tokenURI)
public onlyOwner
returns (uint256)
{
_tokenIds.increment();
uint256 newItemId = _tokenIds.current();
_mint(recipient, newItemId);
_setTokenURI(newItemId, tokenURI);
return newItemId;
}
}
hardhat.config.js
/**
* #type import('hardhat/config').HardhatUserConfig
*/
require('dotenv').config();
require("#nomiclabs/hardhat-ethers");
const { API_URL, PRIVATE_KEY } = process.env;
//console.log(PRIVATE_KEY);
module.exports = {
solidity: "0.8.1",
defaultNetwork: "ropsten",
networks: {
hardhat: {},
ropsten: {
url: API_URL,
accounts: [`0x${PRIVATE_KEY}`]
}
},
}
deploy.js
async function main() {
const MyNFT = await ethers.getContractFactory("MyNFT")
// Start deployment, returning a promise that resolves to a contract object
const myNFT = await MyNFT.deploy()
await myNFT.deployed()
console.log("Contract deployed to address:", myNFT.address)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
That error is clear. you do not have sufficient funds. This is how you are getting the account information:
const { API_URL, PRIVATE_KEY } = process.env;
I had an issue with webpack once destructuring process.env. Try this
// ASSUMING you pass correct private key here
const PRIVATE_KEY = process.env.PRIVATE_KEY;
console.log the private key.
If it does not get resolved, that means you are not passing the correct PRIVATE_KEY.

Solidity: Events triggered but state not updated after delegatecall

I would like to use delegatecall to deposit ETH into WETH and batch this process together with other actions (see code below). When running truffle tests, it seems like the events are triggered correctly but the state does not seem to be updated.
Same behavior on the Rinkeby network. The weird thing is that the transaction completes completely successfully. Here is an example of transaction: https://rinkeby.etherscan.io/tx/0x93c724174e2b70f2257544ebc0e0b9da6e31a7a752872da7683711bd4d4bd92b
Solidity code:
pragma solidity 0.4.24;
import "../interfaces/ERC20.sol";
import "./WETH9.sol";
contract SetupAccount {
address public exchangeAddress;
address public wethAddress;
constructor (
address _exchangeAddress,
address _wethAddress
) public {
exchangeAddress = _exchangeAddress;
wethAddress = _wethAddress;
}
function setup(
address[] _tokenAddresses,
uint256[] _values
) public payable {
for (uint i = 0; i < _tokenAddresses.length; i++) {
_tokenAddresses[i].delegatecall(abi.encodeWithSignature("approve(address,uint256)", exchangeAddress, _values[i]));
}
if (msg.value != 0) {
wethAddress.delegatecall(abi.encodeWithSignature("deposit()"));
}
}
Failing truffle test:
describe('setupAccount', async () => {
beforeEach(async () => {
weth = await WETH.new()
exchange = await Exchange.new(rewardAccount)
bnb = await BNB.new(user1, 1000)
dai = await DAI.new(user1, 1000)
omg = await OMG.new(user1, 1000)
setupAccount = await SetupAccount.new(exchange.address, weth.address)
})
it('setupAccount should deposit weth and approve tokens', async () => {
await setupAccount.setup(
[bnb.address, dai.address, omg.address],
[1000, 1000, 1000],
{ from: user1, value: 10 ** 18 }
)
let wethBalance = await weth.balanceOf(user1)
wethBalance.should.be.bignumber.equal(10 ** 18)
bnbAllowance.should.be.bignumber.equal(1000)
daiAllowance.should.be.bignumber.equal(1000)
omgAllowance.should.be.bignumber.equal(1000)
})
})
Events emitted during test:
Events emitted during test:
---------------------------
Deposit(dst: <indexed>, wad: 1000000000000000000)
---------------------------
Test result:
1) Contract: SetupAccount
setupAccount
setupAccount should deposit weth and approve tokens:
AssertionError: expected '0' to equal '1000000000000000000'
+ expected - actual
-0
+1000000000000000000
I'm confused as to why this doesn't work. Thank's in advance.
msg.value is preserved correctly through a delegatecall. The following code demonstrates this, as evidenced by the emitted event showing the correct value.
pragma solidity >0.4.99 <0.6;
contract Target {
event Received(uint256 amount);
function deposit() external payable {
emit Received(msg.value);
}
}
contract Delegater {
function deposit() external payable {
(bool success,) = address(new Target()).delegatecall(abi.encodeWithSignature("deposit()"));
require(success);
}
}