Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert for to do while to save gas on multiple runs #10

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
## PBT (Physical Backed Token)

NFT collectors enjoy collecting digital assets and sharing them with others online. However, there is currently no such standard for showcasing physical assets as NFTs with verified authenticity and ownership. Existing solutions are fragmented and tend to be susceptible to at least one of the following:
- The NFT cannot proxy as ownership of the physical item. In most current implementations, the NFT and physical item are functionally two decoupled distinct assets after the NFT mint, in which the NFT can be freely traded independently from the physical item.

- Verification of authenticity of the physical item requires action from a trusted entity (e.g. StockX).
- The NFT cannot proxy as ownership of the physical item. In most current implementations, the NFT and physical item are functionally two decoupled distinct assets after the NFT mint, in which the NFT can be freely traded independently from the physical item.

- Verification of authenticity of the physical item requires action from a trusted entity (e.g. StockX).

PBT aims to mitigate these issues in a decentralized way through a new token standard (an extension of EIP-721).

From the [Azuki](https://twitter.com/AzukiOfficial) team.
**Chiru Labs is not liable for any outcomes as a result of using PBT**, DYOR. Repo still in beta.


## Resources

- [pbt.io](https://www.pbt.io/)
- [EIP (Draft)](https://github.com/ethereum/EIPs/pull/5791)
- [Blog](https://www.azuki.com/updates/pbt)

- [pbt.io](https://www.pbt.io/)
- [EIP (Draft)](https://github.com/ethereum/EIPs/pull/5791)
- [Blog](https://www.azuki.com/updates/pbt)

## How does it work?

#### Requirements

This approach assumes that the physical item must have a chip attached to it that fulfills the following requirements ([Kong](https://arx.org/) is one such vendor for these chips):

- the ability to securely generate and store an ECDSA secp256k1 asymmetric keypair
- the ability to sign messages from the private key of the asymmetric keypair
- the ability for one to retrieve only the public key of the asymmetric keypair (private key non-extractable)
- The ability to securely generate and store an ECDSA secp256k1 asymmetric keypair
- The ability to sign messages from the private key of the asymmetric keypair
- The ability for one to retrieve only the public key of the asymmetric keypair (private key non-extractable)

The approach also requires that the contract uses an account-bound implementation of EIP-721 (where all EIP-721 functions that transfer must throw, e.g. the "read only NFT registry" implementation referenced in EIP-721). This ensures that ownership of the physical item is required to initiate transfers and manage ownership of the NFT, through a new function introduced in `IPBT.sol` (`transferTokenWithChip`).

#### Approach

On a high level:
- Each NFT is conceptually linked to a physical chip.
- The NFT can only be transferred to a different owner if a signature from the chip is supplied to the contract.
- This guarantees that a token cannot be transferred without consent from the owner of the physical item.

- Each NFT is conceptually linked to a physical chip.
- The NFT can only be transferred to a different owner if a signature from the chip is supplied to the contract.
- This guarantees that a token cannot be transferred without consent from the owner of the physical item.

More details available in the [EIP](https://github.com/ethereum/EIPs/pull/5791) and inlined into `IPBT.sol`.

#### Reference Implementation

A simple mint for a physical drop could look something like this:
```

```solidity
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chiru-labs/pbt/src/PBTSimple.sol";

contract Example is PBTSimple, Ownable {

/// @notice Initialize a mapping from chipAddress to tokenId.
/// @param chipAddresses The addresses derived from the public keys of the chips
constructor(address[] calldata chipAddresses, uint256[] calldata tokenIds)
constructor(address[] memory chipAddresses, uint256[] memory tokenIds)
PBTSimple("Example", "EXAMPLE")
{
_seedChipToTokenMapping(chipAddresses, tokenIds);
Expand All @@ -67,8 +68,8 @@ contract Example is PBTSimple, Ownable {
}
}
```
As mentioned above, this repo is still in beta and more documentation is on its way. Feel free to contact [@2pmflow](https://twitter.com/2pmflow) if you have any questions.

As mentioned above, this repo is still in beta and more documentation is on its way. Feel free to contact [@2pmflow](https://twitter.com/2pmflow) if you have any questions.

## Contributing

Expand Down
37 changes: 16 additions & 21 deletions gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
ERC721ReadOnlyTest:testApprove() (gas: 10677)
ERC721ReadOnlyTest:testGetApproved() (gas: 16236)
ERC721ReadOnlyTest:testIsApprovedForAll() (gas: 10051)
ERC721ReadOnlyTest:testSetApprovalForAll() (gas: 10714)
ERC721ReadOnlyTest:testTransferFunctions() (gas: 19077)
PBTSimpleTest:testGetTokenDataForChipSignature() (gas: 127965)
PBTSimpleTest:testGetTokenDataForChipSignatureBlockNumTooOld() (gas: 122516)
PBTSimpleTest:testGetTokenDataForChipSignatureInvalid() (gas: 131807)
PBTSimpleTest:testGetTokenDataForChipSignatureInvalidBlockNumber() (gas: 122355)
PBTSimpleTest:testIsChipSignatureForToken() (gas: 216733)
PBTSimpleTest:testMintTokenWithChip() (gas: 176613)
PBTSimpleTest:testSeedChipToTokenMapping() (gas: 115251)
PBTSimpleTest:testSeedChipToTokenMappingExistingToken() (gas: 212298)
PBTSimpleTest:testSeedChipToTokenMappingInvalidInput() (gas: 17175)
PBTSimpleTest:testSupportsInterface() (gas: 7059)
PBTSimpleTest:testTokenIdFor() (gas: 117085)
PBTSimpleTest:testTokenIdMappedFor() (gas: 66779)
PBTSimpleTest:testTransferTokenWithChip(bool) (runs: 256, μ: 211915, ~: 211834)
PBTSimpleTest:testUpdateChips() (gas: 171055)
PBTSimpleTest:testUpdateChipsInvalidInput() (gas: 16433)
PBTSimpleTest:testUpdateChipsUnsetChip() (gas: 23430)
testGetTokenDataForChipSignature() (gas: 127752)
testGetTokenDataForChipSignatureBlockNumTooOld() (gas: 122298)
testGetTokenDataForChipSignatureInvalid() (gas: 131594)
testGetTokenDataForChipSignatureInvalidBlockNumber() (gas: 122132)
testIsChipSignatureForToken() (gas: 216535)
testMintTokenWithChip() (gas: 176395)
testSeedChipToTokenMapping() (gas: 115078)
testSeedChipToTokenMappingExistingToken() (gas: 212100)
testSeedChipToTokenMappingInvalidInput() (gas: 17178)
testSupportsInterface() (gas: 7059)
testTokenIdFor() (gas: 116986)
testTokenIdMappedFor() (gas: 66680)
testTransferTokenWithChip(bool) (runs: 256, μ: 211706, ~: 211616)
testUpdateChips() (gas: 170776)
testUpdateChipsInvalidInput() (gas: 16433)
testUpdateChipsUnsetChip() (gas: 23405)
2 changes: 1 addition & 1 deletion src/IPBT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ interface IPBT {
/// @notice Calls transferTokenWithChip as defined above, with useSafeTransferFrom set to false.
function transferTokenWithChip(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig) external;

/// @notice Emitted when a token is minted
/// @notice Emitted when a token is minted.
event PBTMint(uint256 indexed tokenId, address indexed chipAddress);

/// @notice Emitted when a token is mapped to a different chip.
Expand Down
30 changes: 24 additions & 6 deletions src/PBTSimple.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,25 @@ contract PBTSimple is ERC721ReadOnly, IPBT {
uint256[] memory tokenIds,
bool throwIfTokenAlreadyMinted
) internal {
if (tokenIds.length != chipAddresses.length) {
uint256 tokenIdsLength = tokenIds.length;
if (tokenIdsLength != chipAddresses.length) {
revert ArrayLengthMismatch();
}
for (uint256 i = 0; i < tokenIds.length; ++i) {
uint256 i = 0;
do {
address chipAddress = chipAddresses[i];
uint256 tokenId = tokenIds[i];

if (throwIfTokenAlreadyMinted && _exists(tokenId)) {
revert SeedingChipDataForExistingToken();
}

_tokenDatas[chipAddress] = TokenData(tokenId, chipAddress, true);
}

unchecked {
++i;
}
} while(i < tokenIdsLength);
}

// Should only be called for tokenIds that have been minted
Expand All @@ -66,20 +74,29 @@ contract PBTSimple is ERC721ReadOnly, IPBT {
if (chipAddressesOld.length != chipAddressesNew.length) {
revert ArrayLengthMismatch();
}
for (uint256 i = 0; i < chipAddressesOld.length; ++i) {
uint256 i = 0;
do {
address oldChipAddress = chipAddressesOld[i];
TokenData memory oldTokenData = _tokenDatas[oldChipAddress];
if (!oldTokenData.set) {

if (!oldTokenData.set) {
revert UpdatingChipForUnsetChipMapping();
}

address newChipAddress = chipAddressesNew[i];
uint256 tokenId = oldTokenData.tokenId;
_tokenDatas[newChipAddress] = TokenData(tokenId, newChipAddress, true);

if (_exists(tokenId)) {
emit PBTChipRemapping(tokenId, oldChipAddress, newChipAddress);
}

delete _tokenDatas[oldChipAddress];
}

unchecked {
++i;
}
} while(i < chipAddressesOld.length);
}

function tokenIdFor(address chipAddress) external view override returns (uint256) {
Expand Down Expand Up @@ -156,6 +173,7 @@ contract PBTSimple is ERC721ReadOnly, IPBT {

function _getTokenDataForChipSignature(bytes calldata signatureFromChip, uint256 blockNumberUsedInSig)
internal
view
returns (TokenData memory)
{
// The blockNumberUsedInSig must be in a previous block because the blockhash of the current
Expand Down