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));
-}