Skip to content

Commit

Permalink
Refactor NFT contract to use u64 instead of u256 for token IDs and ba…
Browse files Browse the repository at this point in the history
…lances
  • Loading branch information
Ben-Rey committed Dec 4, 2024
1 parent 900be13 commit b3057d5
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 222 deletions.
36 changes: 17 additions & 19 deletions smart-contracts/assembly/contracts/NFT/NFT-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Args,
boolToByte,
stringToBytes,
u256ToBytes,
u64ToBytes,
} from '@massalabs/as-types';
import {
_approve,
Expand All @@ -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
*
Expand All @@ -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<u8>): void {
export function constructor(_: StaticArray<u8>): 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());
}

Expand All @@ -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.
*/
Expand All @@ -75,31 +73,31 @@ export function balanceOf(binaryArgs: StaticArray<u8>): StaticArray<u8> {
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<u8>): StaticArray<u8> {
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<u8>): StaticArray<u8> {
const args = new Args(binaryArgs);
const tokenId = args
.nextU256()
.nextU64()
.expect('tokenId argument is missing or invalid');
return stringToBytes(_getApproved(tokenId));
}
Expand Down Expand Up @@ -132,7 +130,7 @@ export function approve(binaryArgs: StaticArray<u8>): 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);
}
Expand Down Expand Up @@ -165,7 +163,7 @@ export function transferFrom(binaryArgs: StaticArray<u8>): 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);
}
Expand All @@ -190,14 +188,14 @@ export function mint(binaryArgs: StaticArray<u8>): 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.
Expand All @@ -211,7 +209,7 @@ export function mint(binaryArgs: StaticArray<u8>): void {
export function burn(binaryArgs: StaticArray<u8>): void {
const args = new Args(binaryArgs);
const tokenId = args
.nextU256()
.nextU64()
.expect('tokenId argument is missing or invalid');
_update('', tokenId, '');
}
Expand Down
46 changes: 22 additions & 24 deletions smart-contracts/assembly/contracts/NFT/NFT-internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = [0x01];
export const SYMBOL_KEY: StaticArray<u8> = [0x02];

Expand Down Expand Up @@ -62,16 +60,16 @@ export function balanceKey(address: string): StaticArray<u8> {
* @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<u8> {
return OWNER_KEY_PREFIX.concat(u256ToBytes(tokenId));
export function ownerKey(tokenId: u64): StaticArray<u8> {
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<u8> {
return ALLOWANCE_KEY_PREFIX.concat(u256ToBytes(tokenId));
function allowanceKey(tokenId: u64): StaticArray<u8> {
return ALLOWANCE_KEY_PREFIX.concat(u64ToBytes(tokenId));
}

/**
Expand All @@ -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;
}

/**
Expand All @@ -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)) : '';
}
Expand Down Expand Up @@ -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 != ''
Expand All @@ -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)) : '';
}
Expand All @@ -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
Expand Down Expand Up @@ -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) ||
Expand All @@ -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 != '') {
Expand All @@ -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()));
Expand All @@ -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',
Expand Down
Loading

0 comments on commit b3057d5

Please sign in to comment.