Skip to content

Commit

Permalink
Optimize gas consumption (#55)
Browse files Browse the repository at this point in the history
* add benchmark DuckyGame minting

* replace ERC721Enumerable with counter

* rename duckies-mint to game-mint script

* add meld benchmark

---------

Co-authored-by: nksazonov <[email protected]>
  • Loading branch information
nksazonov and nksazonov authored Apr 25, 2023
1 parent c7ec6c9 commit de543d2
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 21 deletions.
22 changes: 18 additions & 4 deletions contracts/duckies/ducklings/DucklingsV1.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;

import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721RoyaltyUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';
Expand All @@ -25,7 +25,7 @@ import '../games/Genome.sol';
contract DucklingsV1 is
Initializable,
IDucklings,
ERC721EnumerableUpgradeable,
ERC721Upgradeable,
ERC721RoyaltyUpgradeable,
UUPSUpgradeable,
AccessControlUpgradeable
Expand Down Expand Up @@ -57,6 +57,7 @@ contract DucklingsV1 is
// Server address that is prepended to tokenURI
string public apiBaseURL;

CountersUpgradeable.Counter internal _totalSupply;
CountersUpgradeable.Counter public nextNewTokenId;
mapping(uint256 => Duckling) public tokenToDuckling;

Expand Down Expand Up @@ -95,13 +96,26 @@ contract DucklingsV1 is

// -------- ERC721 --------

function totalSupply() external view returns (uint256) {
return _totalSupply.current();
}

/**
* @notice Necessary override to specify what implementation of _burn to use.
* @dev Necessary override to specify what implementation of _burn to use.
*/
function _burn(uint256 tokenId) internal override(ERC721RoyaltyUpgradeable, ERC721Upgradeable) {
// check on token existence is performed in ERC721Upgradeable._burn
super._burn(tokenId);

_totalSupply.decrement();
}

function _safeMint(address to, uint256 tokenId) internal override(ERC721Upgradeable) {
// check on token existence is performed in ERC721Upgradeable._burn
super._safeMint(to, tokenId);

_totalSupply.increment();
}

/**
Expand Down Expand Up @@ -137,7 +151,7 @@ contract DucklingsV1 is
virtual
override(
IERC165Upgradeable,
ERC721EnumerableUpgradeable,
ERC721Upgradeable,
ERC721RoyaltyUpgradeable,
AccessControlUpgradeable
)
Expand Down Expand Up @@ -304,7 +318,7 @@ contract DucklingsV1 is
address to,
uint256 firstTokenId,
uint256 batchSize
) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) {
) internal override(ERC721Upgradeable) {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);

// mint and burn for not transferable is allowed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ contract TESTDuckyFamilyV1 is DuckyFamilyV1 {
emit GenomeReturned(_generateGenome(collectionId));
}

function generateRarity() external {
emit Uint8Returned(uint8(_generateRarity()));
}

function generateAndSetGenes(uint256 genome, uint8 collectionId) external {
emit GenomeReturned(_generateAndSetGenes(genome, collectionId));
}
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "clearsync-project",
"scripts": {
"lint": "npx eslint \"./\" && prettier '**/*.{json,sol,md}' -c && solhint 'contracts/**/*.sol'",
"lint:fix": "eslint '**/*.{js,ts}' --fix && prettier '**/*.{json,sol,md}' --write && solhint 'contracts/**/*.sol' --fix"
"lint:fix": "eslint '**/*.{js,ts}' --fix && prettier '**/*.{json,sol,md}' --write && solhint 'contracts/**/*.sol' --fix",
"bench:game-mint": "REPORT_GAS=true npx hardhat test 'test/benchmarks/DuckyFamilyV1/benchmark_mint.test.ts'",
"bench:game-meld": "REPORT_GAS=true npx hardhat test 'test/benchmarks/DuckyFamilyV1/benchmark_meld.test.ts'"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
Expand Down
127 changes: 127 additions & 0 deletions test/benchmarks/DuckyFamilyV1/benchmark_meld.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { assert } from 'chai';

import {
GenerateAndMintGenomesFunctT,
MintToFuncT,
randomGenome,
randomGenomes,
setupGenerateAndMintGenomes,
setupMintTo,
} from '../../duckies/games/DuckyFamily/helpers';
import {
Collections,
DucklingGenes,
FLOCK_SIZE,
GeneDistrTypes,
Rarities,
ZombeakGenes,
collectionsGeneValuesNum,
} from '../../duckies/games/DuckyFamily/config';
import { setup } from '../../duckies/games/DuckyFamily/setup';

import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import type { DucklingsV1, DuckyFamilyV1, TESTDuckyFamilyV1 } from '../../../typechain-types';

describe('Benchmark DuckyFamilyV1 melding', () => {
let Someone: SignerWithAddress;
let GenomeSetter: SignerWithAddress;

let Ducklings: DucklingsV1;
let Game: TESTDuckyFamilyV1;

let GameAsSomeone: DuckyFamilyV1;

let mintTo: MintToFuncT;
let generateAndMintGenomes: GenerateAndMintGenomesFunctT;

const isCollectionMutating = async (rarity: Rarities): Promise<boolean> => {
const tx = await Game.isCollectionMutating(rarity);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'BoolReturned');
return event?.args?.returnedBool as boolean;
};

const meldGenomes = async (genomes: bigint[]): Promise<bigint> => {
const tx = await Game.meldGenomes(genomes);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'GenomeReturned');
return event?.args?.genome.toBigInt() as bigint;
};

const meldGenes = async (
genomes: bigint[],
gene: number,
maxGeneValue: number,
geneDistrType: GeneDistrTypes,
): Promise<number> => {
const tx = await Game.meldGenes(genomes, gene, maxGeneValue, geneDistrType);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'GeneReturned');
// gene is already a number
return event?.args?.gene as number;
};

