I am developing an NFT market using solidity, specifically I am creating my own smart contract on top of OpenZeppelin's ERC-721 smart contract. My NFT at the moment have 5 attributes (id, image, description, collection and image) for the image, I save the hash that ipfs develve when uploading it.
My question is where to save all these attributes, since I have the Image struct that has the aforementioned attributes, I add it to an array and I mint the NFT using the id of the Image object in the array and the address of the creator. I mean, I'm saving all the information outside of the ERC-721 contract, so I don't quite understand what an NFT is, since the attributes are not from the NFT but the NFT is an attribute of my struct.
Am I implementing it correctly and the ERC-721 standard is only the necessary functions of an NFT or am I saving the information where it does not touch?
My code is currently the following:
pragma solidity ^0.5.0;
import "./ERC721Full.sol";
contract NftShop is ERC721Full {
string public name;
Image[] public nft;
uint public imageId = 0;
mapping(uint => bool) public _nftExists;
mapping(uint => Image) public images;
struct Image {
uint id; //id of the nft
string hash; //hash of the ipfs
string description; //nft description
string collection; //what collection the nft bellongs
address payable author; //creator of the nft
}
//Event used when new Token is created
event TokenCreated(
uint id,
string hash,
string description,
string collection,
address payable author
);
constructor() public payable ERC721Full("NftShop", "NFTSHOP") {
name = "NftShop";
}
//uploadImage to the blockchain and mint the nft.
function uploadImage(string memory _imgHash, string memory _description, string memory _collection) public {
// Make sure the image hash exists
require(bytes(_imgHash).length > 0);
// Make sure image description exists
require(bytes(_description).length > 0);
// Make sure collectionage exists
require(bytes(_collection).length > 0);
// Make sure uploader address exists
require(msg.sender!=address(0));
// Increment image id
imageId ++;
// Add Image to the contract
images[imageId] = Image(imageId, _imgHash, _description, _collection, msg.sender);
//Mint the token
require(!_nftExists[imageId]);
uint _id = nft.push(images[imageId]);
_mint(msg.sender, _id);
_nftExists[imageId] = true;
// Trigger an event
emit TokenCreated(imageId, _imgHash, _description, _collection, msg.sender);
}
}
Any suggestions on how to improve the code if there is something weird is welcome.
I hope it is not an absurd question, I am starting in the world of Ethereum.
Thanks a lot.
Yes the information such as image address and so on is stored in a contract that inherits from ERC721. The ERC721 mainly tracks ownership, minting and transfer of a token.
One thing you might want to think about is the uniqueness of a token. In your example many tokens can exist that have the same image and description.
You might want to prevent that by storing a hash of the _imgHash, _description, _collection and require it to be unique, so that no user can create a "copy" of an existing token.
something like:
keccak256(abi.encodePacked(_imgHash, _description, _collection))
another frequently used standart is the Opensea Metadata Standard, where a tokenURI is stored on the chain and the metadata lives outside the blockchain. https://docs.opensea.io/docs/metadata-standards
Related
I am learning solidity in cryptozombies. I just finished 12 chapters with the good understanding. but, I don't understand the events concept. I want to understand the full code. can somebody help me with that??
pragma solidity >=0.5.0 <0.6.0;
contract ZombieFactory {
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
function _createZombie(string memory _name, uint _dna) private {
uint id = zombies.push(Zombie(_name, _dna)) - 1;
emit NewZombie(id, _name, _dna);
}
function _generateRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
}
function createRandomZombie(string memory _name) public {
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
}
The function of the event is that when the caller invokes this function, an additional logs will be added to the transaction content.
For a common example of ERC20 tokens, if the contract creator didn't add the event function when writing the Transfer function, something interesting would happen, the blockchain browser would not display the number of Hodlers properly and it would not show the detailed transaction history of the user-user transfer of ERC20 tokens.
Let's conclude that adding the event function will make it easier for the searcher on the blockchain browser to understand or analyze each transaction, but it will also make it easier for the searcher on the blockchain to find transactions or filter them for some arbitrage :)
Think of Events as the code piece that you put in a place where you want to be notified instantly if something happens there.
contract test{
function Alert() public returns (string memory) {
string memory str = "Alert! Do Something.";
return str;
}
}
for example in this code here you would want to be notified on your end-user screen so in order to that you would want your front-end to be able to get this info! right? and how would you go about doing that? by creating an event with which your front-end can interact with (particularly read from). Therefore you would modify the above code to add event to this as follows:
contract test{
event readAlert(string str);
function Alert() public returns (string memory) {
string memory str = "Alert! Do Something.";
return str;
emit readAlert(str);
}
}
After doing this you can now use the event you just created to do something with the info passed to it on you app front-end.
Let me know if you need further explanation. :)
When we want to mint an NFT with its own metadata, the asset file and JSON file should be uploaded to IPFS before the minting.
And we mint a new NFT by sending transactions to the smart contract and within the transaction, the token URI(hashed URI from IPFS) would be set as JSON metadata URI of the token.
Now I am wondering whether there is a way to import the attributes from JSON on IPFS into a smart contract and use the data like const variables in the contract.
You can do it but it is going to cost you alot. You need to create a mapping in smart contract.
mapping(string ->Metadata) private tokenURIToMetadata
Since it is mapped from string, it cannot be public. so create a function
function getMetadata(string tokenUri) public returns(Metadata){
return tokenURIToMetadata[tokenUri]
}
also create Metadata struct:
struct Metadata{
string name;
string description;
string image;
}
And then you have to add new medata to the mapping:
function addMetada(string memory tokenUri,?????????){}
Since we cannot pass json object as an argument to the addMetada, now you have to create a separate mapping for each property of the metadata object. So the cost will be crazy amount.
Instead, you could write the metadata to the filesystem or database but this would not be a good option.
As it is now, anyone can call the setMyString function in the FirstContract. I'm trying to restrict access to that function to an instance of SecondContract. But not one specific instance, any contract of type SecondContract should be able to call setMyString.
contract FirstContract{
String public myString;
function setMyString(String memory what) public {
myString=what;
}
}
contract SecondContract{
address owner;
address firstAddress;
FirstContract firstContract;
constructor(address _1st){
owner=msg.sender;
firstAddress=_1st;
firstContract=FirstContract(firstAddress);
}
function callFirst(String memory what){
require(msg.sender==owner);
firstContract.setMyString("hello");
}
}
Solidity currently doesn't have an easy way to validate an address against an interface.
You can check the bytecode, whether it contains the specified signatures (of the public properties and methods). This requires a bit larger scope than a usual StackOverflow answer, so I'm just going to describe the steps instead of writing the code.
First, define the desired list of signatures (1st 4 bytes of keccak256 hash of the name and arguments datatypes) that you're going to be looking for. You can find more info about signatures in my other answers here and here.
An example in the documentation shows how to get any address's (in your case msg.sender) bytecode as bytes (dynamic-length array).
You'll then need to loop through the returned bytes array and search for the 4-byte signatures.
If you find them all, it means that msg.sender "implements the interface". If any of the signatures is missing in the external contract, it means it doesn't implement the interface.
But... I'd really recommend you to rethink your approach to whitelisting. Yes, you'll need to maintain the list and call setIsSecondContract() when a new SecondContract wants to call the setMyString() function for the first time. But it's more gas efficient for all callers of the FirstContract's setMyString() function, as well as easier to write and test the functionality in the first place.
contract FirstContract{
String public myString;
address owner;
mapping (address => bool) isSecondContract;
modifier onlySecondContract {
require(isSecondContract[msg.sender]);
_;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function setIsSecondContract(address _address, bool _value) public onlyOwner {
isSecondContract[_address] = _value;
}
function setMyString(String memory what) public onlySecondContract {
myString=what;
}
}
I am developing an Ethereum based Card Game. A user can collect n amount of individual/unique Cards. Everything is up and running, I am using the following balance mapping:
mapping (address => mapping (uint256 => uint256)) balances;
The first uint is the Card ID, the second uint is the Card count. I will have up to 1000 Cards, right now I am testing with 700 Cards.
I retrieve the balances on DApp Start by calling:
function balanceOf(address _owner, uint256 _id) view external returns(uint256) {
return balances[_owner][_id];
}
for every single ID. On balance changes I do partial balance updates. This generally works. It is free, but it is also extremely slow as the initial retrieval call has to be done 640 times. I have researched a lot and also tried various implementations, but the main problem is that I need to retrieve an address mapped array holding the Card ID and Count information. Currently you can not easily retrieve dynamic sized Arrays or Structs.
What would be the proposal to resolve the issue? Am I stuck with up to 1000 balanceOf calls on DApp Start until Solidity introduces simple Array calls?
I thought about caching data on my WebServer, but for this to work I would need to run a node on the WebServer which I would like to avoid.
A Client based caching, where the Client posts the balance to the WebServer may also run into an inconsistent state because of the asynchronous nature of the Blockchain.
You can also use struct for managing data more easily. I made one contract using struct and retained data too. Please let me know if this approach didnt work for you.
pragma solidity ^0.4.18;
contract Test{
struct User{
uint cardId;
uint cardCount;
}
address user_address;
mapping (address => User) public Users;
function add_user() public {
user_address = msg.sender ;
var new_user = Users[user_address];
new_user.cardId =2;
new_user.cardCount = 50;
}
function get() public returns(uint,uint)
{
return(Users[user_address].cardId,Users[user_address].cardCount);
}
}
Your best chance is to use a secondary mapping to store the card IDs some user has, query it first, and than, for each ID, check the count for that user and that card ID.
Here is the code, tested on Remix:
pragma solidity 0.4.24;
contract Test {
mapping (address => uint[]) cardsOwned;
mapping (address => mapping (uint => uint)) cardsCounter;
function cardsOwnedBy(address _owner) view public returns (uint[]) {
return (cardsOwned[_owner]);
}
function cardsCounterFor(address _owner, uint _id) view public returns (uint) {
return cardsCounter[_owner][_id];
}
}
I kept on trying various different solutions but couldn't find any good way to handle this. I found a setup which will work for me for now, until Solidity is updated to be more functional when it comes to array handling and especially dynamically sized variables.
Fastest solution for my requirements:
mapping (address => uint256[1000]) public balances;
The mapping now assigns addresses to a fixed size array. I can now retrieve the full list on DApp Start by using:
function balancesOf(address _owner) view external returns (uint256[1000]) {
return balances[_owner];
}
The main advantage is that it is extremely fast, compared to any other solution. The main disadvantage is, that I lost my dynamically sized array and I have to know the maximum Card Count in advance - which I do not. I used a safety buffer now but if I hit the 1000 Cards mark I will have to update the contract. Hopefully there will be better solutions in the future.
I'm building a game on ethereum as my first project and I'm facing with the storage and gas limits. I would like to store a storage smart contract on the blockchain to be queried after the deployment. I really need to initialize a fixed length array with constant values I insert manually. My situation is the following:
contract A {
...some states variables/modifiers and events......
uint[] public vector = new uint[](162);
vector = [.......1, 2, 3,......];
function A () {
....some code....
ContractB contract = new ContractB(vector);
}
....functions....
}
This code doesn't deploy. Apparently I exceed gas limits on remix. I tried the following:
I split the vector in 10 different vectors and then pass just one of them to the constructor. With this the deploy works.
I really need to have just one single vector because it represents the edges set of a graph where ContractB is the data structure to build a graph. Vectors elements are ordered like this:
vector = [edge1From, edge1To, edge2From, edge2To,.......]
and I got 81 edges (162 entries in the vector).
I tought I can create a setData function that push the values in the vector one by one calling this function after the deployment but this is not my case because I need to have the vector filled before the call
ContractB contract = new ContractB(vector);
Now I can see I have two doubts:
1) Am I wrong trying to pass a vector as parameter in a function call inside the A constructor ?
2) I can see that I can create a double mapping for the edges. Something like
mapping (bool => mapping(uint => uint))
but then I will need multi-key valued mappings (more edges starting from the same point) and I will have the problem to initialize all the mappings at once like I do with the vector?
Why does the contract need to be initialized at construction time?
This should work
pragma solidity ^0.4.2;
contract Graph {
address owner;
struct GraphEdge {
uint128 from;
uint128 to;
}
GraphEdge[] public graph;
bool public initialized = false;
constructor() public {
owner = msg.sender;
}
function addEdge(uint128 edgeFrom, uint128 edgeTo) public {
require(!initialized);
graph.push(GraphEdge({
from: edgeFrom,
to: edgeTo
}));
}
function finalize() public {
require(msg.sender == owner);
initialized = true;
}
}
contract ContractB {
Graph graph;
constructor(address graphAddress) public {
Graph _graph = Graph(graphAddress);
require(_graph.initialized());
graph = _graph;
}
}
If the range of values for you array are small enough, you can save on gas consumption by using a more appropriate size for your uints. Ethereum stores values into 32-bytes slots and you pay 20,000 gas for every slot used. If you are able to use a smaller sized uint (remember, uint is the same as uint256), you'll be able to save on gas usage.
For example, consider the following contract:
pragma solidity ^0.4.19;
contract Test {
uint256[100] big;
uint128[100] small;
function addBig(uint8 index, uint256 num) public {
big[index] = num;
}
function addSmall(uint8 index, uint128 num1, uint128 num2) public {
small[index] = num1;
small[index + 1] = num2;
}
}
Calling addBig() each time with a previously unused index will have an execution cost of a little over 20,000 gas and results in one value being added to an array. Calling addSmall() each time will cost about 26,000, but you're adding 2 elements to the array. Both only use 1 slot of storage. You can get even better results if you can go smaller than uint128.
Another option (depending on if you need to manipulate the array data) is to store your vector off chain. You can use an oracle to retrieve data or store your data in IPFS.
If neither of those options work for your use case, then you'll have to change your data structure and/or use multiple transactions to initialize your array.