Skip to content

Commit

Permalink
add MRC1155
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah committed Nov 6, 2024
1 parent 81ec0dc commit 539f441
Show file tree
Hide file tree
Showing 19 changed files with 1,991 additions and 0 deletions.
47 changes: 47 additions & 0 deletions smart-contracts/assembly/contracts/MRC1155/NFT-exemple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Args, stringToBytes } from '@massalabs/as-types';
import { u256 } from 'as-bignum/assembly';
import * as token from './token';
import * as mint from './mintable/mint';
import { Context } from '@massalabs/massa-as-sdk';
import { grantRole } from '../utils/accessControl';
export * from './burnable/burn';
export * from './mintable/mint';
export * from '../utils/accessControl';
export * from '../utils/ownership';

export function constructor(binaryArgs: StaticArray<u8>): void {
const args = new Args(binaryArgs);
const uri = args.nextString().expect('uri argument is missing or invalid');
const ids = args
.nextFixedSizeArray<u256>()
.expect('ids argument is missing or invalid');
const amounts = args
.nextFixedSizeArray<u256>()
.expect('amounts argument is missing or invalid');
token.constructor(new Args().add(uri).serialize());
grantRole(
new Args()
.add(mint.MINTER_ROLE)
.add(Context.caller().toString())
.serialize(),
);
mint.mintBatch(
new Args()
.add(Context.caller().toString())
.add(ids)
.add(amounts)
.add(stringToBytes(''))
.serialize(),
);
}

// export everything from the token module except the constructor
export {
uri,
balanceOf,
balanceOfBatch,
setApprovalForAll,
isApprovedForAll,
safeTransferFrom,
safeBatchTransferFrom,
} from './token';
56 changes: 56 additions & 0 deletions smart-contracts/assembly/contracts/MRC1155/OnERC1155Received.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Args } from '@massalabs/as-types';
import { createEvent, generateEvent } from '@massalabs/massa-as-sdk';

import { u256 } from 'as-bignum/assembly';

export function onERC1155BatchReceived(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const operator = args
.nextString()
.expect('operator argument is missing or invalid');
const from = args.nextString().expect('from argument is missing or invalid');
const ids = args
.nextFixedSizeArray<u256>()
.expect('ids argument is missing or invalid');
const values = args
.nextFixedSizeArray<u256>()
.expect('values argument is missing or invalid');
const data = args.nextBytes().expect('data argument is missing or invalid');

generateEvent(
createEvent('ERC1155BatchReceived', [
operator,
from,
ids.map<string>((id: u256) => id.toString()).join(';'),
values.map<string>((id: u256) => id.toString()).join(';'),
data.toString(),
]),
);
return new Args().add('bc197c81').serialize();
}

export function onERC1155Received(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const operator = args
.nextString()
.expect('operator argument is missing or invalid');
const from = args.nextString().expect('from argument is missing or invalid');
const id = args.nextU256().expect('id argument is missing or invalid');
const value = args.nextU256().expect('value argument is missing or invalid');
const data = args.nextBytes().expect('data argument is missing or invalid');

generateEvent(
createEvent('ERC1155Received', [
operator,
from,
id.toString(),
value.toString(),
data.toString(),
]),
);
return new Args().add('f23a6e61').serialize();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Args } from '@massalabs/as-types';
import { createEvent, generateEvent } from '@massalabs/massa-as-sdk';

import { u256 } from 'as-bignum/assembly';

export function onERC1155BatchReceived(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const operator = args
.nextString()
.expect('operator argument is missing or invalid');
const from = args.nextString().expect('from argument is missing or invalid');
const ids = args
.nextFixedSizeArray<u256>()
.expect('ids argument is missing or invalid');
const values = args
.nextFixedSizeArray<u256>()
.expect('values argument is missing or invalid');
const data = args.nextBytes().expect('data argument is missing or invalid');

generateEvent(
createEvent('ERC1155BatchReceived', [
operator,
from,
ids.map<string>((id: u256) => id.toString()).join(';'),
values.map<string>((id: u256) => id.toString()).join(';'),
data.toString(),
]),
);
return new Args().add('wrong').serialize();
}

