Hi All
-
_createKitty()
function is of constant complexity because it implies just one array push and setting one mapping element.
-
getAllCatsFor()
function contains a for loop that iterates through the whole kitty population to scan the owner. Complexity therefore increases with the total kitty supply.
-
Instead of scanning the whole kitty population for every call to getAllCatsFor()
, it is possible to pre-build an array of owned kitties for each owner and update it whenever a kitty is transferred.
Added a new mapping kittyOwnerToIndex
that associates an owner with its array of owned kitties.
This array has to be kept up-to-date with the _transfer()
function.
It is easy to update ownedKitties
for the recipient with a simple push().
However updating the array for the sender of the kitty is a bit more tricky because an element has to be deleted from ownedKitties
. This is made possible with an additional mapping kittiesIndex
that keeps track of the index of a given kitty id in ownedKitties
array.
Measurements:
The table below shows getAllCatsFor()
execution costs (gas).
- The first figure indicates gas cost for an address owning the whole cat population.
- The 2nd figure indicates gas cost for an address owning zero cat.
|
Total population 3 cats |
Total population 10 cats |
Before modification |
10060, 8568 |
26994, 22141 |
After modification |
6139, 2708 |
13980, 2708 |
The new code shows a clear improvement in performance. Calling getAllCatsFor()
for an address owning zero cats is constant no matter the size of the kitty population (2708 gas for 3 and 10 cats).
Although independent of total kitty supply, getAllCatsFor()
execution cost increases with the size of the kitty array being returned (unfortunately it is returned by copy, which is expensive).
Pros: better performance of getAllCatsFor()
, independent of the total kitty population.
Cons: _transfer()
code is more complex and a bit harder to read, additional array and mappings take more storage space,
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract Kittycontract {
string public constant name = "TestKitties";
string public constant symbol = "TK";
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Birth(
address owner,
uint256 kittenId,
uint256 mumId,
uint256 dadId,
uint256 genes
);
struct Kitty {
uint256 genes;
uint64 birthTime;
uint32 mumId;
uint32 dadId;
uint16 generation;
}
Kitty[] kitties;
struct OwnedKitties{
uint256[] ownedKitties;
mapping(uint256 => KittiesIndex) kittiesIndex;
}
struct KittiesIndex{
uint256 index;
bool exists;
}
mapping (uint256 => address) public kittyIndexToOwner;
mapping (address => OwnedKitties) kittyOwnerToIndex;
function balanceOf(address owner) external view returns (uint256 balance){
return kittyOwnerToIndex[owner].ownedKitties.length;
}
function totalSupply() public view returns (uint) {
return kitties.length;
}
function ownerOf(uint256 _tokenId) external view returns (address)
{
return kittyIndexToOwner[_tokenId];
}
function transfer(address _to,uint256 _tokenId) external
{
require(_to != address(0));
require(_to != address(this));
require(_owns(msg.sender, _tokenId), "You do not own this token");
_transfer(msg.sender, _to, _tokenId);
}
function getAllCatsFor(address _owner) external view returns (uint[] memory cats){
return kittyOwnerToIndex[_owner].ownedKitties;
}
function createKittyGen0(uint256 _genes) public returns (uint256) {
return _createKitty(0, 0, 0, _genes, msg.sender);
}
function _createKitty(
uint256 _mumId,
uint256 _dadId,
uint256 _generation,
uint256 _genes,
address _owner
) private returns (uint256) {
Kitty memory _kitty = Kitty({
genes: _genes,
birthTime: uint64(block.timestamp),
mumId: uint32(_mumId),
dadId: uint32(_dadId),
generation: uint16(_generation)
});
kitties.push(_kitty);
uint256 newKittenId = kitties.length - 1;
emit Birth(_owner, newKittenId, _mumId, _dadId, _genes);
_transfer(address(0), _owner, newKittenId);
return newKittenId;
}
function _transfer(address _from, address _to, uint256 _tokenId) internal {
kittyIndexToOwner[_tokenId] = _to;
kittyOwnerToIndex[_to].ownedKitties.push(_tokenId);
uint256 addedTokenIndex = kittyOwnerToIndex[_to].ownedKitties.length-1;
kittyOwnerToIndex[_to].kittiesIndex[_tokenId].exists = true;
kittyOwnerToIndex[_to].kittiesIndex[_tokenId].index = addedTokenIndex;
if (_from != address(0)) {
assert(kittyOwnerToIndex[_from].kittiesIndex[_tokenId].exists == true);
//Update the kitties array
uint256 deletedTokenIndex = kittyOwnerToIndex[_from].kittiesIndex[_tokenId].index;
uint256 length = kittyOwnerToIndex[_from].ownedKitties.length;
assert(length > 0);
// Replace entry to delete with the last entry and pop the last entry off the array
kittyOwnerToIndex[_from].ownedKitties[deletedTokenIndex] = kittyOwnerToIndex[_from].ownedKitties[length-1];
kittyOwnerToIndex[_from].ownedKitties.pop();
assert(length > kittyOwnerToIndex[_from].ownedKitties.length);
kittyOwnerToIndex[_from].kittiesIndex[_tokenId].exists = false;
}
// Emit the transfer event.
emit Transfer(_from, _to, _tokenId);
}
function _owns(address _claimant, uint256 _tokenId) internal view returns (bool) {
return kittyIndexToOwner[_tokenId] == _claimant;
}
}