Is it possible for the same contract to handle multiple ERC721 tokens? And if so, how?
Let’s say you’re making a game and you need to track two types of ERC721 tokens: unique Weapons and unique Vehicles.
You could have Structs for them, as follows:
struct Weapon {
uint256 IDNumber;
string type; // Sword, Bow&Arrow, Gun, Rifle, etc.
string specialFeature;
}
struct Vehicle {
uint256 IDNumber;
string type; // Car, Airplane, Boat, Spaceship, etc.
uint256 damageFactor;
}
To track their ownership, you could double the arrays managing them - for example instead of having the standard:
// Enumerable mapping from token ids to their owners
EnumerableMap.UintToAddressMap private _tokenOwners;
You would do:
// Enumerable mapping from token ids to their owners
EnumerableMap.UintToAddressMap private _weaponTokenOwners;
EnumerableMap.UintToAddressMap private _vehicleTtokenOwners;
(This may not be the most elegant way, but it's a start.)
The real question though is: how would you handle mandatory functions that are part of the ERC721 standard, such as balanceOf() and ownerOf()?
To be specific, are you allowed to say add an additional argument to these methods’ signatures to help indicate which particular Token you’re querying about?
For example, instead of this:
function balanceOf(address owner) public view override returns (uint256) {
}
You’d add a tokenName argument to the function’s signature, as follows:
function balanceOf(address owner, string tokenName) public view override returns (uint256) {
if(tokenName == “weapon”) {
return ownerAndHisWeaponTokensDictionary[owner].length;
}
else if(tokenName == “vehicle”) {
return ownerAndHisVehicleTokensDictionary[owner].length;
}
}
And you’d do something similar for ownerOf()?
Is this allowable?
And is this even the right approach tackling this - or is there a different way to reason about all of this and approach it differently?
My approach would be to define 3 separate contracts on 3 separate addresses:
address 0x123123 as the Weapons ERC-721 token contract
address 0x456456 as the Vehicles ERC-721 token contract
address 0x789789 as the actual game contract
In the game contract, you can then call the NFTs contracts to get or validate values:
function attack(uint attackerWeaponId) {
require(weaponsContract.isOwnerOf(msg.sender, attackerWeaponId));
// ...
}
The isOwnerOf() function takes 2 arguments, address owner and uint256 weaponId. Also, a user can probably own more weapons so that's why I'm showing the validation.
And the weapons contract balanceOf(address) would reflect the total amount of the Weapon NFTs that the user has.
mapping (address => Weapon[]) userOwnedWeapons;
function balanceOf(address owner) external view returns (uint256) {
return userOwnedWeapons[msg.sender].length;
}
Related
Im new to Solidity, I got a problem that when I call purchaseCard function, it always returns the error The called function should be payable if you send value and the value you send should be less than your current balance. on Remix IDE.
I think the problem is at the complexity of my structs or I probably called variables not allowed. But I still cant find fix after many days researching.
These are my structs.
struct Card {
string name;
uint price;
}
mapping(uint => Card) public cards;
uint public numberOfTypes;
struct Purchase {
Card card;
uint purchaseDate;
}
struct User {
uint numberOfCards;
Purchase[] purchase;
bool exist;
}
mapping(address => User) public users;
And these are my functions, I will make it short.
// function addCard(string memory _name, uint _price) public isOwner;
// function removeCard(uint _id) public isOwner;
// function checkExistedUser(address _userAddress) public view returns(bool);
function purchaseCard(uint _id) public {
User storage user = users[msg.sender];
if (!checkExistedUser(msg.sender)) {
user.exist = true;
user.numberOfCards = 0;
}
Purchase storage purchase = user.purchase[user.numberOfCards];
purchase.card = cards[_id];
purchase.purchaseDate = block.timestamp;
user.numberOfCards++;
}
// function expiredCard(uint _id) public;
// function showRemainingDate(uint _id) public view returns(uint);
// function showPurchasedCards() public view returns (Purchase[] memory);
This is my full code: https://pastebin.com/4HXDZZVK
Thank you very much, I hope to learn more things.
In this case, you cannot create an empty 'space' for purchase array in storage before fill it because the type of this operation is an array and not a mapping!
To solve this issue, you can use the push() method (available only for storage array) to fill purchase() array into Purchase struct.
Following your logic, you must change purchaseCard() method in this way:
function purchaseCard(uint _id) public {
User storage user = users[msg.sender];
if (!checkExistedUser(msg.sender)) {
user.exist = true;
user.numberOfCards = 0;
}
user.purchase.push(Purchase(cards[_id], block.timestamp));
user.numberOfCards++;
}
In Solidity, If you have to send/receive ETHs then the payable function should be used.
Try this:
function purchaseCard(uint _id) public payable {
or add payable to your constructor.
pls refer to this: https://solidity-by-example.org/payable/
And btw pls next time share the complete code snippet.
I can't able to understand the entire code and flow.. but while debugging..
the revert occurs in the belowline of the purchasecard function
Purchase storage purchase = user.purchase[user.numberOfCards];
It seems like you are accessing the user mapping which does not contain any User Struct..
I think you forgot to assing the value to the user mapping in the addCard Function.
I may be wrong and I hope it helps..
function sendGift(uint256 _mintAmount,address recipient) public payable {
uint256 supply = totalSupply();
require(!paused);
require(_mintAmount > 0);
require(_mintAmount <= maxMintAmount);
require(supply + _mintAmount<= availableSupplyForSale);
//require(_amount >= cost * _mintAmount);
require(coinToken.allowance(msg.sender,address(this))>=cost * _mintAmount);
coinToken.transferFrom(msg.sender, address(this),cost * _mintAmount);
if(supply<currentSupply){
for (uint256 i = 1; i <= _mintAmount; i++) {
_safeMint(recipient, supply + i);
}
}
else{
uint256[] memory tokenIds = walletOfOwner(address(this));
for(uint256 i=1;i<=_mintAmount;i++)
transferFrom(address(this),recipient,tokenIds[i]);
}
}
Do I need to use payable here? Contract does not take any matic. It only takes custom token as payment .
(bool os, ) = payable(admin).call{value: address(this).balance}("");
require(os);
Also since I am not taking any matic, will this above line necessary for withdraw assets from contract as an owner? I have a sense that this above line only is useful to withdraw eth/polygon.
I am a new blockchain kid. Please help.
The payable modifier of a function is required when your function accepts native tokens (ETH, BNB, MATIC, ... depending on the network).
So in this case, you can safely remove it from the function header.
// removed `payable`
function sendGift(uint256 _mintAmount,address recipient) public {
The low-level .call() also doesn't require using payable to send native tokens.
payable(admin).call{value: address(this).balance}("");
However, if you used the high-level .transfer(), then you'd need to cast the admin variable type address to its extension type address payable using the typecasting function.
// will not work as it's type `address`
admin.transfer(address(this).balance);
// need to cast type `address` to type `address payable`
payable(admin).transfer(address(this).balance);
In order to withdraw tokens from your contract address, you need to invoke the transfer() function (defined in the ERC-20 standard) on the token contract. Do not confuse it with the native transfer() function of address payable, these are two separate things, just with the same name.
interface IERC20 {
function transfer(address, uint256) external returns (bool);
}
contract MyContract {
function withdrawToken() {
IERC20(tokenContractAddress).transfer(recipient, amount);
}
}
I have a problem where I'm trying to implement a smart contract with 'future maintainability' software design in mind. Suppose there is a contract which represents a person personal.sol We have another contract called record.col This record at the start only requires to be handled by the police department if they want to send some data to the personal.sol and change it's state. Later this record.sol needs to be modified for it be used by the hospital hospital.sol
There will be inheritance and abstract method required and right now, I don't exactly know how to do it. The following code should further clarify what I'm trying to achieve.
Person.sol
contract Person
{
Record[] records
strut Record{
string name;
uint time;
}
function updateRecords(string _name, uint _time){
Record _record = Record({name:_name,time:_time});
records.push(_record);
}
}
Record.sol
contract Record{
contract Person {
struct Record{} // can this work? Object properties are defined in Person
function updateRecords(string _name, uint _time){};
}
function commit(address _personaddr, string _name, uint _time){
_personaddr.transfer(address(this).balance;
Person person = Person.at(_personaddr); // creates an instance of the Person contract located at _personaddr address
person.updateRecords(_name,_time);
}
function abstractMethod() {}
// an abstract method which must be defined in contracts extending this
}
}
Police.sol
contract Police is Record {
//inherits updateRecords & abstract abstractMethod
function policeNewMethod(address _personaddr, string _name, uint _time){
// does something neww
commit(address _personaddr, string _name, uint _time);
}
function abstractMethod(){
//police own implementation of the method
}
}
Hospital.sol
contract Hospital is Record {
//inherits updateRecords & abstract abstractMethod
function HospitalNewMethod{
// does something
commit(address _personaddr, string _name, uint _time);
}
function abstractMethod(){
//police own implementation of the method
}
}
I don't want contracts extending Record.sol to interact directly with Person.sol's updateRecords() method. Instead, a check should be implemented to verify the contract calling updateRecords() is indeed an extention of solidity file Record.sol Is there a way to check this type like it is in Java instanceOf or .getClass
Sorry for the bad formatting but I wrote it all in an editor. It doesn't translate the indentation smoothly
The short answer is no at least in plain solidity you may be able to do it using assembly but I wouldn't know about that, you could put a function in Record.sol such as
function isRecord() public pure returns(bool){
return true;
}
And call that from the Person contract to verify that the calling contract does contain the record class. Although this could open you up to security vulnerabilities depending on what you will be doing with these contracts.
I am making a simple smart contract that is essentially a ledger that people can sign with a string (UI to come) and then pass onto a next person (using their ether address). I just wanted to create something that could be passed from person to person while recording its journey.
The logic of 'transferring' the signing-ledger is all done within the smart contract, with it existing as a mapping and the key value of an address changing from 0 to 1 signifying ownership (1 person owning it at a time). Because there's no actual transfer of ether/actual monetary value, where would actual ethereum transactions be occurring in this contract?
Also to sign the ledger I verify that the public key signing it has 1 as its value in the mapping, but how would I check that that person owns that public key (using a private key)?
my solidity code so far:
pragma solidity ^0.4.4;
contract Pass{
mapping(address => bool) ownership;
mapping(address => string) notes;
constructor(address genesis) public {
ownership[genesis] = true; //this address starts with it
}
function checkOwnership(address p) public view returns(bool){
if(ownership[p]){
return true;
}
return false;
}
function sign(string signedNote) public returns(uint){ // 1 on success 0 on fail
if(checkOwnership(msg.sender)){ //if msg.sender owns the note
notes[msg.sender] = signedNote;
return 1;
}
return 0;
}
function pass(address recipient) public returns(uint){ // 1 on success 0 on fail
if(checkOwnership(msg.sender)){ //if msg.sender owns the note
ownership[msg.sender] = 0;
ownership[recipient] = 1;
return 1;
}
return 0;
}
function viewNotes(address participant) public returns(string){ // signed note on success nothing on fail
if(notes[participant] !== 0){
return (notes(participant));
}
}
}
The logic of 'transferring' the signing-ledger is all done within the smart contract, with it existing as a mapping and the key value of an address changing from 0 to 1 signifying ownership (1 person owning it at a time)
This is a common pattern known as the owner pattern. You can simplify this by simply keeping track of a single owner address and updating that, instead of using a mapping, since you only care about the current owner. Something as simple as:
address public owner;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
constructor() {
owner = msg.sender;
}
function transferOwnership(address _owner) onlyOwner {
owner = _owner;
}
Open Zeppelin has a more complete implementation of ownership here.
Because there's no actual transfer of ether/actual monetary value, where would actual ethereum transactions be occurring in this contract?
Transactions do not require ether movement. You can call any public/external function on your contract with a 0 value transaction, and pass data to it. The transaction will execute on the blockchain like any other, and run the code you invoke within the contract.
Also to sign the ledger I verify that the public key signing it has 1 as its value in the mapping, but how would I check that that person owns that public key (using a private key)?
You are already checking is msg.sender matches the whitelisted address. For basic transactions, this means that the transaction has been signed by the address in msg.sender (msg.sender can also be a contract, if another contract calls yours. To check who actually signed the transaction in all scenarios, you must use tx.origin).
We have a problem with contract redeploying. Each time when some logic is changed during new contract version deployment we are loosing all contract related data (which are stored in arrays, mappings). Then we need to execute data load procedures in order to restore environment to desired state which is time consuming action. I tried to split contract to tow ones (AbcDataContract, AbcActionsContract) but faced with problem of accessing to the mappings : Error: Indexed expression has to be a type, mapping or array (is function (bytes32) view external returns (uint256))
Initial contract :
contract AbcContract {
EntityA[] public entities;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
/* Events */
event Event1(uint id);
event Event2(uint id);
/* Structures */
struct EntityA {
string field1;
string field2;
bool field3;
uint field4;
Status field5;
}
enum Status {PROPOSED, VOTED, CONFIRMED}
function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)
}
Refactored contracts :
contract AbcDataContract {
EntityA[] public items;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
/* Events */
event Event1(uint id);
event Event2(uint id);
/* Structures */
struct EntityA {
string field1;
string field2;
bool field3;
uint field4;
Status proposalStatus;
}
enum Status {PROPOSED, VOTED, CONFIRMED}
}
contract AbcActionsContract {
AbcDataContract abcDataContract;
/* constructor */
function AbcActionsContract(address _AbcDataContract) {
abcDataContract = AbcDataContract(_AbcDataContract);
}
/* accessing to the mapping like abcDataContract.mapping1[someId] will raise Solidity compile error */
function function1(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping2[someId] will raise Solidity compile error */
function function2(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping3[someId] will raise Solidity compile error */
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)
}
We would like to implement approach like we have in DB development when logic changes in stored procedures/views/other not data objects usually does not affect data itself. What is the best design solution for this problem ?
The first part of your question is fairly easy. To access a public mapping in another contract, simply use ():
abcDataContract.mapping1(someId)
Of course, you can also provide your own access methods to AbcDataContract instead of using the public mapping as well. If you go down this path, I'd recommend going through an interface to access your data contract
As for the design part of your question, it looks like you're on the right track. Separating your data store into its own contract has huge benefits. Not only is it much easier to deploy since you don't have to worry about migrating your data, but it's also much cheaper to deploy the new contract.
That being said, there's a couple things I want to point out with the refactored version you posted.
It's hard to tell what you're planning on doing with Struct1. There's no reference to it in your pseudocode. You can't return structs from functions in Solidity unless they are internal calls (or you explicitly decompose the struct).
Similarly, you can't return strings between contracts. If you plan on using Struct1.field1/2 in AbcActionsContract, you'll need to convert them to bytes32.
You'll probably want to move your event definitions into your business logic contract.
Separating your data store from your business logic is a key component to upgrading contracts. Using interfaces and libraries help with this. There are several blog posts out there addressing this issue. I personally recommend starting with this one along with a follow up here.
Here is approximate design of contracts which should resolve issue with losing data due to deployment some changes in contract's business logic :
contract DomainObjectDataContract {
struct DomainObject {
string field1;
string field2;
bool field3;
uint field4;
Status field5;
}
enum Status {PROPOSED, VOTED, CONFIRMED}
//primitives
//getters/setters for primitives
/arrays
DomainObject[] public entities;
//getters(element by id)/setters(via push function)/counting functions
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
//getters(element by id/ids)/setters(depends from the mapping structure)/counting functions
}
contract DomainObjectActionsContract {
DomainObjectDataContract domainObjectDataContract;
/*constructor*/
function DomainObjectActionsContract(address _DomainObjectDataContract) {
domainObjectDataContract = DomainObjectDataContract(_DomainObjectDataContract);
}
/* functions which contain business logic and access/change data via domainObjectDataContract.* Redeploying of this contract will not affect data*/
function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)
}
One of the pending design issues is application pagination capabilities. Let's suppose we have following structure :
struct EntityA {
string lessThen32ByteString1;
string moreThen32ByteString1;
string lessThen32ByteString2;
string moreThen32ByteString3;
bool flag;
uint var1;
uint var2;
uint var3;
uint var4;
ProposalStatus proposalStatus;
}
// 100K entities
EntityA[] public items;
And we need to return subset of data based on offset and limit to our UI per one contract's function invocation. Due to different limits/errors of Solidity our current functions (with helper functions for string to byte32 conversion, splitting string to a few byte32 parts and so on) looks like this :
function getChunkOfPart1EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString1Arr, bytes32[100] moreThen32ByteString1PrefixArr, bytes32[100] moreThen32ByteString1SuffixArr) {
}
function getChunkOfPart2EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString2Arr, bytes32[100] moreThen32ByteString2PrefixArr, bytes32[100] moreThen32ByteString2SuffixArr) {
}
function getChunkOfPart3EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bool[100] flagArr, uint[100] var1Arr, uint[100] var2Arr, uint[100] var3Arr, uint[100] var4Arr, ProposalStatus[100] proposalStatusArr,) {
}
Definitely they looks like awful from design perspective but we still have no better solution for pagination. I am not even saying that there is no any query language support, even basic filtering by some field require manual implementation.