beforeEach(async () => {
({ Someone, GenomeSetter, Ducklings, Game, GameAsSomeone } = await setup());

mintTo = setupMintTo(Ducklings.connect(GenomeSetter));
generateAndMintGenomes = setupGenerateAndMintGenomes(mintTo, Someone.address);
});

it('meld Common Ducklings', async () => {
const { tokenIds } = await generateAndMintGenomes(Collections.Duckling, {
[DucklingGenes.Rarity]: Rarities.Common,
[DucklingGenes.Color]: 0,
});

await GameAsSomeone.meldFlock(tokenIds);
});

it('meld Common Zombeaks', async () => {
const { tokenIds } = await generateAndMintGenomes(Collections.Zombeak, {
[ZombeakGenes.Rarity]: Rarities.Common,
[ZombeakGenes.Color]: 0,
});

try {
await GameAsSomeone.meldFlock(tokenIds);
assert(true);
} catch {
assert(false);
}
});

it('meldGenomes', async () => {
await Game.setCollectionMutationChances([0, 0, 0, 0]);

const genomes = randomGenomes(Collections.Duckling, {
amount: FLOCK_SIZE,
[DucklingGenes.Rarity]: Rarities.Common,
[DucklingGenes.Color]: 0,
});

await meldGenomes(genomes);
});

it('isCollectionMutating', async () => {
await isCollectionMutating(Rarities.Common);
});

it('meldGenes', async () => {
const geneValuesNum = collectionsGeneValuesNum[Collections.Duckling];
const genomes = [];
for (let i = 0; i < FLOCK_SIZE; i++) {
const genome = randomGenome(Collections.Duckling, {
[DucklingGenes.Head]: i,
});
genomes.push(genome);
}

await meldGenes(
genomes,
DucklingGenes.Head,
geneValuesNum[DucklingGenes.Head],
GeneDistrTypes.Uneven,
);
});
});
74 changes: 74 additions & 0 deletions test/benchmarks/DuckyFamilyV1/benchmark_mint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { setup } from '../../duckies/games/DuckyFamily/setup';
import { Collections, GeneDistrTypes } from '../../duckies/games/DuckyFamily/config';

import type { DuckyFamilyV1, TESTDuckyFamilyV1 } from '../../../typechain-types';

describe('Benchmark DuckyFamilyV1 minting', () => {
let Game: TESTDuckyFamilyV1;
let GameAsSomeone: DuckyFamilyV1;

const generateGenome = async (collectionId: Collections): Promise<bigint> => {
const tx = await Game.generateGenome(collectionId);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'GenomeReturned');
return event?.args?.genome.toBigInt() as bigint;
};
const generateRarity = async (): Promise<number> => {
const tx = await Game.generateRarity();
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'Uint8Returned');
return event?.args?.uint8 as number;
};

const generateAndSetGenes = async (
genome: bigint,
collectionId: Collections,
): Promise<bigint> => {
const tx = await Game.generateAndSetGenes(genome, collectionId);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'GenomeReturned');
return event?.args?.genome.toBigInt() as bigint;
};

