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

Created ERC1155 ownable example/template #2807

Merged
merged 31 commits into from
May 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7053631
Created ERC1155 ownable example/template
Apr 17, 2022
f73abfe
Applied most of proposed changes
Apr 17, 2022
9d6cb9c
Applied more of the feedback.
Apr 17, 2022
c4636d1
ERC165 fix
Apr 17, 2022
f45f150
Applied @view/@pure, updated test to accomodate
Apr 18, 2022
435ced8
Moved tests into the correct folder
Apr 18, 2022
76a0db2
added interface ERC1155.py
Apr 18, 2022
d2a5305
refactor: clean up built-in interface
fubuloubu Apr 18, 2022
a93b047
Merge branch 'vyperlang:master' into master
Doc-Pixel Apr 20, 2022
4bd7c35
rewrote tests to use eth-tester
Apr 25, 2022
d776fad
cleaned up brownie tests in favor of eth-tester
Apr 27, 2022
e3e0a51
Update examples/tokens/ERC1155ownable.vy
Doc-Pixel Apr 27, 2022
6be8be4
applied proposed changes in contract and tests
Apr 27, 2022
5d50181
Merge branch 'master' of https://github.com/Doc-Pixel/vyper
Apr 27, 2022
9ff63bb
interface fix / callback bytes constant
Apr 27, 2022
241b0c3
Ownership test assertion to ZERO_ADDRESS
Apr 28, 2022
72f9bff
reworked uri part, simplified tests
Apr 29, 2022
24b21f8
Fixed all testscript issues
Apr 29, 2022
b3d2be8
refactor: apply suggestions from code review
fubuloubu Apr 29, 2022
d374eed
Update examples/tokens/ERC1155ownable.vy
Doc-Pixel May 1, 2022
cff1c64
Update examples/tokens/ERC1155ownable.vy
Doc-Pixel May 1, 2022
08e4cc7
applied codebase shrink tips, adjusted tests
May 2, 2022
f1ec2e6
cleaning up commented out code
May 2, 2022
0297391
refactor: flip orientation of `balanceOf`
fubuloubu May 2, 2022
375f624
test: update tests for switch of args to `balanceOf`
fubuloubu May 2, 2022
d87c321
pull, isort, black, push
May 2, 2022
a98de54
Resolved linter issues
Doc-Pixel May 3, 2022
8f2eb31
Linter fixes for ERC1155 test script
Doc-Pixel May 3, 2022
904ba2a
Fixed ownership check in safeTransferFrom
May 4, 2022
d904063
Fixed setURI permissions, updated to docstrings
May 4, 2022
4d97475
Minor test update
May 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 367 additions & 0 deletions examples/tokens/ERC1155ownable.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
# @version >=0.3.3
"""
@dev Implementation of ERC-1155 non-fungible token standard ownable, with approval, OPENSEA compatible (name, symbol)
@author Dr. Pixel (github: @Doc-Pixel)
"""
############### imports ###############
from vyper.interfaces import ERC165
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved

############### variables ###############
# maximum items in a batch call. Set to 128, to be determined what the practical limits are.
BATCH_SIZE: constant(uint256) = 128

# callback number of bytes
CALLBACK_NUMBYTES: constant(uint256) = 4096

# URI length set to 1024.
MAX_URI_LENGTH: constant(uint256) = 1024
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved

# the contract owner
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved
# not part of the core spec but a common feature for NFT projects
owner: public(address)

# pause status True / False
# not part of the core spec but a common feature for NFT projects
paused: public(bool)

# the contracts URI to find the metadata
_uri: String[MAX_URI_LENGTH]

# NFT marketplace compatibility
name: public(String[128])
symbol: public(String[16])

# Interface IDs
ERC165_INTERFACE_ID: constant(bytes4) = 0x01ffc9a7
ERC1155_INTERFACE_ID: constant(bytes4) = 0xd9b67a26
ERC1155_INTERFACE_ID_METADATA: constant(bytes4) = 0x0e89341c

# mappings

# Mapping from token ID to account balances
balanceOf: public(HashMap[address, HashMap[uint256, uint256]])

# Mapping from account to operator approvals
isApprovedForAll: public( HashMap[address, HashMap[address, bool]])

############### events ###############
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved
event Paused:
# Emits a pause event with the address that paused the contract
account: address

event unPaused:
# Emits an unpause event with the address that paused the contract
account: address

event OwnershipTransferred:
# Emits smart contract ownership transfer from current to new owner
previouwOwner: address
newOwner: address

event TransferSingle:
# Emits on transfer of a single token
operator: indexed(address)
fromAddress: indexed(address)
to: indexed(address)
id: uint256
value: uint256

event TransferBatch:
# Emits on batch transfer of tokens. the ids array correspond with the values array by their position
operator: indexed(address) # indexed
fromAddress: indexed(address)
to: indexed(address)
ids: DynArray[uint256, BATCH_SIZE]
values: DynArray[uint256, BATCH_SIZE]

event ApprovalForAll:
# This emits when an operator is enabled or disabled for an owner. The operator manages all tokens for an owner
account: indexed(address)
operator: indexed(address)
approved: bool

