From 900be13d6240df3ac75a5f72c468f446f916608e Mon Sep 17 00:00:00 2001 From: BenRey Date: Mon, 2 Dec 2024 10:59:54 +0100 Subject: [PATCH 1/5] Add enumerable nft --- .../assembly/contracts/NFT/NFT-internals.ts | 10 +- .../contracts/NFT/NFTEnumerable-example.ts | 253 ++++++++++++++ .../contracts/NFT/NFTEnumerable-internals.ts | 164 +++++++++ .../__tests__/NFTEnumerable-example.spec.ts | 310 +++++++++++++++++ .../__tests__/NFTEnumerable-internals.spec.ts | 316 ++++++++++++++++++ .../contracts/NFT/__tests__/helpers.ts | 22 ++ 6 files changed, 1071 insertions(+), 4 deletions(-) create mode 100644 smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts create mode 100644 smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts create mode 100644 smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts create mode 100644 smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts create mode 100644 smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts diff --git a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts index 8d068df..e863a05 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts @@ -54,7 +54,7 @@ export function _constructor(name: string, symbol: string): void { * @param address - address to get the balance for * @returns the key of the balance in the storage for the given address */ -function balanceKey(address: string): StaticArray { +export function balanceKey(address: string): StaticArray { return BALANCE_KEY_PREFIX.concat(stringToBytes(address)); } @@ -62,7 +62,7 @@ function balanceKey(address: string): StaticArray { * @param tokenId - the tokenID of the owner * @returns the key of the owner in the storage for the given tokenId */ -function ownerKey(tokenId: u256): StaticArray { +export function ownerKey(tokenId: u256): StaticArray { return OWNER_KEY_PREFIX.concat(u256ToBytes(tokenId)); } @@ -227,8 +227,7 @@ export function _isAuthorized(operator: string, tokenId: u256): bool { * For example if you were to wrap this helper in a `transfer` function, * you should check that the caller is the owner of the token, and then call the _update function. */ - -export function _update(to: string, tokenId: u256, auth: string): void { +export function _update(to: string, tokenId: u256, auth: string): string { const from = _ownerOf(tokenId); assert(to != from, 'The from and to addresses are the same'); if (auth != '') { @@ -258,7 +257,10 @@ export function _update(to: string, tokenId: u256, auth: string): void { // burn the token Storage.del(ownerKey(tokenId)); } + + return from; } + /** * Transfers the ownership of an NFT from one address to another address. * @param from - The address of the current owner diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts new file mode 100644 index 0000000..4e57664 --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts @@ -0,0 +1,253 @@ +/** + * This is an example of an NFT contract that uses the `NFTEnumerable-internals` + * helper functions to implement enumeration functionality similar to the ERC-721 Enumerable extension. + * + * **Note:** We have diverged from the ERC-721 Enumerable standard in this implementation. + * On the Massa blockchain, indices are not necessary for token enumeration because we can directly access + * the storage and structure data in a way that allows developers to easily retrieve the needed values. + * + * Instead of maintaining explicit arrays or mappings of token IDs and owner tokens by index, + * we utilize key prefix querying to retrieve all token IDs and tokens owned by specific addresses. + * This approach leverages Massa's storage capabilities for efficient data access and simplifies the contract logic. + * + * **Benefits of This Approach:** + * - **Reduced Storage Costs:** Eliminates the need for additional storage structures to maintain indices. + * - **Simplified Logic:** Streamlines the process of token enumeration, making the contract easier to maintain. + * + * **Underlying Storage Structure:** + * + * We store important information in the following formats: + * + * - **Total Supply:** + * + * [TOTAL_SUPPLY_KEY] = totalSupply + * - `TOTAL_SUPPLY_KEY`: A constant key for the total supply of tokens. + * - `totalSupply`: A `u256` value representing the total number of tokens in existence. + * + * - **Owned Tokens:** + * + * [OWNED_TOKENS_KEY][owner][tokenId] = tokenId + * - `OWNED_TOKENS_KEY`: A constant prefix for all owned tokens. + * - `owner`: The owner's address. + * - `tokenId`: The token ID. + * - The value `tokenId` is stored to facilitate easy retrieval. + * + * **Retrieving Data Using Key Prefixes:** + * + * We utilize the `getKeys` function from the `massa-as-sdk`, which allows us to retrieve all keys that start with a + * specific prefix. This enables us to: + * - Retrieve all tokens owned by a specific address by querying keys with the prefix `[OWNED_TOKENS_KEY][owner]`. + * - Retrieve all existing token IDs by querying keys with the appropriate prefix if needed. + * + * **Key Points:** + * - The `getKeys` function from the `massa-as-sdk` allows us to filter storage keys by a given prefix, + * enabling efficient data retrieval. + * + * **This file does two things:** + * 1. It wraps the `NFTEnumerable-internals` functions, manages the deserialization/serialization of the arguments + * and return values, and exposes them to the outside world. + * 2. It implements some custom features that are not part of the ERC-721 standard, + * such as `mint`, `burn`, or ownership management. + * + * **Important:** The `NFTEnumerable-internals` functions are not supposed to be re-exported by this file. + */ + +import { + Args, + boolToByte, + stringToBytes, + u256ToBytes, +} from '@massalabs/as-types'; +import { + _approve, + _balanceOf, + _constructor, + _getApproved, + _isApprovedForAll, + _name, + _ownerOf, + _setApprovalForAll, + _symbol, + _update, + _transferFrom, + _totalSupply, +} from './NFTEnumerable-internals'; +import { setOwner, onlyOwner } from '../utils/ownership'; +import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; + +/** + * @param binaryArgs - serialized strings representing the name and the symbol of the NFT + * + * @remarks This is the constructor of the contract. It can only be called once, when the contract is being deployed. + * It expects two serialized arguments: the name and the symbol of the NFT. + * Once the constructor has handled the deserialization of the arguments, + * it calls the _constructor function from the NFT-enumerable-internals. + * + * Finally, it sets the owner of the contract to the caller of the constructor. + */ +export function constructor(binaryArgs: StaticArray): void { + assert(isDeployingContract()); + const args = new Args(binaryArgs); + const name = args.nextString().expect('name argument is missing or invalid'); + const symbol = args + .nextString() + .expect('symbol argument is missing or invalid'); + _constructor(name, symbol); + setOwner(new Args().add(Context.caller().toString()).serialize()); +} + +export function name(): string { + return _name(); +} + +export function symbol(): string { + return _symbol(); +} + +/** + * + * @param binaryArgs - serialized string representing the address whose balance we want to check + * @returns a serialized u256 representing the balance of the address + */ +export function balanceOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const address = args + .nextString() + .expect('address argument is missing or invalid'); + return u256ToBytes(_balanceOf(address)); +} + +/** + * + * @param binaryArgs - serialized u256 representing the tokenId whose owner we want to check + * @returns a serialized string representing the address of owner of the tokenId + */ +export function ownerOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + return stringToBytes(_ownerOf(tokenId)); +} + +/** + * + * @param binaryArgs - serialized u256 representing the tokenId whose approved address we want to check + * @returns a serialized string representing the address of the approved address of the tokenId + */ +export function getApproved(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + return stringToBytes(_getApproved(tokenId)); +} + +/** + * + * @param binaryArgs - serialized strings representing the address of an owner and an operator + * @returns a serialized u8 representing a boolean value indicating if + * the operator is approved for all the owner's tokens + */ +export function isApprovedForAll(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const owner = args + .nextString() + .expect('owner argument is missing or invalid'); + const operator = args + .nextString() + .expect('operator argument is missing or invalid'); + return boolToByte(_isApprovedForAll(owner, operator)); +} + +/** + * + * @param binaryArgs - serialized strings representing the address of the recipient and the tokenId to approve + * @remarks This function is only callable by the owner of the tokenId or an approved operator. + * Indeed, this will be checked by the _approve function of the NFT-internals. + * + */ +export function approve(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _approve(to, tokenId); +} + +/** + * + * @param binaryArgs - serialized arguments representing the address of the operator and a boolean value indicating + * if the operator should be approved for all the caller's tokens + * + */ +export function setApprovalForAll(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const approved = args + .nextBool() + .expect('approved argument is missing or invalid'); + _setApprovalForAll(to, approved); +} + +/** + * + * @param binaryArgs - serialized arguments representing the address of the sender, + * the address of the recipient, and the tokenId to transfer. + * + * @remarks This function is only callable by the owner of the tokenId or an approved operator. + */ +export function transferFrom(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const from = args.nextString().expect('from argument is missing or invalid'); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _transferFrom(from, to, tokenId); +} + +/** + * + * @param binaryArgs - serialized arguments representing the address of the recipient and the tokenId to mint + * + * @remarks This function is only callable by the owner of the contract. + */ +export function mint(binaryArgs: StaticArray): void { + onlyOwner(); + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _update(to, tokenId, ''); +} + +/** + * + * @param binaryArgs - serialized u256 representing the tokenId to burn + * + * @remarks This function is not part of the ERC721 standard. + * It serves as an example of how to use the NFT-enumerable-internals functions to implement custom features. + */ +export function burn(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _update('', tokenId, ''); +} + +/** + * Returns the total number of tokens. + * @returns a serialized u256 representing the total supply + */ +export function totalSupply(_: StaticArray): StaticArray { + return u256ToBytes(_totalSupply()); +} + +/** + * Expose the ownerAddress function to allow checking the owner of the contract. + */ +export { ownerAddress } from '../utils/ownership'; diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts new file mode 100644 index 0000000..8af1de1 --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts @@ -0,0 +1,164 @@ +/** + * This file provides internal functions to support token enumeration functionality for the NFT contract on Massa. + * It utilizes key prefix querying to retrieve all token IDs and tokens owned by specific addresses + * in the datastore without maintaining explicit indices. + */ + +import { Context, Storage } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; +import { bytesToU256, stringToBytes, u256ToBytes } from '@massalabs/as-types'; +import { + _isAuthorized, + _ownerOf, + ownerKey, + _update as _updateBase, + _constructor as _constructorBase, +} from './NFT-internals'; +export const TOTAL_SUPPLY_KEY: StaticArray = stringToBytes('totalSupply'); +export const OWNED_TOKENS_KEY: StaticArray = stringToBytes('ownedTokens'); + +/** + * Constructs a new NFT contract. + * @param binaryArgs - the binary arguments name and symbol + * + * @remarks This function shouldn't be directly exported by the implementation contract. + * It is meant to be called by the constructor of the implementation contract. + * Please check the NFTEnumerable-example.ts file for an example of how to use this function. + */ +export function _constructor(name: string, symbol: string): void { + _constructorBase(name, symbol); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(u256.Zero)); +} + +/* -------------------------------------------------------------------------- */ +/* TOTAL SUPPLY */ +/* -------------------------------------------------------------------------- */ + +/** + * Returns the total number of tokens in existence. + */ +export function _totalSupply(): u256 { + return bytesToU256(Storage.get(TOTAL_SUPPLY_KEY)); +} + +/** + * Increases the total supply by the given delta. + * @param delta - The amount to increase the total supply by. + * + * @throws Will throw an error if the addition of delta to currentSupply exceeds u256.Max. + */ +export function _increaseTotalSupply(delta: u256): void { + const currentSupply = _totalSupply(); + const maxAllowedDelta = u256.sub(u256.Max, currentSupply); + assert(u256.le(delta, maxAllowedDelta), 'Total supply overflow'); + const newSupply = u256.add(currentSupply, delta); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); +} + +/** + * Decreases the total supply by the given delta. + * @param delta - The amount to decrease the total supply by. + * + * @throws Will throw an error if `delta` exceeds the current total supply, causing an underflow. + */ +export function _decreaseTotalSupply(delta: u256): void { + const currentSupply = _totalSupply(); + assert(u256.le(delta, currentSupply), 'Total supply underflow'); + const newSupply = u256.sub(currentSupply, delta); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); +} + +/* -------------------------------------------------------------------------- */ +/* OWNED TOKENS */ +/* -------------------------------------------------------------------------- */ + +/** + * Returns the key prefix for the owned tokens of an owner. + * @param owner - The owner's address. + */ +export function _getOwnedTokensKeyPrefix(owner: string): StaticArray { + return OWNED_TOKENS_KEY.concat(stringToBytes(owner)); +} + +/** + * Adds a token to the owner's list of tokens. + * @param owner - The owner's address. + * @param tokenId - The token ID to add. + */ +function _addTokenToOwnerEnumeration(owner: string, tokenId: u256): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); + Storage.set(key, u256ToBytes(tokenId)); +} + +/** + * Removes a token from the owner's list of tokens. + * @param owner - The owner's address. + * @param tokenId - The token ID to remove. + */ +function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u256): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); + Storage.del(key); +} + +/* -------------------------------------------------------------------------- */ +/* UPDATE */ +/* -------------------------------------------------------------------------- */ + +/** + * Updates the token ownership and enumerations. + * @param to - The address to transfer the token to. + * @param tokenId - The token ID. + * @param auth - The address authorized to perform the update. + */ +export function _update(to: string, tokenId: u256, auth: string): void { + const previousOwner = _updateBase(to, tokenId, auth); + + // Mint + if (previousOwner == '') { + _addTokenToOwnerEnumeration(to, tokenId); + _increaseTotalSupply(u256.One); + } else { + // Transfer + if (to != '' && to != previousOwner) { + _removeTokenFromOwnerEnumeration(previousOwner, tokenId); + _addTokenToOwnerEnumeration(to, tokenId); + } + // Burn + else if (to == '') { + _removeTokenFromOwnerEnumeration(previousOwner, tokenId); + _decreaseTotalSupply(u256.One); + } + } +} + +/* -------------------------------------------------------------------------- */ +/* EXPORT NECESSARY FUNCTIONS */ +/* -------------------------------------------------------------------------- */ + +/** + * Transfers a token from one address to another. + * @param from - The current owner's address. + * @param to - The new owner's address. + * @param tokenId - The token ID to transfer. + */ +export function _transferFrom(from: string, to: string, tokenId: u256): void { + assert( + _isAuthorized(Context.caller().toString(), tokenId), + 'Unauthorized caller', + ); + assert(from == _ownerOf(tokenId), 'Unauthorized from'); + assert(to != '', 'Unauthorized to'); + assert(Storage.has(ownerKey(tokenId)), 'Nonexistent token'); + _update(to, tokenId, from); +} + +export { + _approve, + _balanceOf, + _getApproved, + _isApprovedForAll, + _name, + _ownerOf, + _setApprovalForAll, + _symbol, +} from './NFT-internals'; diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts new file mode 100644 index 0000000..866f72e --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts @@ -0,0 +1,310 @@ +import { + changeCallStack, + resetStorage, + setDeployContext, +} from '@massalabs/massa-as-sdk'; + +import { + Args, + byteToBool, + bytesToString, + bytesToU256, +} from '@massalabs/as-types'; +import { u256 } from 'as-bignum/assembly'; +import { + approve, + balanceOf, + burn, + constructor, + getApproved, + isApprovedForAll, + mint, + name, + ownerAddress, + ownerOf, + setApprovalForAll, + symbol, + totalSupply, + transferFrom, +} from '../NFTEnumerable-example'; + +import { getOwnedTokens } from './helpers'; + +const NFTName = 'MASSA_NFT'; +const NFTSymbol = 'NFT'; +const contractOwner = 'A12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; +const tokenAddress = 'AS12BqZEQ6sByhRLyEuf0YbQmcF2PsDdkNNG1akBJu9XcjZA1eT'; +const from = 'AU12CzoKEASaeBHnxGLnHDG2u73dLzWWfgvW6bc4L1UfMA5Uc5Fg7'; +const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; +const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; +const zeroAddress = ''; +const tokenIds = [ + u256.One, + u256.fromU32(2), + u256.fromU32(3), + u256.fromU32(4), + u256.fromU32(5), +]; + +function switchUser(user: string): void { + changeCallStack(user + ' , ' + tokenAddress); +} + +beforeEach(() => { + resetStorage(); + switchUser(contractOwner); + setDeployContext(contractOwner); + constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); +}); + +describe('NFT Enumerable Contract', () => { + describe('Initialization', () => { + test('should return correct name and symbol', () => { + expect(name()).toBe(NFTName); + expect(symbol()).toBe(NFTSymbol); + }); + test('should return correct contract owner', () => { + expect(bytesToString(ownerAddress([]))).toBe(contractOwner); + }); + }); + + describe('Minting', () => { + test('should mint a token to an address', () => { + mint(new Args().add(to).add(tokenIds[0]).serialize()); + expect( + bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), + ).toBe(to); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + }); + + test('should mint multiple tokens to different addresses', () => { + mint(new Args().add(to).add(tokenIds[0]).serialize()); + mint(new Args().add(from).add(tokenIds[1]).serialize()); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); + expect( + bytesToU256(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(u256.One); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); + }); + + test('should not mint to zero address', () => { + expect(() => { + mint(new Args().add(zeroAddress).add(tokenIds[0]).serialize()); + }).toThrow('Unauthorized to'); + }); + + test('should not mint an already existing tokenId', () => { + mint(new Args().add(to).add(tokenIds[0]).serialize()); + expect(() => { + mint(new Args().add(to).add(tokenIds[0]).serialize()); + }).toThrow('Token already minted'); + }); + + test('should not allow non-owner to mint tokens', () => { + switchUser(from); + expect(() => { + mint(new Args().add(to).add(tokenIds[0]).serialize()); + }).toThrow('Only owner can call this function'); + }); + }); + + describe('Approval', () => { + test('should approve a token for an address', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + approve(new Args().add(approved).add(tokenIds[0]).serialize()); + expect( + bytesToString(getApproved(new Args().add(tokenIds[0]).serialize())), + ).toBe(approved); + }); + + test('should set approval for all', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + setApprovalForAll(new Args().add(approved).add(true).serialize()); + expect( + byteToBool( + isApprovedForAll(new Args().add(from).add(approved).serialize()), + ), + ).toBe(true); + }); + + test('should revoke approval for all', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + setApprovalForAll(new Args().add(approved).add(true).serialize()); + setApprovalForAll(new Args().add(approved).add(false).serialize()); + expect( + byteToBool( + isApprovedForAll(new Args().add(from).add(approved).serialize()), + ), + ).toBe(false); + }); + + test('should not approve token not owned', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(approved); + expect(() => { + approve(new Args().add(approved).add(tokenIds[0]).serialize()); + }).toThrow('Unauthorized caller'); + }); + }); + + describe('Transfers', () => { + test('should transfer token from owner', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); + expect( + bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), + ).toBe(to); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); + expect( + bytesToU256(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(u256.Zero); + }); + + test('should transfer approved token', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + approve(new Args().add(approved).add(tokenIds[0]).serialize()); + switchUser(approved); + transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); + expect( + bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), + ).toBe(to); + }); + + test('should transfer token using approval for all', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + setApprovalForAll(new Args().add(approved).add(true).serialize()); + switchUser(approved); + transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); + expect( + bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), + ).toBe(to); + }); + + test('should not transfer token without approval', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(approved); + expect(() => { + transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); + }).toThrow('Unauthorized caller'); + }); + }); + + describe('Burning', () => { + test('should burn a token', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + burn(new Args().add(tokenIds[0]).serialize()); + + expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( + [], + ); + + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + }); + + test('should burn a token with approval', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + approve(new Args().add(approved).add(tokenIds[0]).serialize()); + switchUser(approved); + burn(new Args().add(tokenIds[0]).serialize()); + expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( + [], + ); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + }); + + test('should burn a token using approval for all', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(from); + setApprovalForAll(new Args().add(approved).add(true).serialize()); + switchUser(approved); + burn(new Args().add(tokenIds[0]).serialize()); + expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( + [], + ); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + }); + + test('should not burn token without approval', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + switchUser(to); + expect(() => { + burn(new Args().add(tokenIds[0]).serialize()); + }).toThrow('Unauthorized caller'); + }); + }); + + describe('Enumeration', () => { + test('should return correct total supply', () => { + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + mint(new Args().add(from).add(tokenIds[0]).serialize()); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + mint(new Args().add(to).add(tokenIds[1]).serialize()); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); + }); + + test('should return correct tokens owned by an address', () => { + // Assuming we have an exported function to get owned tokens + mint(new Args().add(from).add(tokenIds[0]).serialize()); + mint(new Args().add(from).add(tokenIds[1]).serialize()); + mint(new Args().add(to).add(tokenIds[2]).serialize()); + + // Get tokens owned by 'from' address + const fromTokens = getOwnedTokens(from); + expect(fromTokens.length).toBe(2); + expect(fromTokens).toContainEqual(tokenIds[0]); + expect(fromTokens).toContainEqual(tokenIds[1]); + + // Get tokens owned by 'to' address + const toTokens = getOwnedTokens(to); + expect(toTokens.length).toBe(1); + expect(toTokens).toContainEqual(tokenIds[2]); + }); + + test('should update owned tokens after transfer', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + mint(new Args().add(from).add(tokenIds[1]).serialize()); + switchUser(from); + transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); + + // Get tokens owned by 'from' address + const fromTokens = getOwnedTokens(from); + expect(fromTokens.length).toBe(1); + expect(fromTokens).toContainEqual(tokenIds[1]); + + // Get tokens owned by 'to' address + const toTokens = getOwnedTokens(to); + expect(toTokens.length).toBe(1); + expect(toTokens).toContainEqual(tokenIds[0]); + }); + + test('should update owned tokens after burn', () => { + mint(new Args().add(from).add(tokenIds[0]).serialize()); + mint(new Args().add(from).add(tokenIds[1]).serialize()); + switchUser(from); + burn(new Args().add(tokenIds[0]).serialize()); + + // Get tokens owned by 'from' address + const fromTokens = getOwnedTokens(from); + expect(fromTokens.length).toBe(1); + expect(fromTokens).toContainEqual(tokenIds[1]); + + // Total supply should be updated + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + }); + }); +}); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts new file mode 100644 index 0000000..c89d903 --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts @@ -0,0 +1,316 @@ +import { resetStorage, setDeployContext } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; +import { + _update, + _balanceOf, + _totalSupply, + _constructor, + _ownerOf, + _transferFrom, + _decreaseTotalSupply, + _increaseTotalSupply, +} from '../NFTEnumerable-internals'; +import { getOwnedTokens } from './helpers'; + +const caller = 'A12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; +const owner1 = caller; +const owner2 = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; +const zeroAddress = ''; +const tokenIds = [ + u256.fromU32(1), + u256.fromU32(2), + u256.fromU32(3), + u256.fromU32(4), + u256.fromU32(5), +]; + +const NFTName = 'MASSA_NFT'; +const NFTSymbol = 'NFT'; + +function mint(to: string, tokenId: u256): void { + _update(to, tokenId, zeroAddress); +} + +function transfer(from: string, to: string, tokenId: u256): void { + _update(to, tokenId, from); +} + +function burn(owner: string, tokenId: u256): void { + _update(zeroAddress, tokenId, owner); +} + +describe('NFT Enumerable Internals', () => { + beforeEach(() => { + resetStorage(); + setDeployContext(caller); + _constructor(NFTName, NFTSymbol); + }); + + describe('Initialization', () => { + it('should have zero total supply initially', () => { + expect(_totalSupply()).toStrictEqual(u256.Zero); + }); + }); + + describe('Total Supply Management', () => { + it('should update total supply when token is minted', () => { + mint(owner1, tokenIds[0]); + expect(_totalSupply()).toStrictEqual(u256.One); + }); + + it('should update total supply when token is burned', () => { + mint(owner1, tokenIds[0]); + expect(_totalSupply()).toStrictEqual(u256.One); + burn(owner1, tokenIds[0]); + expect(_totalSupply()).toStrictEqual(u256.Zero); + }); + + it('should not allow total supply to exceed u256.Max', () => { + // Set total supply to u256.Max - 1 + const nearMaxSupply = u256.sub(u256.Max, u256.One); + _increaseTotalSupply(u256.sub(u256.Max, u256.One)); + expect(_totalSupply()).toStrictEqual(nearMaxSupply); + + // Mint one more token should succeed (totalSupply = u256.Max) + mint(owner1, tokenIds[0]); + expect(_totalSupply()).toStrictEqual(u256.Max); + + // Minting another token should fail due to overflow + expect(() => { + _increaseTotalSupply(u256.One); + }).toThrow('Total supply overflow'); // Ensure your contract throws this exact error + }); + + it('should not allow total supply to underflow', () => { + // Ensure total supply is zero + expect(_totalSupply()).toStrictEqual(u256.Zero); + + // Attempt to decrease supply by 1 should fail + expect(() => { + _decreaseTotalSupply(u256.One); + }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error + + // Set total supply to 1 + _increaseTotalSupply(u256.One); + expect(_totalSupply()).toStrictEqual(u256.One); + + // Decrease supply by 1 should succeed + _decreaseTotalSupply(u256.One); + expect(_totalSupply()).toStrictEqual(u256.Zero); + + // Attempt to decrease supply by another 1 should fail + expect(() => { + _decreaseTotalSupply(u256.One); + }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error + }); + }); + + describe('Owner Token Enumeration', () => { + it('should return correct balances and owned tokens after minting', () => { + mint(owner1, tokenIds[0]); + mint(owner1, tokenIds[1]); + mint(owner2, tokenIds[2]); + + expect(_balanceOf(owner1)).toStrictEqual(u256.fromU32(2)); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); + + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(2); + expect(owner1Tokens).toContainEqual(tokenIds[0]); + expect(owner1Tokens).toContainEqual(tokenIds[1]); + + const owner2Tokens = getOwnedTokens(owner2); + expect(owner2Tokens.length).toBe(1); + expect(owner2Tokens).toContainEqual(tokenIds[2]); + }); + + it('should update balances and tokens after transfer', () => { + mint(owner1, tokenIds[0]); + mint(owner1, tokenIds[1]); + + transfer(owner1, owner2, tokenIds[0]); + + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); + + // Verify ownership + expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); + expect(_ownerOf(tokenIds[1])).toStrictEqual(owner1); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(1); + expect(owner1Tokens).toContainEqual(tokenIds[1]); + + const owner2Tokens = getOwnedTokens(owner2); + expect(owner2Tokens.length).toBe(1); + expect(owner2Tokens).toContainEqual(tokenIds[0]); + }); + }); + + describe('Token Transfers and Ownership', () => { + it('should update ownership after transfer', () => { + mint(owner1, tokenIds[0]); + transfer(owner1, owner2, tokenIds[0]); + + expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(0); + + const owner2Tokens = getOwnedTokens(owner2); + expect(owner2Tokens.length).toBe(1); + expect(owner2Tokens).toContainEqual(tokenIds[0]); + }); + + it('should handle transfer to owner with balance', () => { + mint(owner1, tokenIds[0]); + mint(owner2, tokenIds[1]); + + transfer(owner1, owner2, tokenIds[0]); + + expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(0); + + const owner2Tokens = getOwnedTokens(owner2); + expect(owner2Tokens.length).toBe(2); + expect(owner2Tokens).toContainEqual(tokenIds[0]); + expect(owner2Tokens).toContainEqual(tokenIds[1]); + }); + }); + + describe('Token Burning and Ownership', () => { + it('should update balances and tokens after burning', () => { + mint(owner1, tokenIds[0]); + mint(owner1, tokenIds[1]); + + burn(owner1, tokenIds[0]); + + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_totalSupply()).toStrictEqual(u256.One); + + // Verify that accessing the burned token's owner returns zero address + expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(1); + expect(owner1Tokens).toContainEqual(tokenIds[1]); + }); + + it('should handle burning multiple tokens', () => { + mint(owner1, tokenIds[0]); + mint(owner1, tokenIds[1]); + + burn(owner1, tokenIds[0]); + burn(owner1, tokenIds[1]); + + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(u256.Zero); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(0); + }); + }); + + describe('Complex Token Interactions', () => { + it('should handle mints, transfers, and burns correctly', () => { + // Mint tokens to owner1 and owner2 + mint(owner1, tokenIds[0]); + mint(owner1, tokenIds[1]); + mint(owner2, tokenIds[2]); + + // Transfer tokenIds[1] from owner1 to owner2 + transfer(owner1, owner2, tokenIds[1]); + + // Burn tokenIds[0] owned by owner1 + burn(owner1, tokenIds[0]); + + // Verify total supply + expect(_totalSupply()).toStrictEqual(u256.fromU32(2)); + + // Verify balances + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); + + // Verify ownership + expect(_ownerOf(tokenIds[1])).toStrictEqual(owner2); + expect(_ownerOf(tokenIds[2])).toStrictEqual(owner2); + + // Verify that accessing the burned token's owner returns zero address + expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(0); + + const owner2Tokens = getOwnedTokens(owner2); + expect(owner2Tokens.length).toBe(2); + expect(owner2Tokens).toContainEqual(tokenIds[1]); + expect(owner2Tokens).toContainEqual(tokenIds[2]); + }); + }); + + describe('Error Handling and Boundary Conditions', () => { + it('should not mint existing token ID', () => { + mint(owner1, tokenIds[0]); + + expect(() => { + mint(owner1, tokenIds[0]); + }).toThrow('Token already minted'); + }); + + it('should not transfer nonexistent token', () => { + expect(() => { + transfer(owner1, owner2, tokenIds[0]); + }).toThrow('Nonexistent token'); + }); + + it('should not transfer to zero address', () => { + mint(owner1, tokenIds[0]); + + expect(() => { + _transferFrom(owner1, zeroAddress, tokenIds[0]); + }).toThrow('Unauthorized to'); + }); + + it('should not burn nonexistent token', () => { + expect(() => { + burn(owner1, tokenIds[0]); + }).toThrow('Nonexistent token'); + }); + + it('should have zero total supply after all tokens are burned', () => { + mint(owner1, tokenIds[0]); + burn(owner1, tokenIds[0]); + + expect(_totalSupply()).toStrictEqual(u256.Zero); + + // Check token ownership has been cleared + expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); + + // Verify owned tokens + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(0); + }); + + it('should mint a token with id=u256.Max', () => { + mint(owner1, u256.Max); + expect(_totalSupply()).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_ownerOf(u256.Max)).toStrictEqual(owner1); + const owner1Tokens = getOwnedTokens(owner1); + expect(owner1Tokens.length).toBe(1); + expect(owner1Tokens).toContainEqual(u256.Max); + }); + }); +}); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts new file mode 100644 index 0000000..e4a0312 --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import { bytesToU256 } from '@massalabs/as-types'; +import { getKeys, Storage } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; +import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; + +/** + * Returns the all the tokens owned by a specific address. + * + * @param owner - The address of the owner. + * + * @returns An array of u256 representing the tokens owned by the address. + * + */ +export function getOwnedTokens(owner: string): u256[] { + const tokens: u256[] = []; + const keys = getKeys(_getOwnedTokensKeyPrefix(owner)); + + for (let i = 0; i < keys.length; i++) { + tokens.push(bytesToU256(Storage.get(keys[i]))); + } + return tokens; +} From b3057d515e38ea08be169dae7b8b6c6f38f42f6c Mon Sep 17 00:00:00 2001 From: BenRey Date: Wed, 4 Dec 2024 14:30:07 +0100 Subject: [PATCH 2/5] Refactor NFT contract to use u64 instead of u256 for token IDs and balances --- .../assembly/contracts/NFT/NFT-example.ts | 36 ++++--- .../assembly/contracts/NFT/NFT-internals.ts | 46 +++++---- .../contracts/NFT/NFTEnumerable-example.ts | 42 ++++---- .../contracts/NFT/NFTEnumerable-internals.ts | 48 ++++----- .../NFT/__tests__/NFT-example.spec.ts | 40 ++++---- .../NFT/__tests__/NFT-internals.spec.ts | 7 +- .../__tests__/NFTEnumerable-example.spec.ts | 98 +++++++++---------- .../__tests__/NFTEnumerable-internals.spec.ts | 95 +++++++++--------- .../contracts/NFT/__tests__/helpers.ts | 11 +-- 9 files changed, 201 insertions(+), 222 deletions(-) diff --git a/smart-contracts/assembly/contracts/NFT/NFT-example.ts b/smart-contracts/assembly/contracts/NFT/NFT-example.ts index d4ea73a..b9eaecc 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-example.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-example.ts @@ -15,7 +15,7 @@ import { Args, boolToByte, stringToBytes, - u256ToBytes, + u64ToBytes, } from '@massalabs/as-types'; import { _approve, @@ -34,6 +34,9 @@ import { setOwner, onlyOwner } from '../utils/ownership'; import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; +const NAME = 'MASSA_NFT'; +const SYMBOL = 'NFT'; + /** * @param binaryArgs - serialized strings representing the name and the symbol of the NFT * @@ -44,14 +47,9 @@ import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; * * Finally, it sets the owner of the contract to the caller of the constructor. */ -export function constructor(binaryArgs: StaticArray): void { +export function constructor(_: StaticArray): void { assert(isDeployingContract()); - const args = new Args(binaryArgs); - const name = args.nextString().expect('name argument is missing or invalid'); - const symbol = args - .nextString() - .expect('symbol argument is missing or invalid'); - _constructor(name, symbol); + _constructor(NAME, SYMBOL); setOwner(new Args().add(Context.caller().toString()).serialize()); } @@ -66,7 +64,7 @@ export function symbol(): string { /** * * @param binaryArgs - serialized string representing the address whose balance we want to check - * @returns a serialized u256 representing the balance of the address + * @returns a serialized u64 representing the balance of the address * @remarks As we can see, instead of checking the storage directly, * we call the _balanceOf function from the NFT-internals. */ @@ -75,31 +73,31 @@ export function balanceOf(binaryArgs: StaticArray): StaticArray { const address = args .nextString() .expect('address argument is missing or invalid'); - return u256ToBytes(_balanceOf(address)); + return u64ToBytes(_balanceOf(address)); } /** * - * @param binaryArgs - serialized u256 representing the tokenId whose owner we want to check + * @param binaryArgs - serialized u64 representing the tokenId whose owner we want to check * @returns a serialized string representing the address of owner of the tokenId */ export function ownerOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); return stringToBytes(_ownerOf(tokenId)); } /** * - * @param binaryArgs - serialized u256 representing the tokenId whose approved address we want to check + * @param binaryArgs - serialized u64 representing the tokenId whose approved address we want to check * @returns a serialized string representing the address of the approved address of the tokenId */ export function getApproved(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); return stringToBytes(_getApproved(tokenId)); } @@ -132,7 +130,7 @@ export function approve(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _approve(to, tokenId); } @@ -165,7 +163,7 @@ export function transferFrom(binaryArgs: StaticArray): void { const from = args.nextString().expect('from argument is missing or invalid'); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _transferFrom(from, to, tokenId); } @@ -190,14 +188,14 @@ export function mint(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _update(to, tokenId, ''); } /** * - * @param binaryArgs - serialized u256 representing the tokenId to burn + * @param binaryArgs - serialized u64 representing the tokenId to burn * * @remarks This function is not part of the ERC721 standard. * It serves as an example of how to use the NFT-internals functions to implement custom features. @@ -211,7 +209,7 @@ export function mint(binaryArgs: StaticArray): void { export function burn(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _update('', tokenId, ''); } diff --git a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts index e863a05..6579fbc 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts @@ -19,16 +19,14 @@ import { stringToBytes, - bytesToU256, + bytesToU64, bytesToString, boolToByte, byteToBool, - u256ToBytes, + u64ToBytes, } from '@massalabs/as-types'; import { Storage, Context } from '@massalabs/massa-as-sdk'; -import { u256 } from 'as-bignum/assembly'; - export const NAME_KEY: StaticArray = [0x01]; export const SYMBOL_KEY: StaticArray = [0x02]; @@ -62,16 +60,16 @@ export function balanceKey(address: string): StaticArray { * @param tokenId - the tokenID of the owner * @returns the key of the owner in the storage for the given tokenId */ -export function ownerKey(tokenId: u256): StaticArray { - return OWNER_KEY_PREFIX.concat(u256ToBytes(tokenId)); +export function ownerKey(tokenId: u64): StaticArray { + return OWNER_KEY_PREFIX.concat(u64ToBytes(tokenId)); } /** * @param tokenId - the tokenID of the approved token * @returns the key of the allowance in the storage for the given owner and spender */ -function allowanceKey(tokenId: u256): StaticArray { - return ALLOWANCE_KEY_PREFIX.concat(u256ToBytes(tokenId)); +function allowanceKey(tokenId: u64): StaticArray { + return ALLOWANCE_KEY_PREFIX.concat(u64ToBytes(tokenId)); } /** @@ -94,9 +92,9 @@ function operatorAllowanceKey( * * @param owner - An address for whom to query the balance */ -export function _balanceOf(owner: string): u256 { +export function _balanceOf(owner: string): u64 { const key = balanceKey(owner); - return Storage.has(key) ? bytesToU256(Storage.get(key)) : u256.Zero; + return Storage.has(key) ? bytesToU64(Storage.get(key)) : 0; } /** @@ -105,7 +103,7 @@ export function _balanceOf(owner: string): u256 { * @param tokenId - The identifier for an NFT * @returns the address of the owner of the NFT or an empty string if the NFT is not owned */ -export function _ownerOf(tokenId: u256): string { +export function _ownerOf(tokenId: u64): string { const key = ownerKey(tokenId); return Storage.has(key) ? bytesToString(Storage.get(key)) : ''; } @@ -134,7 +132,7 @@ export function _symbol(): string { * @remarks If approved is the zero address, the function will clear the approval for the NFT by deleting the key. * */ -export function _approve(approved: string, tokenId: u256): void { +export function _approve(approved: string, tokenId: u64): void { assert(_isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized'); const key = allowanceKey(tokenId); approved != '' @@ -149,7 +147,7 @@ export function _approve(approved: string, tokenId: u256): void { * @param tokenId - Id of the NFT * @returns Address of the approved owner of the NFT or an empty string if no address is approved. */ -export function _getApproved(tokenId: u256): string { +export function _getApproved(tokenId: u64): string { const key = allowanceKey(tokenId); return Storage.has(key) ? bytesToString(Storage.get(key)) : ''; } @@ -161,7 +159,7 @@ export function _getApproved(tokenId: u256): string { * @param tokenId - tokenId of the token * @returns true if the operator is approved, false if not */ -export function _isApproved(operator: string, tokenId: u256): bool { +export function _isApproved(operator: string, tokenId: u64): bool { const allowKey = allowanceKey(tokenId); return Storage.has(allowKey) ? bytesToString(Storage.get(allowKey)) == operator @@ -202,7 +200,7 @@ export function _isApprovedForAll(owner: string, operator: string): bool { * 2. The operator has been approved by the owner * 3. The operator has been approved for all NFTs by the owner */ -export function _isAuthorized(operator: string, tokenId: u256): bool { +export function _isAuthorized(operator: string, tokenId: u64): bool { return ( _ownerOf(tokenId) == operator || _isApproved(operator, tokenId) || @@ -227,7 +225,7 @@ export function _isAuthorized(operator: string, tokenId: u256): bool { * For example if you were to wrap this helper in a `transfer` function, * you should check that the caller is the owner of the token, and then call the _update function. */ -export function _update(to: string, tokenId: u256, auth: string): string { +export function _update(to: string, tokenId: u64, auth: string): string { const from = _ownerOf(tokenId); assert(to != from, 'The from and to addresses are the same'); if (auth != '') { @@ -237,19 +235,19 @@ export function _update(to: string, tokenId: u256, auth: string): string { // clear the approval _approve('', tokenId); // update the balance of the from - const fromBalance = bytesToU256(Storage.get(balanceKey(from))); - assert(fromBalance > u256.Zero, 'Insufficient balance'); - Storage.set(balanceKey(from), u256ToBytes(fromBalance - u256.One)); + const fromBalance = bytesToU64(Storage.get(balanceKey(from))); + assert(fromBalance > 0, 'Insufficient balance'); + Storage.set(balanceKey(from), u64ToBytes(fromBalance - 1)); } if (to != '') { const toBalanceKey = balanceKey(to); // update the balance of the to if (Storage.has(toBalanceKey)) { - const toBalance = bytesToU256(Storage.get(toBalanceKey)); - assert(toBalance < u256.Max, 'Balance overflow'); - Storage.set(toBalanceKey, u256ToBytes(toBalance + u256.One)); + const toBalance = bytesToU64(Storage.get(toBalanceKey)); + assert(toBalance < u64.MAX_VALUE, 'Balance overflow'); + Storage.set(toBalanceKey, u64ToBytes(toBalance + 1)); } else { - Storage.set(toBalanceKey, u256ToBytes(u256.One)); + Storage.set(toBalanceKey, u64ToBytes(1)); } // update the owner of the token Storage.set(ownerKey(tokenId), stringToBytes(to.toString())); @@ -271,7 +269,7 @@ export function _update(to: string, tokenId: u256, auth: string): string { * If the caller is not the owner, it must be an authorized operator to execute the function. * **/ -export function _transferFrom(from: string, to: string, tokenId: u256): void { +export function _transferFrom(from: string, to: string, tokenId: u64): void { assert( _isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized caller', diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts index 4e57664..9e0b80c 100644 --- a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts @@ -22,7 +22,7 @@ * * [TOTAL_SUPPLY_KEY] = totalSupply * - `TOTAL_SUPPLY_KEY`: A constant key for the total supply of tokens. - * - `totalSupply`: A `u256` value representing the total number of tokens in existence. + * - `totalSupply`: A `u64` value representing the total number of tokens in existence. * * - **Owned Tokens:** * @@ -56,7 +56,7 @@ import { Args, boolToByte, stringToBytes, - u256ToBytes, + u64ToBytes, } from '@massalabs/as-types'; import { _approve, @@ -75,6 +75,9 @@ import { import { setOwner, onlyOwner } from '../utils/ownership'; import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; +const NAME = 'MASSA_NFT'; +const SYMBOL = 'NFT'; + /** * @param binaryArgs - serialized strings representing the name and the symbol of the NFT * @@ -85,14 +88,9 @@ import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; * * Finally, it sets the owner of the contract to the caller of the constructor. */ -export function constructor(binaryArgs: StaticArray): void { +export function constructor(_: StaticArray): void { assert(isDeployingContract()); - const args = new Args(binaryArgs); - const name = args.nextString().expect('name argument is missing or invalid'); - const symbol = args - .nextString() - .expect('symbol argument is missing or invalid'); - _constructor(name, symbol); + _constructor(NAME, SYMBOL); setOwner(new Args().add(Context.caller().toString()).serialize()); } @@ -107,38 +105,38 @@ export function symbol(): string { /** * * @param binaryArgs - serialized string representing the address whose balance we want to check - * @returns a serialized u256 representing the balance of the address + * @returns a serialized u64 representing the balance of the address */ export function balanceOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const address = args .nextString() .expect('address argument is missing or invalid'); - return u256ToBytes(_balanceOf(address)); + return u64ToBytes(_balanceOf(address)); } /** * - * @param binaryArgs - serialized u256 representing the tokenId whose owner we want to check + * @param binaryArgs - serialized u64 representing the tokenId whose owner we want to check * @returns a serialized string representing the address of owner of the tokenId */ export function ownerOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); return stringToBytes(_ownerOf(tokenId)); } /** * - * @param binaryArgs - serialized u256 representing the tokenId whose approved address we want to check + * @param binaryArgs - serialized u64 representing the tokenId whose approved address we want to check * @returns a serialized string representing the address of the approved address of the tokenId */ export function getApproved(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); return stringToBytes(_getApproved(tokenId)); } @@ -171,7 +169,7 @@ export function approve(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _approve(to, tokenId); } @@ -203,7 +201,7 @@ export function transferFrom(binaryArgs: StaticArray): void { const from = args.nextString().expect('from argument is missing or invalid'); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _transferFrom(from, to, tokenId); } @@ -219,14 +217,14 @@ export function mint(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _update(to, tokenId, ''); } /** * - * @param binaryArgs - serialized u256 representing the tokenId to burn + * @param binaryArgs - serialized u64 representing the tokenId to burn * * @remarks This function is not part of the ERC721 standard. * It serves as an example of how to use the NFT-enumerable-internals functions to implement custom features. @@ -234,17 +232,17 @@ export function mint(binaryArgs: StaticArray): void { export function burn(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const tokenId = args - .nextU256() + .nextU64() .expect('tokenId argument is missing or invalid'); _update('', tokenId, ''); } /** * Returns the total number of tokens. - * @returns a serialized u256 representing the total supply + * @returns a serialized u64 representing the total supply */ export function totalSupply(_: StaticArray): StaticArray { - return u256ToBytes(_totalSupply()); + return u64ToBytes(_totalSupply()); } /** diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts index 8af1de1..f8f80aa 100644 --- a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts @@ -5,8 +5,8 @@ */ import { Context, Storage } from '@massalabs/massa-as-sdk'; -import { u256 } from 'as-bignum/assembly'; -import { bytesToU256, stringToBytes, u256ToBytes } from '@massalabs/as-types'; + +import { bytesToU64, stringToBytes, u64ToBytes } from '@massalabs/as-types'; import { _isAuthorized, _ownerOf, @@ -27,7 +27,7 @@ export const OWNED_TOKENS_KEY: StaticArray = stringToBytes('ownedTokens'); */ export function _constructor(name: string, symbol: string): void { _constructorBase(name, symbol); - Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(u256.Zero)); + Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(0)); } /* -------------------------------------------------------------------------- */ @@ -37,22 +37,22 @@ export function _constructor(name: string, symbol: string): void { /** * Returns the total number of tokens in existence. */ -export function _totalSupply(): u256 { - return bytesToU256(Storage.get(TOTAL_SUPPLY_KEY)); +export function _totalSupply(): u64 { + return bytesToU64(Storage.get(TOTAL_SUPPLY_KEY)); } /** * Increases the total supply by the given delta. * @param delta - The amount to increase the total supply by. * - * @throws Will throw an error if the addition of delta to currentSupply exceeds u256.Max. + * @throws Will throw an error if the addition of delta to currentSupply exceeds u64.Max. */ -export function _increaseTotalSupply(delta: u256): void { +export function _increaseTotalSupply(delta: u64): void { const currentSupply = _totalSupply(); - const maxAllowedDelta = u256.sub(u256.Max, currentSupply); - assert(u256.le(delta, maxAllowedDelta), 'Total supply overflow'); - const newSupply = u256.add(currentSupply, delta); - Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); + const maxAllowedDelta = u64.MAX_VALUE - currentSupply; + assert(delta <= maxAllowedDelta, 'Total supply overflow'); + const newSupply = currentSupply + delta; + Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(newSupply)); } /** @@ -61,11 +61,11 @@ export function _increaseTotalSupply(delta: u256): void { * * @throws Will throw an error if `delta` exceeds the current total supply, causing an underflow. */ -export function _decreaseTotalSupply(delta: u256): void { +export function _decreaseTotalSupply(delta: u64): void { const currentSupply = _totalSupply(); - assert(u256.le(delta, currentSupply), 'Total supply underflow'); - const newSupply = u256.sub(currentSupply, delta); - Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); + assert(delta <= currentSupply, 'Total supply underflow'); + const newSupply = currentSupply - delta; + Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(newSupply)); } /* -------------------------------------------------------------------------- */ @@ -85,9 +85,9 @@ export function _getOwnedTokensKeyPrefix(owner: string): StaticArray { * @param owner - The owner's address. * @param tokenId - The token ID to add. */ -function _addTokenToOwnerEnumeration(owner: string, tokenId: u256): void { - const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); - Storage.set(key, u256ToBytes(tokenId)); +function _addTokenToOwnerEnumeration(owner: string, tokenId: u64): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u64ToBytes(tokenId)); + Storage.set(key, u64ToBytes(tokenId)); } /** @@ -95,8 +95,8 @@ function _addTokenToOwnerEnumeration(owner: string, tokenId: u256): void { * @param owner - The owner's address. * @param tokenId - The token ID to remove. */ -function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u256): void { - const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); +function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u64): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u64ToBytes(tokenId)); Storage.del(key); } @@ -110,13 +110,13 @@ function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u256): void { * @param tokenId - The token ID. * @param auth - The address authorized to perform the update. */ -export function _update(to: string, tokenId: u256, auth: string): void { +export function _update(to: string, tokenId: u64, auth: string): void { const previousOwner = _updateBase(to, tokenId, auth); // Mint if (previousOwner == '') { _addTokenToOwnerEnumeration(to, tokenId); - _increaseTotalSupply(u256.One); + _increaseTotalSupply(1); } else { // Transfer if (to != '' && to != previousOwner) { @@ -126,7 +126,7 @@ export function _update(to: string, tokenId: u256, auth: string): void { // Burn else if (to == '') { _removeTokenFromOwnerEnumeration(previousOwner, tokenId); - _decreaseTotalSupply(u256.One); + _decreaseTotalSupply(1); } } } @@ -141,7 +141,7 @@ export function _update(to: string, tokenId: u256, auth: string): void { * @param to - The new owner's address. * @param tokenId - The token ID to transfer. */ -export function _transferFrom(from: string, to: string, tokenId: u256): void { +export function _transferFrom(from: string, to: string, tokenId: u64): void { assert( _isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized caller', diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts index dd145d7..bf5c90e 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts @@ -20,11 +20,11 @@ import { } from '../NFT-example'; import { Args, + NoArg, byteToBool, bytesToString, - bytesToU256, + bytesToU64, } from '@massalabs/as-types'; -import { u256 } from 'as-bignum/assembly'; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; @@ -35,7 +35,7 @@ const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const newOwner = 'AU12F7y3PWpw72XcwhSksJztRiTSqAvLxaLacP2qDYhNUEfEXuG4T'; const zeroAddress = ''; -const tokenId = u256.One; +const tokenId: u64 = 1; function switchUser(user: string): void { changeCallStack(user + ' , ' + tokenAddress); @@ -45,7 +45,7 @@ beforeEach(() => { resetStorage(); switchUser(contractOwner); setDeployContext(contractOwner); - constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); + constructor(NoArg.serialize()); }); describe('Initialization', () => { @@ -67,9 +67,9 @@ describe('Minting', () => { expect(bytesToString(ownerOf(new Args().add(tokenId).serialize()))).toBe( to, ); - expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(u256.One); + expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( + 1, + ); }); throws('Minting from not owner should fail', () => { switchUser(from); @@ -84,23 +84,23 @@ describe('Minting', () => { }); test('Mint multiple tokens to an address', () => { mint(new Args().add(to).add(tokenId).serialize()); - mint(new Args().add(to).add(new u256(2)).serialize()); - expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(new u256(2)); + mint(new Args().add(to).add(u64(2)).serialize()); + expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( + 2, + ); }); test('Mint multiple tokens to different addresses', () => { mint(new Args().add(to).add(tokenId).serialize()); - mint(new Args().add(from).add(new u256(2)).serialize()); - expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(u256.One); - expect( - bytesToString(ownerOf(new Args().add(new u256(2)).serialize())), - ).toBe(from); + mint(new Args().add(from).add(u64(2)).serialize()); + expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( + 1, + ); + expect(bytesToString(ownerOf(new Args().add(u64(2)).serialize()))).toBe( + from, + ); expect( - bytesToU256(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(u256.One); + bytesToU64(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(1); expect(bytesToString(ownerOf(new Args().add(tokenId).serialize()))).toBe( to, ); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts index 390be02..34c89be 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts @@ -4,7 +4,6 @@ import { setDeployContext, } from '@massalabs/massa-as-sdk'; -import { u256 } from 'as-bignum/assembly'; import { _approve, _balanceOf, @@ -27,7 +26,7 @@ const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const newOwner = 'AU12F7y3PWpw72XcwhSksJztRiTSqAvLxaLacP2qDYhNUEfEXuG4T'; const zeroAddress = ''; -const tokenId = u256.One; +const tokenId: u64 = 1; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; @@ -125,10 +124,10 @@ describe('Transferring NFTs', () => { expect(ownerOfToken).toBe(newOwner); const balanceOfNewOwner = _balanceOf(newOwner); - expect(balanceOfNewOwner).toBe(u256.One); + expect(balanceOfNewOwner).toBe(1); const balanceOfOldOwner = _balanceOf(from); - expect(balanceOfOldOwner).toBe(u256.Zero); + expect(balanceOfOldOwner).toBe(0); }); throws('Transferring a non-existent token should fail', () => { _transferFrom(from, to, tokenId); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts index 866f72e..8c20109 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts @@ -6,11 +6,11 @@ import { import { Args, + NoArg, byteToBool, bytesToString, - bytesToU256, + bytesToU64, } from '@massalabs/as-types'; -import { u256 } from 'as-bignum/assembly'; import { approve, balanceOf, @@ -38,13 +38,7 @@ const from = 'AU12CzoKEASaeBHnxGLnHDG2u73dLzWWfgvW6bc4L1UfMA5Uc5Fg7'; const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const zeroAddress = ''; -const tokenIds = [ - u256.One, - u256.fromU32(2), - u256.fromU32(3), - u256.fromU32(4), - u256.fromU32(5), -]; +const tokenIds: u64[] = [1, 2, 3, 4, 5]; function switchUser(user: string): void { changeCallStack(user + ' , ' + tokenAddress); @@ -54,58 +48,58 @@ beforeEach(() => { resetStorage(); switchUser(contractOwner); setDeployContext(contractOwner); - constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); + constructor(NoArg.serialize()); }); describe('NFT Enumerable Contract', () => { describe('Initialization', () => { - test('should return correct name and symbol', () => { + it('should return correct name and symbol', () => { expect(name()).toBe(NFTName); expect(symbol()).toBe(NFTSymbol); }); - test('should return correct contract owner', () => { + it('should return correct contract owner', () => { expect(bytesToString(ownerAddress([]))).toBe(contractOwner); }); }); describe('Minting', () => { - test('should mint a token to an address', () => { + it('should mint a token to an address', () => { mint(new Args().add(to).add(tokenIds[0]).serialize()); expect( bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), ).toBe(to); expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(u256.One); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + bytesToU64(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(1); + expect(bytesToU64(totalSupply([]))).toStrictEqual(1); }); - test('should mint multiple tokens to different addresses', () => { + it('should mint multiple tokens to different addresses', () => { mint(new Args().add(to).add(tokenIds[0]).serialize()); mint(new Args().add(from).add(tokenIds[1]).serialize()); expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(u256.One); + bytesToU64(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(1); expect( - bytesToU256(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(u256.One); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); + bytesToU64(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(1); + expect(bytesToU64(totalSupply([]))).toStrictEqual(u64(2)); }); - test('should not mint to zero address', () => { + it('should mint to an invalid address', () => { expect(() => { mint(new Args().add(zeroAddress).add(tokenIds[0]).serialize()); }).toThrow('Unauthorized to'); }); - test('should not mint an already existing tokenId', () => { + it('should not mint an already existing tokenId', () => { mint(new Args().add(to).add(tokenIds[0]).serialize()); expect(() => { mint(new Args().add(to).add(tokenIds[0]).serialize()); }).toThrow('Token already minted'); }); - test('should not allow non-owner to mint tokens', () => { + it('should not allow non-owner to mint tokens', () => { switchUser(from); expect(() => { mint(new Args().add(to).add(tokenIds[0]).serialize()); @@ -114,7 +108,7 @@ describe('NFT Enumerable Contract', () => { }); describe('Approval', () => { - test('should approve a token for an address', () => { + it('should approve a token for an address', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); approve(new Args().add(approved).add(tokenIds[0]).serialize()); @@ -123,7 +117,7 @@ describe('NFT Enumerable Contract', () => { ).toBe(approved); }); - test('should set approval for all', () => { + it('should set approval for all', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); setApprovalForAll(new Args().add(approved).add(true).serialize()); @@ -134,7 +128,7 @@ describe('NFT Enumerable Contract', () => { ).toBe(true); }); - test('should revoke approval for all', () => { + it('should revoke approval for all', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); setApprovalForAll(new Args().add(approved).add(true).serialize()); @@ -146,7 +140,7 @@ describe('NFT Enumerable Contract', () => { ).toBe(false); }); - test('should not approve token not owned', () => { + it('should not approve token not owned', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(approved); expect(() => { @@ -156,7 +150,7 @@ describe('NFT Enumerable Contract', () => { }); describe('Transfers', () => { - test('should transfer token from owner', () => { + it('should transfer token from owner', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); transferFrom(new Args().add(from).add(to).add(tokenIds[0]).serialize()); @@ -164,14 +158,14 @@ describe('NFT Enumerable Contract', () => { bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), ).toBe(to); expect( - bytesToU256(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(u256.One); + bytesToU64(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(1); expect( - bytesToU256(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(u256.Zero); + bytesToU64(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(0); }); - test('should transfer approved token', () => { + it('should transfer approved token', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); approve(new Args().add(approved).add(tokenIds[0]).serialize()); @@ -182,7 +176,7 @@ describe('NFT Enumerable Contract', () => { ).toBe(to); }); - test('should transfer token using approval for all', () => { + it('should transfer token using approval for all', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); setApprovalForAll(new Args().add(approved).add(true).serialize()); @@ -193,7 +187,7 @@ describe('NFT Enumerable Contract', () => { ).toBe(to); }); - test('should not transfer token without approval', () => { + it('should not transfer token without approval', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(approved); expect(() => { @@ -203,7 +197,7 @@ describe('NFT Enumerable Contract', () => { }); describe('Burning', () => { - test('should burn a token', () => { + it('should burn a token', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); burn(new Args().add(tokenIds[0]).serialize()); @@ -212,10 +206,10 @@ describe('NFT Enumerable Contract', () => { [], ); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + expect(bytesToU64(totalSupply([]))).toStrictEqual(0); }); - test('should burn a token with approval', () => { + it('should burn a token with approval', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); approve(new Args().add(approved).add(tokenIds[0]).serialize()); @@ -224,10 +218,10 @@ describe('NFT Enumerable Contract', () => { expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( [], ); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + expect(bytesToU64(totalSupply([]))).toStrictEqual(0); }); - test('should burn a token using approval for all', () => { + it('should burn a token using approval for all', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(from); setApprovalForAll(new Args().add(approved).add(true).serialize()); @@ -236,10 +230,10 @@ describe('NFT Enumerable Contract', () => { expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( [], ); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + expect(bytesToU64(totalSupply([]))).toStrictEqual(0); }); - test('should not burn token without approval', () => { + it('should not burn token without approval', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); switchUser(to); expect(() => { @@ -249,15 +243,15 @@ describe('NFT Enumerable Contract', () => { }); describe('Enumeration', () => { - test('should return correct total supply', () => { - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); + it('should return correct total supply', () => { + expect(bytesToU64(totalSupply([]))).toStrictEqual(0); mint(new Args().add(from).add(tokenIds[0]).serialize()); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + expect(bytesToU64(totalSupply([]))).toStrictEqual(1); mint(new Args().add(to).add(tokenIds[1]).serialize()); - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); + expect(bytesToU64(totalSupply([]))).toStrictEqual(2); }); - test('should return correct tokens owned by an address', () => { + it('should return correct tokens owned by an address', () => { // Assuming we have an exported function to get owned tokens mint(new Args().add(from).add(tokenIds[0]).serialize()); mint(new Args().add(from).add(tokenIds[1]).serialize()); @@ -275,7 +269,7 @@ describe('NFT Enumerable Contract', () => { expect(toTokens).toContainEqual(tokenIds[2]); }); - test('should update owned tokens after transfer', () => { + it('should update owned tokens after transfer', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); mint(new Args().add(from).add(tokenIds[1]).serialize()); switchUser(from); @@ -292,7 +286,7 @@ describe('NFT Enumerable Contract', () => { expect(toTokens).toContainEqual(tokenIds[0]); }); - test('should update owned tokens after burn', () => { + it('should update owned tokens after burn', () => { mint(new Args().add(from).add(tokenIds[0]).serialize()); mint(new Args().add(from).add(tokenIds[1]).serialize()); switchUser(from); @@ -304,7 +298,7 @@ describe('NFT Enumerable Contract', () => { expect(fromTokens).toContainEqual(tokenIds[1]); // Total supply should be updated - expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); + expect(bytesToU64(totalSupply([]))).toStrictEqual(1); }); }); }); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts index c89d903..d20e474 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts @@ -1,5 +1,4 @@ import { resetStorage, setDeployContext } from '@massalabs/massa-as-sdk'; -import { u256 } from 'as-bignum/assembly'; import { _update, _balanceOf, @@ -16,26 +15,20 @@ const caller = 'A12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; const owner1 = caller; const owner2 = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const zeroAddress = ''; -const tokenIds = [ - u256.fromU32(1), - u256.fromU32(2), - u256.fromU32(3), - u256.fromU32(4), - u256.fromU32(5), -]; +const tokenIds: u64[] = [1, 2, 3, 4, 5]; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; -function mint(to: string, tokenId: u256): void { +function mint(to: string, tokenId: u64): void { _update(to, tokenId, zeroAddress); } -function transfer(from: string, to: string, tokenId: u256): void { +function transfer(from: string, to: string, tokenId: u64): void { _update(to, tokenId, from); } -function burn(owner: string, tokenId: u256): void { +function burn(owner: string, tokenId: u64): void { _update(zeroAddress, tokenId, owner); } @@ -48,59 +41,59 @@ describe('NFT Enumerable Internals', () => { describe('Initialization', () => { it('should have zero total supply initially', () => { - expect(_totalSupply()).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(0); }); }); describe('Total Supply Management', () => { it('should update total supply when token is minted', () => { mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u256.One); + expect(_totalSupply()).toStrictEqual(1); }); it('should update total supply when token is burned', () => { mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u256.One); + expect(_totalSupply()).toStrictEqual(1); burn(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(0); }); - it('should not allow total supply to exceed u256.Max', () => { - // Set total supply to u256.Max - 1 - const nearMaxSupply = u256.sub(u256.Max, u256.One); - _increaseTotalSupply(u256.sub(u256.Max, u256.One)); + it('should not allow total supply to exceed u64.Max', () => { + // Set total supply to u64.Max - 1 + const nearMaxSupply = u64.MAX_VALUE - 1; + _increaseTotalSupply(nearMaxSupply); expect(_totalSupply()).toStrictEqual(nearMaxSupply); - // Mint one more token should succeed (totalSupply = u256.Max) + // Mint one more token should succeed (totalSupply = u64.Max) mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u256.Max); + expect(_totalSupply()).toStrictEqual(u64.MAX_VALUE); // Minting another token should fail due to overflow expect(() => { - _increaseTotalSupply(u256.One); + _increaseTotalSupply(1); }).toThrow('Total supply overflow'); // Ensure your contract throws this exact error }); it('should not allow total supply to underflow', () => { // Ensure total supply is zero - expect(_totalSupply()).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(0); // Attempt to decrease supply by 1 should fail expect(() => { - _decreaseTotalSupply(u256.One); + _decreaseTotalSupply(1); }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error // Set total supply to 1 - _increaseTotalSupply(u256.One); - expect(_totalSupply()).toStrictEqual(u256.One); + _increaseTotalSupply(1); + expect(_totalSupply()).toStrictEqual(1); // Decrease supply by 1 should succeed - _decreaseTotalSupply(u256.One); - expect(_totalSupply()).toStrictEqual(u256.Zero); + _decreaseTotalSupply(1); + expect(_totalSupply()).toStrictEqual(0); // Attempt to decrease supply by another 1 should fail expect(() => { - _decreaseTotalSupply(u256.One); + _decreaseTotalSupply(1); }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error }); }); @@ -111,8 +104,8 @@ describe('NFT Enumerable Internals', () => { mint(owner1, tokenIds[1]); mint(owner2, tokenIds[2]); - expect(_balanceOf(owner1)).toStrictEqual(u256.fromU32(2)); - expect(_balanceOf(owner2)).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(2); + expect(_balanceOf(owner2)).toStrictEqual(1); const owner1Tokens = getOwnedTokens(owner1); expect(owner1Tokens.length).toBe(2); @@ -130,8 +123,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); - expect(_balanceOf(owner1)).toStrictEqual(u256.One); - expect(_balanceOf(owner2)).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(1); + expect(_balanceOf(owner2)).toStrictEqual(1); // Verify ownership expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); @@ -154,8 +147,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); - expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); - expect(_balanceOf(owner2)).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(0); + expect(_balanceOf(owner2)).toStrictEqual(1); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -173,8 +166,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); - expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); - expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); + expect(_balanceOf(owner1)).toStrictEqual(0); + expect(_balanceOf(owner2)).toStrictEqual(2); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -194,8 +187,8 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); - expect(_balanceOf(owner1)).toStrictEqual(u256.One); - expect(_totalSupply()).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(1); + expect(_totalSupply()).toStrictEqual(1); // Verify that accessing the burned token's owner returns zero address expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); @@ -213,8 +206,8 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); burn(owner1, tokenIds[1]); - expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); - expect(_totalSupply()).toStrictEqual(u256.Zero); + expect(_balanceOf(owner1)).toStrictEqual(0); + expect(_totalSupply()).toStrictEqual(0); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -236,11 +229,11 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); // Verify total supply - expect(_totalSupply()).toStrictEqual(u256.fromU32(2)); + expect(_totalSupply()).toStrictEqual(2); // Verify balances - expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); - expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); + expect(_balanceOf(owner1)).toStrictEqual(0); + expect(_balanceOf(owner2)).toStrictEqual(2); // Verify ownership expect(_ownerOf(tokenIds[1])).toStrictEqual(owner2); @@ -293,7 +286,7 @@ describe('NFT Enumerable Internals', () => { mint(owner1, tokenIds[0]); burn(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(0); // Check token ownership has been cleared expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); @@ -303,14 +296,14 @@ describe('NFT Enumerable Internals', () => { expect(owner1Tokens.length).toBe(0); }); - it('should mint a token with id=u256.Max', () => { - mint(owner1, u256.Max); - expect(_totalSupply()).toStrictEqual(u256.One); - expect(_balanceOf(owner1)).toStrictEqual(u256.One); - expect(_ownerOf(u256.Max)).toStrictEqual(owner1); + it('should mint a token with id=u64.Max', () => { + mint(owner1, u64.MAX_VALUE); + expect(_totalSupply()).toStrictEqual(1); + expect(_balanceOf(owner1)).toStrictEqual(1); + expect(_ownerOf(u64.MAX_VALUE)).toStrictEqual(owner1); const owner1Tokens = getOwnedTokens(owner1); expect(owner1Tokens.length).toBe(1); - expect(owner1Tokens).toContainEqual(u256.Max); + expect(owner1Tokens).toContainEqual(u64.MAX_VALUE); }); }); }); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts index e4a0312..a2a20ea 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts @@ -1,6 +1,5 @@ -import { bytesToU256 } from '@massalabs/as-types'; +import { bytesToU64 } from '@massalabs/as-types'; import { getKeys, Storage } from '@massalabs/massa-as-sdk'; -import { u256 } from 'as-bignum/assembly'; import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; /** @@ -8,15 +7,15 @@ import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; * * @param owner - The address of the owner. * - * @returns An array of u256 representing the tokens owned by the address. + * @returns An array of u64 representing the tokens owned by the address. * */ -export function getOwnedTokens(owner: string): u256[] { - const tokens: u256[] = []; +export function getOwnedTokens(owner: string): u64[] { + const tokens: u64[] = []; const keys = getKeys(_getOwnedTokensKeyPrefix(owner)); for (let i = 0; i < keys.length; i++) { - tokens.push(bytesToU256(Storage.get(keys[i]))); + tokens.push(bytesToU64(Storage.get(keys[i]))); } return tokens; } From 96b2a808d1d6c315c140f863b2e1783062a85caf Mon Sep 17 00:00:00 2001 From: BenRey Date: Wed, 4 Dec 2024 16:00:29 +0100 Subject: [PATCH 3/5] Improve token enumeration logic by not storing tokenID in OwnedTokens values --- .../assembly/contracts/NFT/NFTEnumerable-internals.ts | 2 +- .../assembly/contracts/NFT/__tests__/helpers.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts index f8f80aa..2fb1ede 100644 --- a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts @@ -87,7 +87,7 @@ export function _getOwnedTokensKeyPrefix(owner: string): StaticArray { */ function _addTokenToOwnerEnumeration(owner: string, tokenId: u64): void { const key = _getOwnedTokensKeyPrefix(owner).concat(u64ToBytes(tokenId)); - Storage.set(key, u64ToBytes(tokenId)); + Storage.set(key, []); } /** diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts index a2a20ea..f54a304 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts @@ -1,5 +1,5 @@ import { bytesToU64 } from '@massalabs/as-types'; -import { getKeys, Storage } from '@massalabs/massa-as-sdk'; +import { getKeys } from '@massalabs/massa-as-sdk'; import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; /** @@ -12,10 +12,15 @@ import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; */ export function getOwnedTokens(owner: string): u64[] { const tokens: u64[] = []; - const keys = getKeys(_getOwnedTokensKeyPrefix(owner)); + const prefix = _getOwnedTokensKeyPrefix(owner); + const keys = getKeys(prefix); for (let i = 0; i < keys.length; i++) { - tokens.push(bytesToU64(Storage.get(keys[i]))); + const tokenIdBytesArray = keys[i].slice(keys[i].length - sizeof()); + const tokenIdBytes = StaticArray.fromArray(tokenIdBytesArray); + const tokenId = bytesToU64(tokenIdBytes); + tokens.push(tokenId); } + return tokens; } From d08698b0924a2539f06b8a7e7e8c43c52117230e Mon Sep 17 00:00:00 2001 From: BenRey Date: Thu, 5 Dec 2024 09:28:57 +0100 Subject: [PATCH 4/5] Revert "Refactor NFT contract to use u64 instead of u256 for token IDs and balances" This reverts commit b3057d515e38ea08be169dae7b8b6c6f38f42f6c. --- .../assembly/contracts/NFT/NFT-example.ts | 36 ++--- .../assembly/contracts/NFT/NFT-internals.ts | 46 +++--- .../contracts/NFT/NFTEnumerable-example.ts | 38 ++--- .../contracts/NFT/NFTEnumerable-internals.ts | 46 +++--- .../NFT/__tests__/NFT-example.spec.ts | 40 ++--- .../NFT/__tests__/NFT-internals.spec.ts | 7 +- .../__tests__/NFTEnumerable-example.spec.ts | 54 ++++--- .../__tests__/NFTEnumerable-internals.spec.ts | 105 +++++++------- .../contracts/NFT/__tests__/helpers.ts | 15 +- smart-contracts/assembly/contracts/NFT/y.ts | 137 ++++++++++++++++++ 10 files changed, 341 insertions(+), 183 deletions(-) create mode 100644 smart-contracts/assembly/contracts/NFT/y.ts diff --git a/smart-contracts/assembly/contracts/NFT/NFT-example.ts b/smart-contracts/assembly/contracts/NFT/NFT-example.ts index b9eaecc..d4ea73a 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-example.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-example.ts @@ -15,7 +15,7 @@ import { Args, boolToByte, stringToBytes, - u64ToBytes, + u256ToBytes, } from '@massalabs/as-types'; import { _approve, @@ -34,9 +34,6 @@ import { setOwner, onlyOwner } from '../utils/ownership'; import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; -const NAME = 'MASSA_NFT'; -const SYMBOL = 'NFT'; - /** * @param binaryArgs - serialized strings representing the name and the symbol of the NFT * @@ -47,9 +44,14 @@ const SYMBOL = 'NFT'; * * Finally, it sets the owner of the contract to the caller of the constructor. */ -export function constructor(_: StaticArray): void { +export function constructor(binaryArgs: StaticArray): void { assert(isDeployingContract()); - _constructor(NAME, SYMBOL); + const args = new Args(binaryArgs); + const name = args.nextString().expect('name argument is missing or invalid'); + const symbol = args + .nextString() + .expect('symbol argument is missing or invalid'); + _constructor(name, symbol); setOwner(new Args().add(Context.caller().toString()).serialize()); } @@ -64,7 +66,7 @@ export function symbol(): string { /** * * @param binaryArgs - serialized string representing the address whose balance we want to check - * @returns a serialized u64 representing the balance of the address + * @returns a serialized u256 representing the balance of the address * @remarks As we can see, instead of checking the storage directly, * we call the _balanceOf function from the NFT-internals. */ @@ -73,31 +75,31 @@ export function balanceOf(binaryArgs: StaticArray): StaticArray { const address = args .nextString() .expect('address argument is missing or invalid'); - return u64ToBytes(_balanceOf(address)); + return u256ToBytes(_balanceOf(address)); } /** * - * @param binaryArgs - serialized u64 representing the tokenId whose owner we want to check + * @param binaryArgs - serialized u256 representing the tokenId whose owner we want to check * @returns a serialized string representing the address of owner of the tokenId */ export function ownerOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); return stringToBytes(_ownerOf(tokenId)); } /** * - * @param binaryArgs - serialized u64 representing the tokenId whose approved address we want to check + * @param binaryArgs - serialized u256 representing the tokenId whose approved address we want to check * @returns a serialized string representing the address of the approved address of the tokenId */ export function getApproved(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); return stringToBytes(_getApproved(tokenId)); } @@ -130,7 +132,7 @@ export function approve(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _approve(to, tokenId); } @@ -163,7 +165,7 @@ export function transferFrom(binaryArgs: StaticArray): void { const from = args.nextString().expect('from argument is missing or invalid'); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _transferFrom(from, to, tokenId); } @@ -188,14 +190,14 @@ export function mint(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _update(to, tokenId, ''); } /** * - * @param binaryArgs - serialized u64 representing the tokenId to burn + * @param binaryArgs - serialized u256 representing the tokenId to burn * * @remarks This function is not part of the ERC721 standard. * It serves as an example of how to use the NFT-internals functions to implement custom features. @@ -209,7 +211,7 @@ export function mint(binaryArgs: StaticArray): void { export function burn(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _update('', tokenId, ''); } diff --git a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts index 6579fbc..e863a05 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-internals.ts @@ -19,14 +19,16 @@ import { stringToBytes, - bytesToU64, + bytesToU256, bytesToString, boolToByte, byteToBool, - u64ToBytes, + u256ToBytes, } from '@massalabs/as-types'; import { Storage, Context } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; + export const NAME_KEY: StaticArray = [0x01]; export const SYMBOL_KEY: StaticArray = [0x02]; @@ -60,16 +62,16 @@ export function balanceKey(address: string): StaticArray { * @param tokenId - the tokenID of the owner * @returns the key of the owner in the storage for the given tokenId */ -export function ownerKey(tokenId: u64): StaticArray { - return OWNER_KEY_PREFIX.concat(u64ToBytes(tokenId)); +export function ownerKey(tokenId: u256): StaticArray { + return OWNER_KEY_PREFIX.concat(u256ToBytes(tokenId)); } /** * @param tokenId - the tokenID of the approved token * @returns the key of the allowance in the storage for the given owner and spender */ -function allowanceKey(tokenId: u64): StaticArray { - return ALLOWANCE_KEY_PREFIX.concat(u64ToBytes(tokenId)); +function allowanceKey(tokenId: u256): StaticArray { + return ALLOWANCE_KEY_PREFIX.concat(u256ToBytes(tokenId)); } /** @@ -92,9 +94,9 @@ function operatorAllowanceKey( * * @param owner - An address for whom to query the balance */ -export function _balanceOf(owner: string): u64 { +export function _balanceOf(owner: string): u256 { const key = balanceKey(owner); - return Storage.has(key) ? bytesToU64(Storage.get(key)) : 0; + return Storage.has(key) ? bytesToU256(Storage.get(key)) : u256.Zero; } /** @@ -103,7 +105,7 @@ export function _balanceOf(owner: string): u64 { * @param tokenId - The identifier for an NFT * @returns the address of the owner of the NFT or an empty string if the NFT is not owned */ -export function _ownerOf(tokenId: u64): string { +export function _ownerOf(tokenId: u256): string { const key = ownerKey(tokenId); return Storage.has(key) ? bytesToString(Storage.get(key)) : ''; } @@ -132,7 +134,7 @@ export function _symbol(): string { * @remarks If approved is the zero address, the function will clear the approval for the NFT by deleting the key. * */ -export function _approve(approved: string, tokenId: u64): void { +export function _approve(approved: string, tokenId: u256): void { assert(_isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized'); const key = allowanceKey(tokenId); approved != '' @@ -147,7 +149,7 @@ export function _approve(approved: string, tokenId: u64): void { * @param tokenId - Id of the NFT * @returns Address of the approved owner of the NFT or an empty string if no address is approved. */ -export function _getApproved(tokenId: u64): string { +export function _getApproved(tokenId: u256): string { const key = allowanceKey(tokenId); return Storage.has(key) ? bytesToString(Storage.get(key)) : ''; } @@ -159,7 +161,7 @@ export function _getApproved(tokenId: u64): string { * @param tokenId - tokenId of the token * @returns true if the operator is approved, false if not */ -export function _isApproved(operator: string, tokenId: u64): bool { +export function _isApproved(operator: string, tokenId: u256): bool { const allowKey = allowanceKey(tokenId); return Storage.has(allowKey) ? bytesToString(Storage.get(allowKey)) == operator @@ -200,7 +202,7 @@ export function _isApprovedForAll(owner: string, operator: string): bool { * 2. The operator has been approved by the owner * 3. The operator has been approved for all NFTs by the owner */ -export function _isAuthorized(operator: string, tokenId: u64): bool { +export function _isAuthorized(operator: string, tokenId: u256): bool { return ( _ownerOf(tokenId) == operator || _isApproved(operator, tokenId) || @@ -225,7 +227,7 @@ export function _isAuthorized(operator: string, tokenId: u64): bool { * For example if you were to wrap this helper in a `transfer` function, * you should check that the caller is the owner of the token, and then call the _update function. */ -export function _update(to: string, tokenId: u64, auth: string): string { +export function _update(to: string, tokenId: u256, auth: string): string { const from = _ownerOf(tokenId); assert(to != from, 'The from and to addresses are the same'); if (auth != '') { @@ -235,19 +237,19 @@ export function _update(to: string, tokenId: u64, auth: string): string { // clear the approval _approve('', tokenId); // update the balance of the from - const fromBalance = bytesToU64(Storage.get(balanceKey(from))); - assert(fromBalance > 0, 'Insufficient balance'); - Storage.set(balanceKey(from), u64ToBytes(fromBalance - 1)); + const fromBalance = bytesToU256(Storage.get(balanceKey(from))); + assert(fromBalance > u256.Zero, 'Insufficient balance'); + Storage.set(balanceKey(from), u256ToBytes(fromBalance - u256.One)); } if (to != '') { const toBalanceKey = balanceKey(to); // update the balance of the to if (Storage.has(toBalanceKey)) { - const toBalance = bytesToU64(Storage.get(toBalanceKey)); - assert(toBalance < u64.MAX_VALUE, 'Balance overflow'); - Storage.set(toBalanceKey, u64ToBytes(toBalance + 1)); + const toBalance = bytesToU256(Storage.get(toBalanceKey)); + assert(toBalance < u256.Max, 'Balance overflow'); + Storage.set(toBalanceKey, u256ToBytes(toBalance + u256.One)); } else { - Storage.set(toBalanceKey, u64ToBytes(1)); + Storage.set(toBalanceKey, u256ToBytes(u256.One)); } // update the owner of the token Storage.set(ownerKey(tokenId), stringToBytes(to.toString())); @@ -269,7 +271,7 @@ export function _update(to: string, tokenId: u64, auth: string): string { * If the caller is not the owner, it must be an authorized operator to execute the function. * **/ -export function _transferFrom(from: string, to: string, tokenId: u64): void { +export function _transferFrom(from: string, to: string, tokenId: u256): void { assert( _isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized caller', diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts index 9e0b80c..3dfc623 100644 --- a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-example.ts @@ -22,7 +22,7 @@ * * [TOTAL_SUPPLY_KEY] = totalSupply * - `TOTAL_SUPPLY_KEY`: A constant key for the total supply of tokens. - * - `totalSupply`: A `u64` value representing the total number of tokens in existence. + * - `totalSupply`: A `u256` value representing the total number of tokens in existence. * * - **Owned Tokens:** * @@ -56,7 +56,7 @@ import { Args, boolToByte, stringToBytes, - u64ToBytes, + u256ToBytes, } from '@massalabs/as-types'; import { _approve, @@ -94,49 +94,49 @@ export function constructor(_: StaticArray): void { setOwner(new Args().add(Context.caller().toString()).serialize()); } -export function name(): string { - return _name(); +export function name(): StaticArray { + return stringToBytes(_name()); } -export function symbol(): string { - return _symbol(); +export function symbol(): StaticArray { + return stringToBytes(_symbol()); } /** * * @param binaryArgs - serialized string representing the address whose balance we want to check - * @returns a serialized u64 representing the balance of the address + * @returns a serialized u256 representing the balance of the address */ export function balanceOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const address = args .nextString() .expect('address argument is missing or invalid'); - return u64ToBytes(_balanceOf(address)); + return u256ToBytes(_balanceOf(address)); } /** * - * @param binaryArgs - serialized u64 representing the tokenId whose owner we want to check + * @param binaryArgs - serialized u256 representing the tokenId whose owner we want to check * @returns a serialized string representing the address of owner of the tokenId */ export function ownerOf(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); return stringToBytes(_ownerOf(tokenId)); } /** * - * @param binaryArgs - serialized u64 representing the tokenId whose approved address we want to check + * @param binaryArgs - serialized u256 representing the tokenId whose approved address we want to check * @returns a serialized string representing the address of the approved address of the tokenId */ export function getApproved(binaryArgs: StaticArray): StaticArray { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); return stringToBytes(_getApproved(tokenId)); } @@ -169,7 +169,7 @@ export function approve(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _approve(to, tokenId); } @@ -201,7 +201,7 @@ export function transferFrom(binaryArgs: StaticArray): void { const from = args.nextString().expect('from argument is missing or invalid'); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _transferFrom(from, to, tokenId); } @@ -217,14 +217,14 @@ export function mint(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const to = args.nextString().expect('to argument is missing or invalid'); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _update(to, tokenId, ''); } /** * - * @param binaryArgs - serialized u64 representing the tokenId to burn + * @param binaryArgs - serialized u256 representing the tokenId to burn * * @remarks This function is not part of the ERC721 standard. * It serves as an example of how to use the NFT-enumerable-internals functions to implement custom features. @@ -232,17 +232,17 @@ export function mint(binaryArgs: StaticArray): void { export function burn(binaryArgs: StaticArray): void { const args = new Args(binaryArgs); const tokenId = args - .nextU64() + .nextU256() .expect('tokenId argument is missing or invalid'); _update('', tokenId, ''); } /** * Returns the total number of tokens. - * @returns a serialized u64 representing the total supply + * @returns a serialized u256 representing the total supply */ export function totalSupply(_: StaticArray): StaticArray { - return u64ToBytes(_totalSupply()); + return u256ToBytes(_totalSupply()); } /** diff --git a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts index 2fb1ede..acf1ac3 100644 --- a/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts +++ b/smart-contracts/assembly/contracts/NFT/NFTEnumerable-internals.ts @@ -5,8 +5,8 @@ */ import { Context, Storage } from '@massalabs/massa-as-sdk'; - -import { bytesToU64, stringToBytes, u64ToBytes } from '@massalabs/as-types'; +import { u256 } from 'as-bignum/assembly'; +import { bytesToU256, stringToBytes, u256ToBytes } from '@massalabs/as-types'; import { _isAuthorized, _ownerOf, @@ -27,7 +27,7 @@ export const OWNED_TOKENS_KEY: StaticArray = stringToBytes('ownedTokens'); */ export function _constructor(name: string, symbol: string): void { _constructorBase(name, symbol); - Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(0)); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(u256.Zero)); } /* -------------------------------------------------------------------------- */ @@ -37,22 +37,22 @@ export function _constructor(name: string, symbol: string): void { /** * Returns the total number of tokens in existence. */ -export function _totalSupply(): u64 { - return bytesToU64(Storage.get(TOTAL_SUPPLY_KEY)); +export function _totalSupply(): u256 { + return bytesToU256(Storage.get(TOTAL_SUPPLY_KEY)); } /** * Increases the total supply by the given delta. * @param delta - The amount to increase the total supply by. * - * @throws Will throw an error if the addition of delta to currentSupply exceeds u64.Max. + * @throws Will throw an error if the addition of delta to currentSupply exceeds u256.Max. */ -export function _increaseTotalSupply(delta: u64): void { +export function _increaseTotalSupply(delta: u256): void { const currentSupply = _totalSupply(); - const maxAllowedDelta = u64.MAX_VALUE - currentSupply; - assert(delta <= maxAllowedDelta, 'Total supply overflow'); - const newSupply = currentSupply + delta; - Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(newSupply)); + const maxAllowedDelta = u256.sub(u256.Max, currentSupply); + assert(u256.le(delta, maxAllowedDelta), 'Total supply overflow'); + const newSupply = u256.add(currentSupply, delta); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); } /** @@ -61,11 +61,11 @@ export function _increaseTotalSupply(delta: u64): void { * * @throws Will throw an error if `delta` exceeds the current total supply, causing an underflow. */ -export function _decreaseTotalSupply(delta: u64): void { +export function _decreaseTotalSupply(delta: u256): void { const currentSupply = _totalSupply(); - assert(delta <= currentSupply, 'Total supply underflow'); - const newSupply = currentSupply - delta; - Storage.set(TOTAL_SUPPLY_KEY, u64ToBytes(newSupply)); + assert(u256.le(delta, currentSupply), 'Total supply underflow'); + const newSupply = u256.sub(currentSupply, delta); + Storage.set(TOTAL_SUPPLY_KEY, u256ToBytes(newSupply)); } /* -------------------------------------------------------------------------- */ @@ -85,8 +85,8 @@ export function _getOwnedTokensKeyPrefix(owner: string): StaticArray { * @param owner - The owner's address. * @param tokenId - The token ID to add. */ -function _addTokenToOwnerEnumeration(owner: string, tokenId: u64): void { - const key = _getOwnedTokensKeyPrefix(owner).concat(u64ToBytes(tokenId)); +function _addTokenToOwnerEnumeration(owner: string, tokenId: u256): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); Storage.set(key, []); } @@ -95,8 +95,8 @@ function _addTokenToOwnerEnumeration(owner: string, tokenId: u64): void { * @param owner - The owner's address. * @param tokenId - The token ID to remove. */ -function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u64): void { - const key = _getOwnedTokensKeyPrefix(owner).concat(u64ToBytes(tokenId)); +function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u256): void { + const key = _getOwnedTokensKeyPrefix(owner).concat(u256ToBytes(tokenId)); Storage.del(key); } @@ -110,13 +110,13 @@ function _removeTokenFromOwnerEnumeration(owner: string, tokenId: u64): void { * @param tokenId - The token ID. * @param auth - The address authorized to perform the update. */ -export function _update(to: string, tokenId: u64, auth: string): void { +export function _update(to: string, tokenId: u256, auth: string): void { const previousOwner = _updateBase(to, tokenId, auth); // Mint if (previousOwner == '') { _addTokenToOwnerEnumeration(to, tokenId); - _increaseTotalSupply(1); + _increaseTotalSupply(u256.One); } else { // Transfer if (to != '' && to != previousOwner) { @@ -126,7 +126,7 @@ export function _update(to: string, tokenId: u64, auth: string): void { // Burn else if (to == '') { _removeTokenFromOwnerEnumeration(previousOwner, tokenId); - _decreaseTotalSupply(1); + _decreaseTotalSupply(u256.One); } } } @@ -141,7 +141,7 @@ export function _update(to: string, tokenId: u64, auth: string): void { * @param to - The new owner's address. * @param tokenId - The token ID to transfer. */ -export function _transferFrom(from: string, to: string, tokenId: u64): void { +export function _transferFrom(from: string, to: string, tokenId: u256): void { assert( _isAuthorized(Context.caller().toString(), tokenId), 'Unauthorized caller', diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts index bf5c90e..dd145d7 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-example.spec.ts @@ -20,11 +20,11 @@ import { } from '../NFT-example'; import { Args, - NoArg, byteToBool, bytesToString, - bytesToU64, + bytesToU256, } from '@massalabs/as-types'; +import { u256 } from 'as-bignum/assembly'; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; @@ -35,7 +35,7 @@ const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const newOwner = 'AU12F7y3PWpw72XcwhSksJztRiTSqAvLxaLacP2qDYhNUEfEXuG4T'; const zeroAddress = ''; -const tokenId: u64 = 1; +const tokenId = u256.One; function switchUser(user: string): void { changeCallStack(user + ' , ' + tokenAddress); @@ -45,7 +45,7 @@ beforeEach(() => { resetStorage(); switchUser(contractOwner); setDeployContext(contractOwner); - constructor(NoArg.serialize()); + constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); }); describe('Initialization', () => { @@ -67,9 +67,9 @@ describe('Minting', () => { expect(bytesToString(ownerOf(new Args().add(tokenId).serialize()))).toBe( to, ); - expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( - 1, - ); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); }); throws('Minting from not owner should fail', () => { switchUser(from); @@ -84,23 +84,23 @@ describe('Minting', () => { }); test('Mint multiple tokens to an address', () => { mint(new Args().add(to).add(tokenId).serialize()); - mint(new Args().add(to).add(u64(2)).serialize()); - expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( - 2, - ); + mint(new Args().add(to).add(new u256(2)).serialize()); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(new u256(2)); }); test('Mint multiple tokens to different addresses', () => { mint(new Args().add(to).add(tokenId).serialize()); - mint(new Args().add(from).add(u64(2)).serialize()); - expect(bytesToU64(balanceOf(new Args().add(to).serialize()))).toStrictEqual( - 1, - ); - expect(bytesToString(ownerOf(new Args().add(u64(2)).serialize()))).toBe( - from, - ); + mint(new Args().add(from).add(new u256(2)).serialize()); + expect( + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); + expect( + bytesToString(ownerOf(new Args().add(new u256(2)).serialize())), + ).toBe(from); expect( - bytesToU64(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(1); + bytesToU256(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(u256.One); expect(bytesToString(ownerOf(new Args().add(tokenId).serialize()))).toBe( to, ); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts index 34c89be..390be02 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFT-internals.spec.ts @@ -4,6 +4,7 @@ import { setDeployContext, } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; import { _approve, _balanceOf, @@ -26,7 +27,7 @@ const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const newOwner = 'AU12F7y3PWpw72XcwhSksJztRiTSqAvLxaLacP2qDYhNUEfEXuG4T'; const zeroAddress = ''; -const tokenId: u64 = 1; +const tokenId = u256.One; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; @@ -124,10 +125,10 @@ describe('Transferring NFTs', () => { expect(ownerOfToken).toBe(newOwner); const balanceOfNewOwner = _balanceOf(newOwner); - expect(balanceOfNewOwner).toBe(1); + expect(balanceOfNewOwner).toBe(u256.One); const balanceOfOldOwner = _balanceOf(from); - expect(balanceOfOldOwner).toBe(0); + expect(balanceOfOldOwner).toBe(u256.Zero); }); throws('Transferring a non-existent token should fail', () => { _transferFrom(from, to, tokenId); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts index 8c20109..ced9c4b 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts @@ -6,11 +6,11 @@ import { import { Args, - NoArg, byteToBool, bytesToString, - bytesToU64, + bytesToU256, } from '@massalabs/as-types'; +import { u256 } from 'as-bignum/assembly'; import { approve, balanceOf, @@ -38,7 +38,13 @@ const from = 'AU12CzoKEASaeBHnxGLnHDG2u73dLzWWfgvW6bc4L1UfMA5Uc5Fg7'; const to = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const approved = 'AU1sF3HSa7fcBoE12bE1Eq2ohKqcRPBHuNRmdqAMfw8WEkHCU3aF'; const zeroAddress = ''; -const tokenIds: u64[] = [1, 2, 3, 4, 5]; +const tokenIds = [ + u256.One, + u256.fromU32(2), + u256.fromU32(3), + u256.fromU32(4), + u256.fromU32(5), +]; function switchUser(user: string): void { changeCallStack(user + ' , ' + tokenAddress); @@ -48,7 +54,7 @@ beforeEach(() => { resetStorage(); switchUser(contractOwner); setDeployContext(contractOwner); - constructor(NoArg.serialize()); + constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); }); describe('NFT Enumerable Contract', () => { @@ -69,24 +75,24 @@ describe('NFT Enumerable Contract', () => { bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), ).toBe(to); expect( - bytesToU64(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(1); - expect(bytesToU64(totalSupply([]))).toStrictEqual(1); + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); }); it('should mint multiple tokens to different addresses', () => { mint(new Args().add(to).add(tokenIds[0]).serialize()); mint(new Args().add(from).add(tokenIds[1]).serialize()); expect( - bytesToU64(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(1); + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); expect( - bytesToU64(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(1); - expect(bytesToU64(totalSupply([]))).toStrictEqual(u64(2)); + bytesToU256(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(u256.One); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); }); - it('should mint to an invalid address', () => { + it('should not mint to an invalid address', () => { expect(() => { mint(new Args().add(zeroAddress).add(tokenIds[0]).serialize()); }).toThrow('Unauthorized to'); @@ -158,11 +164,11 @@ describe('NFT Enumerable Contract', () => { bytesToString(ownerOf(new Args().add(tokenIds[0]).serialize())), ).toBe(to); expect( - bytesToU64(balanceOf(new Args().add(to).serialize())), - ).toStrictEqual(1); + bytesToU256(balanceOf(new Args().add(to).serialize())), + ).toStrictEqual(u256.One); expect( - bytesToU64(balanceOf(new Args().add(from).serialize())), - ).toStrictEqual(0); + bytesToU256(balanceOf(new Args().add(from).serialize())), + ).toStrictEqual(u256.Zero); }); it('should transfer approved token', () => { @@ -206,7 +212,7 @@ describe('NFT Enumerable Contract', () => { [], ); - expect(bytesToU64(totalSupply([]))).toStrictEqual(0); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); }); it('should burn a token with approval', () => { @@ -218,7 +224,7 @@ describe('NFT Enumerable Contract', () => { expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( [], ); - expect(bytesToU64(totalSupply([]))).toStrictEqual(0); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); }); it('should burn a token using approval for all', () => { @@ -230,7 +236,7 @@ describe('NFT Enumerable Contract', () => { expect(ownerOf(new Args().add(tokenIds[0]).serialize())).toStrictEqual( [], ); - expect(bytesToU64(totalSupply([]))).toStrictEqual(0); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); }); it('should not burn token without approval', () => { @@ -244,11 +250,11 @@ describe('NFT Enumerable Contract', () => { describe('Enumeration', () => { it('should return correct total supply', () => { - expect(bytesToU64(totalSupply([]))).toStrictEqual(0); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.Zero); mint(new Args().add(from).add(tokenIds[0]).serialize()); - expect(bytesToU64(totalSupply([]))).toStrictEqual(1); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); mint(new Args().add(to).add(tokenIds[1]).serialize()); - expect(bytesToU64(totalSupply([]))).toStrictEqual(2); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.fromU32(2)); }); it('should return correct tokens owned by an address', () => { @@ -298,7 +304,7 @@ describe('NFT Enumerable Contract', () => { expect(fromTokens).toContainEqual(tokenIds[1]); // Total supply should be updated - expect(bytesToU64(totalSupply([]))).toStrictEqual(1); + expect(bytesToU256(totalSupply([]))).toStrictEqual(u256.One); }); }); }); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts index d20e474..c3a6318 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-internals.spec.ts @@ -1,4 +1,5 @@ import { resetStorage, setDeployContext } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; import { _update, _balanceOf, @@ -15,20 +16,26 @@ const caller = 'A12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'; const owner1 = caller; const owner2 = 'AU178qZCfaNXkz9tQiXJcVfAEnYGJ27UoNtFFJh3BiT8jTfY8P2D'; const zeroAddress = ''; -const tokenIds: u64[] = [1, 2, 3, 4, 5]; +const tokenIds = [ + u256.fromU32(1), + u256.fromU32(2), + u256.fromU32(3), + u256.fromU32(4), + u256.fromU32(5), +]; const NFTName = 'MASSA_NFT'; const NFTSymbol = 'NFT'; -function mint(to: string, tokenId: u64): void { +function mint(to: string, tokenId: u256): void { _update(to, tokenId, zeroAddress); } -function transfer(from: string, to: string, tokenId: u64): void { +function transfer(from: string, to: string, tokenId: u256): void { _update(to, tokenId, from); } -function burn(owner: string, tokenId: u64): void { +function burn(owner: string, tokenId: u256): void { _update(zeroAddress, tokenId, owner); } @@ -41,59 +48,59 @@ describe('NFT Enumerable Internals', () => { describe('Initialization', () => { it('should have zero total supply initially', () => { - expect(_totalSupply()).toStrictEqual(0); + expect(_totalSupply()).toStrictEqual(u256.Zero); }); }); describe('Total Supply Management', () => { it('should update total supply when token is minted', () => { mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(1); + expect(_totalSupply()).toStrictEqual(u256.One); }); it('should update total supply when token is burned', () => { mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(1); + expect(_totalSupply()).toStrictEqual(u256.One); burn(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(0); + expect(_totalSupply()).toStrictEqual(u256.Zero); }); - it('should not allow total supply to exceed u64.Max', () => { - // Set total supply to u64.Max - 1 - const nearMaxSupply = u64.MAX_VALUE - 1; - _increaseTotalSupply(nearMaxSupply); + it('should not allow total supply to exceed u256.Max', () => { + // Set total supply to u256.Max - 1 + const nearMaxSupply = u256.sub(u256.Max, u256.One); + _increaseTotalSupply(u256.sub(u256.Max, u256.One)); expect(_totalSupply()).toStrictEqual(nearMaxSupply); - // Mint one more token should succeed (totalSupply = u64.Max) + // Mint one more token should succeed (totalSupply = u256.Max) mint(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(u64.MAX_VALUE); + expect(_totalSupply()).toStrictEqual(u256.Max); // Minting another token should fail due to overflow expect(() => { - _increaseTotalSupply(1); + _increaseTotalSupply(u256.One); }).toThrow('Total supply overflow'); // Ensure your contract throws this exact error }); it('should not allow total supply to underflow', () => { // Ensure total supply is zero - expect(_totalSupply()).toStrictEqual(0); + expect(_totalSupply()).toStrictEqual(u256.Zero); // Attempt to decrease supply by 1 should fail expect(() => { - _decreaseTotalSupply(1); + _decreaseTotalSupply(u256.One); }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error // Set total supply to 1 - _increaseTotalSupply(1); - expect(_totalSupply()).toStrictEqual(1); + _increaseTotalSupply(u256.One); + expect(_totalSupply()).toStrictEqual(u256.One); // Decrease supply by 1 should succeed - _decreaseTotalSupply(1); - expect(_totalSupply()).toStrictEqual(0); + _decreaseTotalSupply(u256.One); + expect(_totalSupply()).toStrictEqual(u256.Zero); // Attempt to decrease supply by another 1 should fail expect(() => { - _decreaseTotalSupply(1); + _decreaseTotalSupply(u256.One); }).toThrow('Total supply underflow'); // Ensure your contract throws this exact error }); }); @@ -104,8 +111,8 @@ describe('NFT Enumerable Internals', () => { mint(owner1, tokenIds[1]); mint(owner2, tokenIds[2]); - expect(_balanceOf(owner1)).toStrictEqual(2); - expect(_balanceOf(owner2)).toStrictEqual(1); + expect(_balanceOf(owner1)).toStrictEqual(u256.fromU32(2)); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); const owner1Tokens = getOwnedTokens(owner1); expect(owner1Tokens.length).toBe(2); @@ -123,8 +130,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); - expect(_balanceOf(owner1)).toStrictEqual(1); - expect(_balanceOf(owner2)).toStrictEqual(1); + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); // Verify ownership expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); @@ -147,8 +154,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); - expect(_balanceOf(owner1)).toStrictEqual(0); - expect(_balanceOf(owner2)).toStrictEqual(1); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.One); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -166,8 +173,8 @@ describe('NFT Enumerable Internals', () => { transfer(owner1, owner2, tokenIds[0]); expect(_ownerOf(tokenIds[0])).toStrictEqual(owner2); - expect(_balanceOf(owner1)).toStrictEqual(0); - expect(_balanceOf(owner2)).toStrictEqual(2); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -187,11 +194,11 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); - expect(_balanceOf(owner1)).toStrictEqual(1); - expect(_totalSupply()).toStrictEqual(1); + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_totalSupply()).toStrictEqual(u256.One); - // Verify that accessing the burned token's owner returns zero address - expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); + // Verify that accessing the burned token's owner returns empty string + expect(_ownerOf(tokenIds[0])).toStrictEqual(''); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -206,8 +213,8 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); burn(owner1, tokenIds[1]); - expect(_balanceOf(owner1)).toStrictEqual(0); - expect(_totalSupply()).toStrictEqual(0); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_totalSupply()).toStrictEqual(u256.Zero); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -229,18 +236,18 @@ describe('NFT Enumerable Internals', () => { burn(owner1, tokenIds[0]); // Verify total supply - expect(_totalSupply()).toStrictEqual(2); + expect(_totalSupply()).toStrictEqual(u256.fromU32(2)); // Verify balances - expect(_balanceOf(owner1)).toStrictEqual(0); - expect(_balanceOf(owner2)).toStrictEqual(2); + expect(_balanceOf(owner1)).toStrictEqual(u256.Zero); + expect(_balanceOf(owner2)).toStrictEqual(u256.fromU32(2)); // Verify ownership expect(_ownerOf(tokenIds[1])).toStrictEqual(owner2); expect(_ownerOf(tokenIds[2])).toStrictEqual(owner2); - // Verify that accessing the burned token's owner returns zero address - expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); + // Verify that accessing the burned token's owner returns empty string + expect(_ownerOf(tokenIds[0])).toStrictEqual(''); // Verify owned tokens const owner1Tokens = getOwnedTokens(owner1); @@ -268,7 +275,7 @@ describe('NFT Enumerable Internals', () => { }).toThrow('Nonexistent token'); }); - it('should not transfer to zero address', () => { + it('should not transfer to an invalid address', () => { mint(owner1, tokenIds[0]); expect(() => { @@ -286,7 +293,7 @@ describe('NFT Enumerable Internals', () => { mint(owner1, tokenIds[0]); burn(owner1, tokenIds[0]); - expect(_totalSupply()).toStrictEqual(0); + expect(_totalSupply()).toStrictEqual(u256.Zero); // Check token ownership has been cleared expect(_ownerOf(tokenIds[0])).toStrictEqual(zeroAddress); @@ -296,14 +303,14 @@ describe('NFT Enumerable Internals', () => { expect(owner1Tokens.length).toBe(0); }); - it('should mint a token with id=u64.Max', () => { - mint(owner1, u64.MAX_VALUE); - expect(_totalSupply()).toStrictEqual(1); - expect(_balanceOf(owner1)).toStrictEqual(1); - expect(_ownerOf(u64.MAX_VALUE)).toStrictEqual(owner1); + it('should mint a token with id=u256.Max', () => { + mint(owner1, u256.Max); + expect(_totalSupply()).toStrictEqual(u256.One); + expect(_balanceOf(owner1)).toStrictEqual(u256.One); + expect(_ownerOf(u256.Max)).toStrictEqual(owner1); const owner1Tokens = getOwnedTokens(owner1); expect(owner1Tokens.length).toBe(1); - expect(owner1Tokens).toContainEqual(u64.MAX_VALUE); + expect(owner1Tokens).toContainEqual(u256.Max); }); }); }); diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts index f54a304..83ac61d 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/helpers.ts @@ -1,24 +1,27 @@ -import { bytesToU64 } from '@massalabs/as-types'; +import { bytesToU256 } from '@massalabs/as-types'; import { getKeys } from '@massalabs/massa-as-sdk'; +import { u256 } from 'as-bignum/assembly'; import { _getOwnedTokensKeyPrefix } from '../NFTEnumerable-internals'; +const SIZE_OF_U256 = 32; + /** * Returns the all the tokens owned by a specific address. * * @param owner - The address of the owner. * - * @returns An array of u64 representing the tokens owned by the address. + * @returns An array of u256 representing the tokens owned by the address. * */ -export function getOwnedTokens(owner: string): u64[] { - const tokens: u64[] = []; +export function getOwnedTokens(owner: string): u256[] { + const tokens: u256[] = []; const prefix = _getOwnedTokensKeyPrefix(owner); const keys = getKeys(prefix); for (let i = 0; i < keys.length; i++) { - const tokenIdBytesArray = keys[i].slice(keys[i].length - sizeof()); + const tokenIdBytesArray = keys[i].slice(keys[i].length - SIZE_OF_U256); const tokenIdBytes = StaticArray.fromArray(tokenIdBytesArray); - const tokenId = bytesToU64(tokenIdBytes); + const tokenId = bytesToU256(tokenIdBytes); tokens.push(tokenId); } diff --git a/smart-contracts/assembly/contracts/NFT/y.ts b/smart-contracts/assembly/contracts/NFT/y.ts new file mode 100644 index 0000000..d7d80ee --- /dev/null +++ b/smart-contracts/assembly/contracts/NFT/y.ts @@ -0,0 +1,137 @@ +import { + stringToBytes, + Args, + u256ToBytes, + boolToByte, +} from '@massalabs/as-types'; +import { Storage, Context } from '@massalabs/massa-as-sdk'; +import { _isOwner } from '../utils/ownership-internal'; +import { + _name, + _symbol, + _balanceOf, + _ownerOf, + _transferFrom, + _approve, + _setApprovalForAll, + _getApproved, + _isApprovedForAll, +} from './NFT-internals'; + +/** + * Get the name of the NFT collection + * @returns name of the NFT collection + */ +export function name(): StaticArray { + return stringToBytes(_name()); +} + +/** + * Get the symbol of the NFT collection + * @returns symbol of the NFT collection + */ +export function symbol(): StaticArray { + return stringToBytes(_symbol()); +} + +/** + * Returns the number of tokens owned by the address + * @param binaryArgs - (address: string) + * @returns Number of tokens owned by the address in u256 as bytes + */ +export function balanceOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const address = args + .nextString() + .expect('address argument is missing or invalid'); + return u256ToBytes(_balanceOf(address)); +} + +/** + * Get the owner of the token + * @param binaryArgs - (tokenId: u256) + * @returns Address of the owner of the token + */ +export function ownerOf(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + const owner = _ownerOf(tokenId); + if (owner == '') { + throw new Error('Token id not found'); + } + return stringToBytes(owner); +} + +/** + * Transfer token from one address to another + * @param binaryArgs - (from: string, to: string, tokenId: u256) + */ +export function transferFrom(binaryArgs: StaticArray): void { + if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { + throw new Error('Transfer is locked'); + } + const args = new Args(binaryArgs); + const from = args.nextString().expect('from argument is missing or invalid'); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _transferFrom(from, to, tokenId); +} + +/** + * Approve the address to transfer the token + * @param binaryArgs - (to: string, tokenId: u256) + */ +export function approve(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + _approve(to, tokenId); +} + +/** + * Set approval for all tokens of the owner + * @param binaryArgs - (to: string, approved: bool) + */ +export function setApprovalForAll(binaryArgs: StaticArray): void { + const args = new Args(binaryArgs); + const to = args.nextString().expect('to argument is missing or invalid'); + const approved = args + .nextBool() + .expect('approved argument is missing or invalid'); + _setApprovalForAll(to, approved); +} + +/** + * Get the address approved to transfer the token or empty address if none + * @param binaryArgs - (tokenId: u256) + * @returns Address of the approved address + */ +export function getApproved(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const tokenId = args + .nextU256() + .expect('tokenId argument is missing or invalid'); + return stringToBytes(_getApproved(tokenId)); +} + +/** + * Returns if the operator is approved to transfer the tokens of the owner + * @param binaryArgs - (owner: string, operator: string) + * @returns Bool as bytes + */ +export function isApprovedForAll(binaryArgs: StaticArray): StaticArray { + const args = new Args(binaryArgs); + const owner = args + .nextString() + .expect('owner argument is missing or invalid'); + const operator = args + .nextString() + .expect('operator argument is missing or invalid'); + return boolToByte(_isApprovedForAll(owner, operator)); +} From 78a3a827322d4c4f051acf1f9c8b3aff1c2a8673 Mon Sep 17 00:00:00 2001 From: BenRey Date: Thu, 5 Dec 2024 15:04:33 +0100 Subject: [PATCH 5/5] Refactor NFT contract ownership management and update constructor initialization --- .../assembly/contracts/NFT/NFT-example.ts | 5 +- .../__tests__/NFTEnumerable-example.spec.ts | 8 +- smart-contracts/assembly/contracts/NFT/y.ts | 137 ------------------ 3 files changed, 8 insertions(+), 142 deletions(-) delete mode 100644 smart-contracts/assembly/contracts/NFT/y.ts diff --git a/smart-contracts/assembly/contracts/NFT/NFT-example.ts b/smart-contracts/assembly/contracts/NFT/NFT-example.ts index d4ea73a..0239551 100644 --- a/smart-contracts/assembly/contracts/NFT/NFT-example.ts +++ b/smart-contracts/assembly/contracts/NFT/NFT-example.ts @@ -30,9 +30,10 @@ import { _update, _transferFrom, } from './NFT-internals'; -import { setOwner, onlyOwner } from '../utils/ownership'; +import { onlyOwner } from '../utils/ownership'; import { Context, isDeployingContract } from '@massalabs/massa-as-sdk'; +import { _setOwner } from '../utils/ownership-internal'; /** * @param binaryArgs - serialized strings representing the name and the symbol of the NFT @@ -52,7 +53,7 @@ export function constructor(binaryArgs: StaticArray): void { .nextString() .expect('symbol argument is missing or invalid'); _constructor(name, symbol); - setOwner(new Args().add(Context.caller().toString()).serialize()); + _setOwner(Context.caller().toString()); } export function name(): string { diff --git a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts index ced9c4b..6f8d323 100644 --- a/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts +++ b/smart-contracts/assembly/contracts/NFT/__tests__/NFTEnumerable-example.spec.ts @@ -6,9 +6,11 @@ import { import { Args, + NoArg, byteToBool, bytesToString, bytesToU256, + stringToBytes, } from '@massalabs/as-types'; import { u256 } from 'as-bignum/assembly'; import { @@ -54,14 +56,14 @@ beforeEach(() => { resetStorage(); switchUser(contractOwner); setDeployContext(contractOwner); - constructor(new Args().add(NFTName).add(NFTSymbol).serialize()); + constructor(NoArg.serialize()); }); describe('NFT Enumerable Contract', () => { describe('Initialization', () => { it('should return correct name and symbol', () => { - expect(name()).toBe(NFTName); - expect(symbol()).toBe(NFTSymbol); + expect(name()).toStrictEqual(stringToBytes(NFTName)); + expect(symbol()).toStrictEqual(stringToBytes(NFTSymbol)); }); it('should return correct contract owner', () => { expect(bytesToString(ownerAddress([]))).toBe(contractOwner); diff --git a/smart-contracts/assembly/contracts/NFT/y.ts b/smart-contracts/assembly/contracts/NFT/y.ts deleted file mode 100644 index d7d80ee..0000000 --- a/smart-contracts/assembly/contracts/NFT/y.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - stringToBytes, - Args, - u256ToBytes, - boolToByte, -} from '@massalabs/as-types'; -import { Storage, Context } from '@massalabs/massa-as-sdk'; -import { _isOwner } from '../utils/ownership-internal'; -import { - _name, - _symbol, - _balanceOf, - _ownerOf, - _transferFrom, - _approve, - _setApprovalForAll, - _getApproved, - _isApprovedForAll, -} from './NFT-internals'; - -/** - * Get the name of the NFT collection - * @returns name of the NFT collection - */ -export function name(): StaticArray { - return stringToBytes(_name()); -} - -/** - * Get the symbol of the NFT collection - * @returns symbol of the NFT collection - */ -export function symbol(): StaticArray { - return stringToBytes(_symbol()); -} - -/** - * Returns the number of tokens owned by the address - * @param binaryArgs - (address: string) - * @returns Number of tokens owned by the address in u256 as bytes - */ -export function balanceOf(binaryArgs: StaticArray): StaticArray { - const args = new Args(binaryArgs); - const address = args - .nextString() - .expect('address argument is missing or invalid'); - return u256ToBytes(_balanceOf(address)); -} - -/** - * Get the owner of the token - * @param binaryArgs - (tokenId: u256) - * @returns Address of the owner of the token - */ -export function ownerOf(binaryArgs: StaticArray): StaticArray { - const args = new Args(binaryArgs); - const tokenId = args - .nextU256() - .expect('tokenId argument is missing or invalid'); - const owner = _ownerOf(tokenId); - if (owner == '') { - throw new Error('Token id not found'); - } - return stringToBytes(owner); -} - -/** - * Transfer token from one address to another - * @param binaryArgs - (from: string, to: string, tokenId: u256) - */ -export function transferFrom(binaryArgs: StaticArray): void { - if (Storage.has(buildLockedKey()) && !_isOwner(Context.caller().toString())) { - throw new Error('Transfer is locked'); - } - const args = new Args(binaryArgs); - const from = args.nextString().expect('from argument is missing or invalid'); - const to = args.nextString().expect('to argument is missing or invalid'); - const tokenId = args - .nextU256() - .expect('tokenId argument is missing or invalid'); - _transferFrom(from, to, tokenId); -} - -/** - * Approve the address to transfer the token - * @param binaryArgs - (to: string, tokenId: u256) - */ -export function approve(binaryArgs: StaticArray): void { - const args = new Args(binaryArgs); - const to = args.nextString().expect('to argument is missing or invalid'); - const tokenId = args - .nextU256() - .expect('tokenId argument is missing or invalid'); - _approve(to, tokenId); -} - -/** - * Set approval for all tokens of the owner - * @param binaryArgs - (to: string, approved: bool) - */ -export function setApprovalForAll(binaryArgs: StaticArray): void { - const args = new Args(binaryArgs); - const to = args.nextString().expect('to argument is missing or invalid'); - const approved = args - .nextBool() - .expect('approved argument is missing or invalid'); - _setApprovalForAll(to, approved); -} - -/** - * Get the address approved to transfer the token or empty address if none - * @param binaryArgs - (tokenId: u256) - * @returns Address of the approved address - */ -export function getApproved(binaryArgs: StaticArray): StaticArray { - const args = new Args(binaryArgs); - const tokenId = args - .nextU256() - .expect('tokenId argument is missing or invalid'); - return stringToBytes(_getApproved(tokenId)); -} - -/** - * Returns if the operator is approved to transfer the tokens of the owner - * @param binaryArgs - (owner: string, operator: string) - * @returns Bool as bytes - */ -export function isApprovedForAll(binaryArgs: StaticArray): StaticArray { - const args = new Args(binaryArgs); - const owner = args - .nextString() - .expect('owner argument is missing or invalid'); - const operator = args - .nextString() - .expect('operator argument is missing or invalid'); - return boolToByte(_isApprovedForAll(owner, operator)); -}