const generateAndSetGene = async (
genome: bigint,
geneIx: number,
geneValuesNum: number,
distrType: GeneDistrTypes,
): Promise<bigint> => {
const tx = await Game.generateAndSetGene(genome, geneIx, geneValuesNum, distrType);
const receipt = await tx.wait();
const event = receipt.events?.find((e) => e.event === 'GenomeReturned');
return event?.args?.genome.toBigInt() as bigint;
};

beforeEach(async () => {
({ Game, GameAsSomeone } = await setup());
await Game.setMintPrice(1);
});

it('mint', async () => {
await GameAsSomeone.mintPack(1);
await GameAsSomeone.mintPack(1);
});

it('generateGenome', async () => {
await generateGenome(Collections.Duckling);
});

it('generateRarity', async () => {
await generateRarity();
});

it('generateAndSetGenes', async () => {
await generateAndSetGenes(0n, Collections.Duckling);
});

it('generateAndSetGene even', async () => {
await generateAndSetGene(0n, 0, 2, GeneDistrTypes.Even);
});

it('generateAndSetGene uneven', async () => {
await generateAndSetGene(0n, 0, 2, GeneDistrTypes.Uneven);
});
});
33 changes: 17 additions & 16 deletions test/ducklings/DucklingsV1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const API_BASE_URL = 'test-url.com';

const GENOME = new Genome().setGene(magicNumberGeneIdx, baseMagicNumber).genome;
const MYTHIC_GENOME = new Genome().setGene(magicNumberGeneIdx, mythicMagicNumber).genome;
const TRANSFERABLE_GENOME = randomGenome(0, { isTransferable: true });

describe('DucklingsV1', () => {
let Admin: SignerWithAddress;
Expand Down Expand Up @@ -376,8 +377,8 @@ describe('DucklingsV1', () => {

describe('transferability', () => {
it('isTransferable return true for transferable token', async () => {
const transferableGenome = randomGenome(0, { isTransferable: true });
await mintTo(Someone.address, transferableGenome);
const TRANSFERABLE_GENOME = randomGenome(0, { isTransferable: true });
await mintTo(Someone.address, TRANSFERABLE_GENOME);
expect(await Ducklings.isTransferable(0)).to.equal(true);
});

Expand All @@ -394,8 +395,7 @@ describe('DucklingsV1', () => {
});

it('can tranfer transferable token', async () => {
const transferableGenome = randomGenome(0, { isTransferable: true });
await mintTo(Someone.address, transferableGenome);
await mintTo(Someone.address, TRANSFERABLE_GENOME);
await DucklingsAsSomeone.transferFrom(Someone.address, Someother.address, 0);
expect(await Ducklings.ownerOf(0)).to.equal(Someother.address);
});
Expand Down Expand Up @@ -480,28 +480,29 @@ describe('DucklingsV1', () => {
});
});

describe('ERC721Enumerable', () => {
it('return correct totalSupply', async () => {
describe.only('ERC721Enumerable', () => {
it('mint increates totalSupply', async () => {
await mintTo(Someone.address, GENOME);
await mintTo(Someone.address, MYTHIC_GENOME);
expect(await Ducklings.totalSupply()).to.equal(2);
});

it('return correct tokenOfOwnerByIndex', async () => {
it('burn decreases totalSupply', async () => {
await mintTo(Someone.address, GENOME);
await mintTo(Someone.address, MYTHIC_GENOME);
expect(await Ducklings.tokenOfOwnerByIndex(Someone.address, 0)).to.equal(0);
expect(await Ducklings.tokenOfOwnerByIndex(Someone.address, 1)).to.equal(1);
expect(await Ducklings.totalSupply()).to.equal(2);

await mintTo(Someother.address, GENOME);
expect(await Ducklings.tokenOfOwnerByIndex(Someother.address, 0)).to.equal(2);
await DucklingsAsGame.burn(0);
expect(await Ducklings.totalSupply()).to.equal(1);
});

it('return correct tokenByIndex', async () => {
await mintTo(Someone.address, GENOME);
await mintTo(Someother.address, MYTHIC_GENOME);
expect(await Ducklings.tokenByIndex(0)).to.equal(0);
expect(await Ducklings.tokenByIndex(1)).to.equal(1);
it('transfer does not affect totalSupply', async () => {
await mintTo(Someone.address, TRANSFERABLE_GENOME);
await mintTo(Someone.address, MYTHIC_GENOME);
expect(await Ducklings.totalSupply()).to.equal(2);

await DucklingsAsSomeone.transferFrom(Someone.address, Someother.address, 0);
expect(await Ducklings.totalSupply()).to.equal(2);
});
});

Expand Down

0 comments on commit de543d2

Please sign in to comment.