export function onERC1155Received(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const operator = args
.nextString()
.expect('operator argument is missing or invalid');
const from = args.nextString().expect('from argument is missing or invalid');
const id = args.nextU256().expect('id argument is missing or invalid');
const value = args.nextU256().expect('value argument is missing or invalid');
const data = args.nextBytes().expect('data argument is missing or invalid');

generateEvent(
createEvent('ERC1155Received', [
operator,
from,
id.toString(),
value.toString(),
data.toString(),
]),
);
return new Args().add('wrong').serialize();
}
22 changes: 22 additions & 0 deletions smart-contracts/assembly/contracts/MRC1155/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# MRC1155

This repository contains a set of smart contracts to implement the ERC1155 standard on the Massa blockchain.
see [ERC1155 documentation](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC1155)

On top of that it also includes multiples extensions to the standard to allow for more complex use cases:
- [burnable](./assembly/contracts/burnable.sol)
- [mintable](./assembly/contracts/mintable.sol)
- [metadata](./assembly/contracts/metadata.sol)

It can be easily merged into massa-standards as this repository contains a set of smart contracts that are fully compatible with the ERC1155 standard with the only common depencies being ownership contract.

## Documentation

A documentation for each functions internals or externals has been created that can be found just before the functions declarations.

## Unit tests

A big set of unit tests has been written to ensure the correctness of the smart contracts compared to the standard and some security related checks.

The only missing coverage is for the call of the ERC1155Receiver function which cannot be tested with the current mocked tests environment.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@as-pect/assembly/types/as-pect" />
205 changes: 205 additions & 0 deletions smart-contracts/assembly/contracts/MRC1155/__tests__/burnable.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import {
changeCallStack,
resetStorage,
setDeployContext,
} from '@massalabs/massa-as-sdk';
import {
Args,
stringToBytes,
u256ToBytes,
fixedSizeArrayToBytes,
boolToByte,
} from '@massalabs/as-types';
import {
balanceOf,
balanceOfBatch,
constructor,
setApprovalForAll,
} from '../token';
import { u256 } from 'as-bignum/assembly';
import { _mint, _mintBatch } from '../token-internal';
import { burn, burnBatch } from '..';

// address of the contract set in vm-mock. must match with contractAddr of @massalabs/massa-as-sdk/vm-mock/vm.js
const contractAddr = 'AS12BqZEQ6sByhRLyEuf0YbQmcF2PsDdkNNG1akBJu9XcjZA1eT';

const user1Address = 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq';

const user2Address = 'AU12BqZEQ6sByhRLyEuf0YbQmcF2PsDdkNNG1akBJu9XcjZA1e8';

const TOKEN_URI = 'ipfs://QmW77ZQQ7Jm9q8WuLbH8YZg2K7T9Qnjbzm7jYVQQrJY5Yd';

function switchUser(user: string): void {
changeCallStack(user + ' , ' + contractAddr);
}

beforeEach(() => {
switchUser(user1Address);
resetStorage();
setDeployContext(user1Address);
constructor(new Args().add(stringToBytes(TOKEN_URI)).serialize());
});

