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);
}
}
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 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.
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.
I've created a simple Contract extending openzeppelin ERC20.
I'm trying to transfer token from one address to another.
Contract code is as below:
File name: Token.sol
pragma solidity ^0.7.0;
import "#openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Token is ERC20 {
uint256 public INITIAL_SUPPLY = 100000;
constructor() ERC20("My Token", "MYT") {
_mint(msg.sender, INITIAL_SUPPLY);
}
}
Code within test file:
const { expect } = require("chai");
describe("Send token from second address in the block", function () {
it("Send 100 MYT to the third account", async function () {
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
// get accounts from the network
const [owner, secondAccount, thirdAccount] = await ethers.getSigners();
// send some credit to the second account
await token.transfer(secondAccount.address, 500);
// Approve token transfer
await token.approve(secondAccount.address, 200);
// Transfer credit from second account to the third account (This step is not working)
await token.transferFrom(secondAccount.address, thirdAccount.address, 100);
});
});
Error received:
Error: VM Exception while processing transaction: reverted with reason string 'ERC20: transfer amount exceeds allowance'
at Token.sub (#openzeppelin/contracts/math/SafeMath.sol:171)
at Token.transferFrom (#openzeppelin/contracts/token/ERC20/ERC20.sol:154)
at processTicksAndRejections (internal/process/task_queues.js:94:5)
at runNextTicks (internal/process/task_queues.js:63:3)
at listOnTimeout (internal/timers.js:501:9)
at processTimers (internal/timers.js:475:7)
at EthModule._estimateGasAction (node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:421:9)
at HardhatNetworkProvider.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:105:18)
at EthersProviderWrapper.send (node_modules/#nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)
Thank you for the help.
If you check Approve if erc20, the function arguments expect address _spender, uint256 _value as arguments. In your case you approved secondAccount to withdraw 200 tokens.
It means secondAccount can transfer those token to his account from the token contract. To do that, you try await token.connect(secondAccount).transferFrom(token.address,secondAccount.address,100);
I'm trying to get the balance of an address on my smart contract using web3, but the balance is always 0. Using metamask on Rinkeby since my contract is deployed on rinkeby. https://rinkeby.etherscan.io/address/0x8e3a88be716ce7c8119c36558ec97bc634592255
You can verify the wallet has a balance by putting it in the balanceOf function on etherScan. Use the address 0x8b54A82a12bD5A7bA33B4842cA677E55f78a8612
let provider = web3.currentProvider;
web3 = new Web3(provider);
let abi = 'too long of a string to post here';
let MyContract = web3.eth.contract(JSON.parse(abi));
let myContractInstance = MyContract.at('0x8e3a88be716ce7c8119c36558ec97bc634592255');
let address = '0x8b54A82a12bD5A7bA33B4842cA677E55f78a8612';
function balanceOf(address) {
if (!address) {
return;
}
this.myContractInstance.balanceOf.call(address, function(error, balance) {
if (error) {
return;
}
alert(balance.c[0] + ' RHC');
});
}
balanceOf(address);
Here is the getBalance function on my contract
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
Website implemented on http://robinhoodcoin.net/metamask.html
Code https://github.com/robinhoodcoin/robinhoodcoin.github.io/blob/master/metamask.html
EDIT
when I change the provider to be the following:
var web3 = new Web3(new Web3.providers.HttpProvider('https://rinkeby.infura.io/'));
I am able to get the balance. SO there is something up with using metamask as the provider.
The line at https://github.com/robinhoodcoin/robinhoodcoin.github.io/blob/master/metamask.html#L118 has a typo. It reads:
self.myContractInstance = self.MyContract.at(self.address);
but the address is stored at self.contractAddress, so it should read:
self.myContractInstance = self.MyContract.at(self.contractAddress);
After making that fix, the page works fine for me with MetaMask.