Skip to content

Commit

Permalink
PBTRandom gas optimizations (#27)
Browse files Browse the repository at this point in the history
* Gas optimizations

* Update
  • Loading branch information
cygaar authored Nov 8, 2022
1 parent 6be7b3e commit cf71ad1
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 28 deletions.
9 changes: 9 additions & 0 deletions gas-snapshot → .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ ERC721ReadOnlyTest:testGetApproved() (gas: 16236)
ERC721ReadOnlyTest:testIsApprovedForAll() (gas: 10051)
ERC721ReadOnlyTest:testSetApprovalForAll() (gas: 10714)
ERC721ReadOnlyTest:testTransferFunctions() (gas: 19077)
PBTRandomTest:testGetTokenDataForChipSignature() (gas: 210148)
PBTRandomTest:testGetTokenDataForChipSignatureInvalid() (gas: 216406)
PBTRandomTest:testIsChipSignatureForToken() (gas: 215744)
PBTRandomTest:testMintTokenWithChip() (gas: 166964)
PBTRandomTest:testSupportsInterface() (gas: 7037)
PBTRandomTest:testTokenIdFor() (gas: 153746)
PBTRandomTest:testTransferTokenWithChip(bool) (runs: 256, μ: 225474, ~: 225393)
PBTRandomTest:testUpdateChips() (gas: 320443)
PBTRandomTest:testUseRandomAvailableTokenId() (gas: 117179)
PBTSimpleTest:testGetTokenDataForChipSignature() (gas: 127958)
PBTSimpleTest:testGetTokenDataForChipSignatureBlockNumTooOld() (gas: 122512)
PBTSimpleTest:testGetTokenDataForChipSignatureInvalid() (gas: 131804)
Expand Down
46 changes: 21 additions & 25 deletions src/PBTRandom.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
using ECDSA for bytes32;

struct TokenData {
uint128 tokenId;
uint256 tokenId;
address chipAddress;
bool set;
}
Expand All @@ -31,13 +31,13 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
mapping(address => TokenData) _tokenDatas;

// Max token supply
uint128 public immutable maxSupply;
uint128 private _numAvailableRemainingTokens;
uint256 public immutable maxSupply;
uint256 private _numAvailableRemainingTokens;

// Data structure used for Fisher Yates shuffle
mapping(uint128 => uint128) internal _availableRemainingTokens;
mapping(uint256 => uint256) internal _availableRemainingTokens;

constructor(string memory name_, string memory symbol_, uint128 maxSupply_) ERC721ReadOnly(name_, symbol_) {
constructor(string memory name_, string memory symbol_, uint256 maxSupply_) ERC721ReadOnly(name_, symbol_) {
maxSupply = maxSupply_;
_numAvailableRemainingTokens = maxSupply_;
}
Expand All @@ -57,24 +57,22 @@ contract PBTRandom is ERC721ReadOnly, IPBT {

for (uint256 i = 0; i < chipAddressesOld.length; i++) {
address oldChipAddress = chipAddressesOld[i];
TokenData memory oldTokenData = _tokenDatas[oldChipAddress];
if (!oldTokenData.set) {
if (!_tokenDatas[oldChipAddress].set) {
revert UpdatingChipForUnsetChipMapping();
}
address newChipAddress = chipAddressesNew[i];
uint128 tokenId = oldTokenData.tokenId;
uint256 tokenId = _tokenDatas[oldChipAddress].tokenId;
_tokenDatas[newChipAddress] = TokenData(tokenId, newChipAddress, true);
emit PBTChipRemapping(tokenId, oldChipAddress, newChipAddress);
delete _tokenDatas[oldChipAddress];
}
}

function tokenIdFor(address chipAddress) external view override returns (uint256) {
TokenData memory tokenData = _tokenDatas[chipAddress];
if (!tokenData.set) {
if (!_tokenDatas[chipAddress].set) {
revert NoMintedTokenForChip();
}
return tokenData.tokenId;
return _tokenDatas[chipAddress].tokenId;
}

// Returns true if the signer of the signature of the payload is the chip for the token id
Expand All @@ -89,8 +87,7 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
}
bytes32 signedHash = keccak256(payload).toEthSignedMessageHash();
address chipAddr = signedHash.recover(signature);
TokenData memory tokenData = _tokenDatas[chipAddr];
return tokenData.set && tokenData.tokenId == tokenId;
return _tokenDatas[chipAddr].set && _tokenDatas[chipAddr].tokenId == tokenId;
}

// Parameters:
Expand All @@ -104,14 +101,13 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
{
address chipAddr = _getChipAddrForChipSignature(signatureFromChip, blockNumberUsedInSig);

TokenData memory tokenData = _tokenDatas[chipAddr];
if (tokenData.set) {
if (_tokenDatas[chipAddr].set) {
revert ChipAlreadyLinkedToMintedToken();
} else if (tokenData.chipAddress != chipAddr) {
} else if (_tokenDatas[chipAddr].chipAddress != chipAddr) {
revert InvalidChipAddress();
}

uint128 tokenId = _useRandomAvailableTokenId();
uint256 tokenId = _useRandomAvailableTokenId();
_mint(_msgSender(), tokenId);
_tokenDatas[chipAddr] = TokenData(tokenId, chipAddr, true);

Expand Down Expand Up @@ -140,17 +136,17 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
// - update the _availableRemainingTokens mapping state
// - set _availableRemainingTokens[randIndex] to either the index or the value of the last entry in the mapping (depends on the last entry's state)
// - decrement _numAvailableRemainingTokens to mimic the shrinking of an array
function _useRandomAvailableTokenId() internal returns (uint128) {
uint128 numAvailableRemainingTokens = _numAvailableRemainingTokens;
function _useRandomAvailableTokenId() internal returns (uint256) {
uint256 numAvailableRemainingTokens = _numAvailableRemainingTokens;
if (numAvailableRemainingTokens == 0) {
revert NoMoreTokenIds();
}

uint256 randomNum = _getRandomNum(numAvailableRemainingTokens);
uint128 randomIndex = uint128(randomNum % numAvailableRemainingTokens);
uint128 valAtIndex = _availableRemainingTokens[randomIndex];
uint256 randomIndex = randomNum % numAvailableRemainingTokens;
uint256 valAtIndex = _availableRemainingTokens[randomIndex];

uint128 result;
uint256 result;
if (valAtIndex == 0) {
// This means the index itself is still an available token
result = randomIndex;
Expand All @@ -159,11 +155,11 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
result = valAtIndex;
}

uint128 lastIndex = numAvailableRemainingTokens - 1;
uint256 lastIndex = numAvailableRemainingTokens - 1;
if (randomIndex != lastIndex) {
// Replace the value at randomIndex, now that it's been used.
// Replace it with the data from the last index in the array, since we are going to decrease the array size afterwards.
uint128 lastValInArray = _availableRemainingTokens[lastIndex];
uint256 lastValInArray = _availableRemainingTokens[lastIndex];
if (lastValInArray == 0) {
// This means the index itself is still an available token
_availableRemainingTokens[randomIndex] = lastIndex;
Expand Down Expand Up @@ -215,7 +211,7 @@ contract PBTRandom is ERC721ReadOnly, IPBT {
bool useSafeTransferFrom
) internal virtual {
TokenData memory tokenData = _getTokenDataForChipSignature(signatureFromChip, blockNumberUsedInSig);
uint128 tokenId = tokenData.tokenId;
uint256 tokenId = tokenData.tokenId;
if (useSafeTransferFrom) {
_safeTransfer(ownerOf(tokenId), _msgSender(), tokenId, "");
} else {
Expand Down
6 changes: 3 additions & 3 deletions src/mocks/PBTRandomMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.13;
import "../PBTRandom.sol";

contract PBTRandomMock is PBTRandom {
constructor(string memory name_, string memory symbol_, uint128 supply_) PBTRandom(name_, symbol_, supply_) {}
constructor(string memory name_, string memory symbol_, uint256 supply_) PBTRandom(name_, symbol_, supply_) {}

function mint(address to, uint256 tokenId) public {
_mint(to, tokenId);
Expand Down Expand Up @@ -37,11 +37,11 @@ contract PBTRandomMock is PBTRandom {
return _getTokenDataForChipSignature(signatureFromChip, blockNumberUsedInSig);
}

function getAvailableRemainingTokens(uint128 index) public view returns (uint128) {
function getAvailableRemainingTokens(uint256 index) public view returns (uint256) {
return _availableRemainingTokens[index];
}

function useRandomAvailableTokenId() public returns (uint128) {
function useRandomAvailableTokenId() public returns (uint256) {
return _useRandomAvailableTokenId();
}
}

0 comments on commit cf71ad1

Please sign in to comment.