event URI:
# This emits when the URI gets changed
value: String[MAX_URI_LENGTH]
id: uint256


############### interfaces ###############
implements: ERC165

interface IERC1155Receiver:
def onERC1155Received(
operator: address,
sender: address,
id: uint256,
amount: uint256,
data: Bytes[CALLBACK_NUMBYTES],
) -> bytes32: payable
def onERC1155BatchReceived(
operator: address,
sender: address,
ids: DynArray[uint256, BATCH_SIZE],
amounts: DynArray[uint256, BATCH_SIZE],
data: Bytes[CALLBACK_NUMBYTES],
) -> bytes4: payable

interface IERC1155MetadataURI:
def uri(id: uint256) -> String[MAX_URI_LENGTH]: view

############### functions ###############

@external
def __init__(name: String[128], symbol: String[16], uri: String[1024]):
"""
@dev contract initialization on deployment
@dev will set name and symbol, interfaces, owner and URI
@dev self.paused will default to false
@param name the smart contract name
@param symbol the smart contract symbol
@param uri the new uri for the contract
"""
self.name = name
self.symbol = symbol
self.owner = msg.sender
self._uri = uri

## contract status ##
@external
def pause():
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved
"""
@dev Pause the contract, checks if the caller is the owner and if the contract is paused already
@dev emits a pause event
@dev not part of the core spec but a common feature for NFT projects
"""
assert self.owner == msg.sender, "Ownable: caller is not the owner"
assert not self.paused, "the contract is already paused"
self.paused = True
log Paused(msg.sender)

@external
def unpause():
"""
@dev Unpause the contract, checks if the caller is the owner and if the contract is paused already
@dev emits an unpause event
@dev not part of the core spec but a common feature for NFT projects
"""
assert self.owner == msg.sender, "Ownable: caller is not the owner"
assert self.paused, "the contract is not paused"
self.paused = False
log unPaused(msg.sender)

## ownership ##
@external
def transferOwnership(newOwner: address):
"""
@dev Transfer the ownership. Checks for contract pause status, current owner and prevent transferring to
@dev zero address
@dev emits an OwnershipTransferred event with the old and new owner addresses
@param newOwner The address of the new owner.
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Ownable: caller is not the owner"
assert newOwner != self.owner, "This account already owns the contract"
assert newOwner != ZERO_ADDRESS, "Transfer to ZERO_ADDRESS not allowed. Use renounceOwnership() instead."
oldOwner: address = self.owner
self.owner = newOwner
log OwnershipTransferred(oldOwner, newOwner)

@external
def renounceOwnership():
"""
@dev Transfer the ownership to ZERO_ADDRESS, this will lock the contract
@dev emits an OwnershipTransferred event with the old and new ZERO_ADDRESS owner addresses
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Ownable: caller is not the owner"
oldOwner: address = self.owner
self.owner = ZERO_ADDRESS
log OwnershipTransferred(oldOwner, ZERO_ADDRESS)

@external
@view
def balanceOfBatch(accounts: DynArray[address, BATCH_SIZE], ids: DynArray[uint256, BATCH_SIZE]) -> DynArray[uint256,BATCH_SIZE]: # uint256[BATCH_SIZE]:
"""
@dev check the balance for an array of specific IDs and addresses
@dev will return an array of balances
@dev Can also be used to check ownership of an ID
@param accounts a dynamic array of the addresses to check the balance for
@param ids a dynamic array of the token IDs to check the balance
"""
assert len(accounts) == len(ids), "ERC1155: accounts and ids length mismatch"
batchBalances: DynArray[uint256, BATCH_SIZE] = []
j: uint256 = 0
for i in ids:
batchBalances.append(self.balanceOf[accounts[j]][i])
j += 1
return batchBalances