describe('burn', () => {
test('should burn tokens', () => {
const id = u256.One;
const value = u256.from(10);
const data = stringToBytes('burn data');
_mint(user1Address, id, value, data);

expect(
balanceOf(
new Args().add(stringToBytes(user1Address)).add(id).serialize(),
),
).toStrictEqual(u256ToBytes(value));

burn(
new Args()
.add(stringToBytes(user1Address))
.add(id)
.add(value)
.serialize(),
);
expect(
balanceOf(
new Args().add(stringToBytes(user1Address)).add(id).serialize(),
),
).toStrictEqual(u256ToBytes(u256.Zero));
});

test('should burn tokens with approval', () => {
const id = u256.One;
const value = u256.from(10);
const data = stringToBytes('burn data');
_mint(user1Address, id, value, data);
expect(
balanceOf(
new Args().add(stringToBytes(user1Address)).add(id).serialize(),
),
).toStrictEqual(u256ToBytes(value));

setApprovalForAll(
new Args()
.add(stringToBytes(user2Address))
.add(boolToByte(true))
.serialize(),
);

switchUser(user2Address);
burn(
new Args()
.add(stringToBytes(user1Address))
.add(id)
.add(value)
.serialize(),
);
expect(
balanceOf(
new Args().add(stringToBytes(user1Address)).add(id).serialize(),
),
).toStrictEqual(u256ToBytes(u256.Zero));
});

throws('ERC1155MissingApprovalForAll', () => {
const id = u256.One;
const value = u256.from(10);
const data = stringToBytes('burn data');
_mint(user1Address, id, value, data);
expect(
balanceOf(
new Args().add(stringToBytes(user1Address)).add(id).serialize(),
),
).toStrictEqual(u256ToBytes(value));

switchUser(user2Address);
burn(
new Args()
.add(stringToBytes(user1Address))
.add(id)
.add(value)
.serialize(),
);
});
});

describe('burnBatch', () => {
test('should burn batch of tokens', () => {
const ids = [u256.One, u256.from(2), u256.from(3)];
const values = [u256.from(10), u256.from(20), u256.from(30)];
const data = stringToBytes('burn data');
_mintBatch(user1Address, ids, values, data);
expect(
balanceOfBatch(
new Args()
.add([user1Address, user1Address, user1Address])
.add(ids)
.serialize(),
),
).toStrictEqual(fixedSizeArrayToBytes<u256>(values));

burnBatch(new Args().add(user1Address).add(ids).add(values).serialize());
expect(
balanceOfBatch(
new Args()
.add([user1Address, user1Address, user1Address])
.add(ids)
.serialize(),
),
).toStrictEqual(
fixedSizeArrayToBytes<u256>([u256.Zero, u256.Zero, u256.Zero]),
);
});

test('should burn batch of tokens with approval', () => {
const ids = [u256.One, u256.from(2), u256.from(3)];
const values = [u256.from(10), u256.from(20), u256.from(30)];
const data = stringToBytes('burn data');
_mintBatch(user1Address, ids, values, data);
expect(
balanceOfBatch(
new Args()
.add([user1Address, user1Address, user1Address])
.add(ids)
.serialize(),
),
).toStrictEqual(fixedSizeArrayToBytes<u256>(values));

setApprovalForAll(
new Args()
.add(stringToBytes(user2Address))
.add(boolToByte(true))
.serialize(),
);

switchUser(user2Address);
burnBatch(new Args().add(user1Address).add(ids).add(values).serialize());
expect(
balanceOfBatch(
new Args()
.add([user1Address, user1Address, user1Address])
.add(ids)
.serialize(),
),
).toStrictEqual(
fixedSizeArrayToBytes<u256>([u256.Zero, u256.Zero, u256.Zero]),
);
});

throws('ERC1155MissingApprovalForAll', () => {
const ids = [u256.One, u256.from(2), u256.from(3)];
const values = [u256.from(10), u256.from(20), u256.from(30)];
const data = stringToBytes('burn data');
_mintBatch(user1Address, ids, values, data);
expect(
balanceOfBatch(
new Args()
.add([user1Address, user1Address, user1Address])
.add(ids)
.serialize(),
),
).toStrictEqual(fixedSizeArrayToBytes<u256>(values));

switchUser(user2Address);
burnBatch(new Args().add(user1Address).add(ids).add(values).serialize());
});
});
Loading

0 comments on commit 539f441

Please sign in to comment.