## mint ##
@external
def mint(receiver: address, id: uint256, amount:uint256, data:bytes32):
"""
@dev mint one new token with a certain ID
@dev this can be a new token or "topping up" the balance of a non-fungible token ID
@param receiver the account that will receive the minted token
@param id the ID of the token
@param amount of tokens for this ID
@param data the data associated with this mint. Usually stays empty
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Only the contract owner can mint"
assert receiver != ZERO_ADDRESS, "Can not mint to ZERO ADDRESS"
operator: address = msg.sender
self.balanceOf[receiver][id] += amount
log TransferSingle(operator, ZERO_ADDRESS, receiver, id, amount)


@external
def mintBatch(receiver: address, ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE], data: bytes32):
"""
@dev mint a batch of new tokens with the passed IDs
@dev this can be new tokens or "topping up" the balance of existing non-fungible token IDs in the contract
@param receiver the account that will receive the minted token
@param ids array of ids for the tokens
@param amounts amounts of tokens for each ID in the ids array
@param data the data associated with this mint. Usually stays empty
"""
assert not self.paused, "The contract has been paused"
assert self.owner == msg.sender, "Only the contract owner can mint"
assert receiver != ZERO_ADDRESS, "Can not mint to ZERO ADDRESS"
assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
operator: address = msg.sender

for i in range(BATCH_SIZE):
if i >= len(ids):
break
self.balanceOf[receiver][ids[i]] += amounts[i]

log TransferBatch(operator, ZERO_ADDRESS, receiver, ids, amounts)

## burn ##
@external
def burn(id: uint256, amount: uint256):
"""
@dev burn one or more token with a certain ID
@dev the amount of tokens will be deducted from the holder's balance
@param receiver the account that will receive the minted token
@param id the ID of the token to burn
@param amount of tokens to burnfor this ID
"""
assert not self.paused, "The contract has been paused"
assert self.balanceOf[msg.sender][id] > 0 , "caller does not own this ID"
self.balanceOf[msg.sender][id] -= amount
log TransferSingle(msg.sender, msg.sender, ZERO_ADDRESS, id, amount)

@external
def burnBatch(ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE]):
"""
@dev burn a batch of tokens with the passed IDs
@dev this can be burning non fungible tokens or reducing the balance of existing non-fungible token IDs in the contract
@dev inside the loop ownership will be checked for each token. We can not burn tokens we do not own
@param ids array of ids for the tokens to burn
@param amounts array of amounts of tokens for each ID in the ids array
"""
assert not self.paused, "The contract has been paused"
assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
operator: address = msg.sender

for i in range(BATCH_SIZE):
if i >= len(ids):
break
self.balanceOf[msg.sender][ids[i]] -= amounts[i]

log TransferBatch(msg.sender, msg.sender, ZERO_ADDRESS, ids, amounts)

## approval ##
@external
def setApprovalForAll(owner: address, operator: address, approved: bool):
"""
@dev set an operator for a certain NFT owner address
@param account the NFT owner address
@param operator the operator address
"""
assert owner == msg.sender, "You can only set operators for your own account"
assert not self.paused, "The contract has been paused"
assert owner != operator, "ERC1155: setting approval status for self"
self.isApprovedForAll[owner][operator] = approved
log ApprovalForAll(owner, operator, approved)

@external
def safeTransferFrom(sender: address, receiver: address, id: uint256, amount: uint256, bytes: bytes32):
"""
@dev transfer token from one address to another.
@param sender the sending account (current owner)
@param receiver the receiving account
@param id the token id that will be sent
@param amount the amount of tokens for the specified id
"""
assert not self.paused, "The contract has been paused"
assert receiver != ZERO_ADDRESS, "ERC1155: transfer to the zero address"
assert sender != receiver
assert sender == msg.sender or self.isApprovedForAll[sender][msg.sender], "Caller is neither owner nor approved operator for this ID"
assert self.balanceOf[sender][id] > 0 , "caller does not own this ID or ZERO balance"
operator: address = msg.sender
self.balanceOf[sender][id] -= amount
self.balanceOf[receiver][id] += amount
log TransferSingle(operator, sender, receiver, id, amount)

@external
def safeBatchTransferFrom(sender: address, receiver: address, ids: DynArray[uint256, BATCH_SIZE], amounts: DynArray[uint256, BATCH_SIZE], _bytes: bytes32):
"""
@dev transfer tokens from one address to another.
@param sender the sending account
@param receiver the receiving account
@param ids a dynamic array of the token ids that will be sent
@param amounts a dynamic array of the amounts for the specified list of ids.
"""
assert not self.paused, "The contract has been paused"
assert receiver != ZERO_ADDRESS, "ERC1155: transfer to the zero address"
assert sender != receiver
assert sender == msg.sender or self.isApprovedForAll[sender][msg.sender], "Caller is neither owner nor approved operator for this ID"
assert len(ids) == len(amounts), "ERC1155: ids and amounts length mismatch"
operator: address = msg.sender
for i in range(BATCH_SIZE):
if i >= len(ids):
break
id: uint256 = ids[i]
amount: uint256 = amounts[i]
self.balanceOf[sender][id] -= amount
self.balanceOf[receiver][id] += amount

log TransferBatch(operator, sender, receiver, ids, amounts)

# URI #
@external
def setURI(uri: String[MAX_URI_LENGTH]):
Doc-Pixel marked this conversation as resolved.
Show resolved Hide resolved
"""
@dev set the URI for the contract
@param uri the new uri for the contract
"""
assert not self.paused, "The contract has been paused"
assert self._uri != uri, "new and current URI are identical"
assert msg.sender == self.owner, "Only the contract owner can update the URI"
self._uri = uri
log URI(uri, 0)

@external
def uri(id: uint256) -> String[MAX_URI_LENGTH]:
"""
@dev retrieve the uri, this function can optionally be extended to return dynamic uris. out of scope.
@param id NFT ID to retrieve the uri for.
"""
return self._uri

@pure
@external
def supportsInterface(interfaceId: bytes4) -> bool:
"""
@dev Returns True if the interface is supported
@param interfaceID bytes4 interface identifier
"""
return interfaceId in [
ERC165_INTERFACE_ID,
ERC1155_INTERFACE_ID,
ERC1155_INTERFACE_ID_METADATA,
]
Loading