Skip to content

Commit

Permalink
feat: susbcription 1155 without blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
aaitor committed Apr 30, 2024
1 parent 2414c4e commit 56b64d5
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 11 deletions.
23 changes: 12 additions & 11 deletions contracts/token/erc1155/NFT1155SubscriptionUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ contract NFT1155SubscriptionUpgradeable is NFT1155Upgradeable {

struct MintedTokens {
uint256 amountMinted; // uint64
uint256 expirationBlock; // uint64
uint256 expirationBlock;
uint256 mintBlock;
bool isMintOps; // true means mint, false means burn
}
Expand Down Expand Up @@ -64,12 +64,13 @@ contract NFT1155SubscriptionUpgradeable is NFT1155Upgradeable {

// solhint-disable-next-line
function burn(address to, uint256 id, uint256 amount, uint256 _seed) override public {
address _sender = _msgSender();
require(balanceOf(to, id) >= amount, 'ERC1155: burn amount exceeds balance');
require(
isOperator(_msgSender()) || // Or the DIDRegistry is burning the NFT
to == _msgSender() || // Or the NFT owner is _msgSender()
nftRegistry.isDIDProvider(bytes32(id), _msgSender()) || // Or the DID Provider (Node) is burning the NFT
isApprovedForAll(to, _msgSender()), // Or the _msgSender() is approved
isOperator(_sender) || // Or the DIDRegistry is burning the NFT
to == _sender || // Or the NFT owner is _msgSender()
nftRegistry.isDIDProvider(bytes32(id), _sender) || // Or the DID Provider (Node) is burning the NFT
isApprovedForAll(to, _sender), // Or the _msgSender() is approved
'ERC1155: caller is not owner nor approved'
);

Expand All @@ -83,15 +84,15 @@ contract NFT1155SubscriptionUpgradeable is NFT1155Upgradeable {
bytes32 _key = _getTokenKey(to, id);

uint256 _pendingToBurn = amount;

for (uint index = 0; index < _tokens[_key].length; index++) {
if (_tokens[_key][index].expirationBlock == 0 || _tokens[_key][index].expirationBlock > block.number) {
if (_pendingToBurn <= _tokens[_key][index].amountMinted) {
_tokens[_key].push( MintedTokens(_pendingToBurn, _tokens[_key][index].expirationBlock, block.number, false));
MintedTokens memory entry = _tokens[_key][index];
if (entry.expirationBlock == 0 || entry.expirationBlock > block.number) {
if (_pendingToBurn <= entry.amountMinted) {
_tokens[_key].push( MintedTokens(_pendingToBurn, entry.expirationBlock, block.number, false));
break;
} else {
_pendingToBurn -= _tokens[_key][index].amountMinted;
_tokens[_key].push( MintedTokens(_tokens[_key][index].amountMinted, _tokens[_key][index].expirationBlock, block.number, false));
_pendingToBurn -= entry.amountMinted;
_tokens[_key].push( MintedTokens(entry.amountMinted, entry.expirationBlock, block.number, false));
}
}
}
Expand Down
131 changes: 131 additions & 0 deletions contracts/token/erc1155/NFT1155SubscriptionWithoutBlocks.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol';
import './NFT1155Upgradeable.sol';
// Copyright 2022 Nevermined AG.
// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
// Code is Apache-2.0 and docs are CC-BY-4.0

contract NFT1155SubscriptionWithoutBlocks is NFT1155Upgradeable {

// struct MintedTokens {
// uint256 amountMinted; // uint64
// uint256 expirationBlock;
// uint256 mintBlock;
// bool isMintOps; // true means mint, false means burn
// }
//
// mapping(bytes32 => MintedTokens[]) internal _tokens;

// It represents the NFT type. It is used to identify the NFT type in the Nevermined ecosystem
// solhint-disable-next-line
bytes32 public constant override nftType = keccak256('nft1155-subscription');

function initialize(
address owner,
address didRegistryAddress,
string memory name_,
string memory symbol_,
string memory uri_,
address nvmConfig_
)
public
override
virtual
initializer
{
__NFT1155Upgradeable_init(owner, didRegistryAddress, name_, symbol_, uri_, nvmConfig_);
}


function mint(address to, uint256 tokenId, uint256 amount, bytes memory data) virtual override public {
super.mint(to, tokenId, amount, data);
}

function burn(uint256 id, uint256 amount) override public {
burn(_msgSender(), id, amount);
}

// solhint-disable-next-line
function burn(address to, uint256 id, uint256 amount) override public {
address _sender = _msgSender();
require(balanceOf(to, id) >= amount, 'ERC1155: burn amount exceeds balance');
require(
isOperator(_sender) || // Or the DIDRegistry is burning the NFT
to == _sender || // Or the NFT owner is _msgSender()
nftRegistry.isDIDProvider(bytes32(id), _sender) || // Or the DID Provider (Node) is burning the NFT
isApprovedForAll(to, _sender), // Or the _msgSender() is approved
'ERC1155: caller is not owner nor approved'
);

// Update nftSupply
_nftAttributes[id].nftSupply -= amount;
// Register provenance event

_burn(to, id, amount);
}

/**
* @dev See {NFT1155Upgradeableable-balanceOf}.
*/
function balanceOf(address account, uint256 tokenId) public view virtual override returns (uint256) {
return super.balanceOf(account, tokenId);
// bytes32 _key = _getTokenKey(account, tokenId);
// uint256 _amountBurned;
// uint256 _amountMinted;
// for (uint index = 0; index < _tokens[_key].length; index++) {
// if (_tokens[_key][index].mintBlock > 0 &&
// (_tokens[_key][index].expirationBlock == 0 || _tokens[_key][index].expirationBlock > block.number)) {
// if (_tokens[_key][index].isMintOps)
// _amountMinted += _tokens[_key][index].amountMinted;
// else
// _amountBurned += _tokens[_key][index].amountMinted;
// }
// }
//
// if (_amountBurned >= _amountMinted)
// return 0;
// else
// return _amountMinted - _amountBurned;
}

// function whenWasMinted(address owner, uint256 tokenId) public view returns (uint256[] memory) {
// bytes32 _key = _getTokenKey(owner, tokenId);
// uint256[] memory _whenMinted = new uint256[](_tokens[_key].length);
// for (uint index = 0; index < _tokens[_key].length; index++) {
// _whenMinted[index] = _tokens[_key][index].mintBlock;
// }
// return _whenMinted;
// }
//
// function getMintedEntries(address owner, uint256 tokenId) public view returns (MintedTokens[] memory) {
// return _tokens[_getTokenKey(owner, tokenId)];
// }
//
// function _getTokenKey(address account, uint256 tokenId) internal pure returns (bytes32) {
// return keccak256(abi.encode(account, tokenId));
// }

function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) external {
require(ids.length == amounts.length, 'mintBatch: lengths do not match');
for (uint i = 0; i < ids.length; i++) {
mint(to, ids[i], amounts[i], data);
}
}

function burnBatch(
address from,
uint256[] memory ids,
uint256[] memory amounts
) external {
require(ids.length == amounts.length, 'burnBatch: lengths do not match');
for (uint i = 0; i < ids.length; i++) {
burn(from, ids[i], amounts[i]);
}
}
}
181 changes: 181 additions & 0 deletions test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/* eslint-env mocha */
/* eslint-disable no-console */
/* global artifacts, contract, describe, it */

const chai = require('chai')
const { assert } = chai
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const { ethers } = require('hardhat')

const DIDRegistry = artifacts.require('DIDRegistry')
const TestERC1155 = artifacts.require('NFT1155SubscriptionWithoutBlocks')

const testUtils = require('../../helpers/utils.js')
const constants = require('../../helpers/constants.js')
const increaseTime = require('../../helpers/increaseTime.js')

Check failure on line 16 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'increaseTime' is assigned a value but never used
const BigNumber = require('bignumber.js')

contract('NFT1155 Subscription', (accounts) => {
const web3 = global.web3

Check failure on line 20 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'web3' is assigned a value but never used

const didSeedExpiring = testUtils.generateId()

Check failure on line 22 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'didSeedExpiring' is assigned a value but never used
const didSeedNonExpiring = testUtils.generateId()

Check failure on line 23 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'didSeedNonExpiring' is assigned a value but never used

let tokenIdExpiring

Check failure on line 25 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'tokenIdExpiring' is defined but never used
let tokenIdNonExpiring

Check failure on line 26 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'tokenIdNonExpiring' is defined but never used

const amount = 1

Check failure on line 28 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'amount' is assigned a value but never used
const blocksExpiring = 10

Check failure on line 29 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'blocksExpiring' is assigned a value but never used
const blocksNonExpiring = 0

Check failure on line 30 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

'blocksNonExpiring' is assigned a value but never used
const data = '0x'

const checksum = testUtils.generateId()
const url = 'https://raw.githubusercontent.com/nevermined-io/assets/main/images/logo/banner_logo.png'

const [
owner,
deployer,
minter,
account1,
account2
] = accounts

let nft
let didRegistry

async function setupTest() {
const config = await artifacts.require('NeverminedConfig').new()
await config.initialize(owner, owner, true)
didRegistry = await DIDRegistry.new()
await didRegistry.initialize(owner, constants.address.zero, constants.address.zero, config.address, constants.address.zero)

nft = await TestERC1155.new({ from: deployer })
await nft.initialize(owner, didRegistry.address, 'TestERC1155', 'TEST', '', config.address, { from: owner })

await nft.setNvmConfigAddress(config.address, { from: owner })
await config.grantNVMOperatorRole(didRegistry.address, { from: owner })
await config.grantNVMOperatorRole(owner, { from: owner })
await config.grantNVMOperatorRole(minter, { from: owner })
}

describe('Providers can burn', () => {
const initialAmount = 10
let tokenId

it('As a minter can register a DID without providers', async () => {
await setupTest()
const didSeed = testUtils.generateId()

tokenId = await didRegistry.hashDID(didSeed, minter)
await didRegistry.methods[
'registerMintableDID(bytes32,address,bytes32,address[],string,uint256,uint256,bool,bytes32,string,string)'
](didSeed, nft.address, checksum, [], url, 0, 0, false, constants.activities.GENERATED, '', '', { from: minter })

await nft.methods[
'mint(address,uint256,uint256,bytes)'
](account1, tokenId, initialAmount, data, { from: minter })

const balance = new BigNumber(await nft.balanceOf(account1, tokenId))
assert.strictEqual(balance.toNumber(), initialAmount)
})

it('NFT holder can burn', async () => {
await nft.methods[
'burn(address,uint256,uint256)'
](account1, tokenId, 1, { from: account1 })

const balance = new BigNumber(await nft.balanceOf(account1, tokenId))
assert.strictEqual(balance.toNumber(), initialAmount - 1)
})

it('Account can not burn unless is a provider', async () => {
await assert.isRejected(
nft.methods[
'burn(address,uint256,uint256)'
](account1, tokenId, 1, { from: account2 }),
'ERC1155: caller is not owner nor approved'
)

let balance = new BigNumber(await nft.balanceOf(account1, tokenId))
assert.strictEqual(balance.toNumber(), initialAmount - 1)

await didRegistry.addDIDProvider(tokenId, account2, { from: minter })

await nft.methods[
'burn(address,uint256,uint256)'
](account1, tokenId, 1, { from: account2 })

balance = new BigNumber(await nft.balanceOf(account1, tokenId))
assert.strictEqual(balance.toNumber(), initialAmount - 2)
})
})


Check failure on line 114 in test/unit/token/NFT1155SubscriptionWithoutBlocks.Test.js

View workflow job for this annotation

GitHub Actions / tests

More than 1 blank line not allowed
describe('Mint and burn', () => {
it('New tokens can be minted and burned', async () => {
await setupTest()

let balance
const didSeed3 = testUtils.generateId()
const tokenId3 = await didRegistry.hashDID(didSeed3, minter)
await didRegistry.methods[
'registerMintableDID(bytes32,address,bytes32,address[],string,uint256,uint256,bool,bytes32,string,string)'
](didSeed3, nft.address, checksum, [], url, 0, 0, false, constants.activities.GENERATED, '', '', { from: minter })

const currentBlockNumber = await ethers.provider.getBlockNumber()

// MINT 7 tokens
await nft.methods[
'mint(address,uint256,uint256,bytes)'
](account2, tokenId3, 7, data, { from: minter })

// Balance is 7
balance = new BigNumber(await nft.balanceOf(account2, tokenId3))
assert.strictEqual(balance.toNumber(), 7)

// MINT 10 tokens
await nft.methods[
'mint(address,uint256,uint256,bytes)'
](account2, tokenId3, 10, data, { from: minter })

// Balance is 17
balance = new BigNumber(await nft.balanceOf(account2, tokenId3))
assert.strictEqual(balance.toNumber(), 17)

// BURN 4 tokens
await nft.methods[
'burn(address,uint256,uint256)'
](account2, tokenId3, 4, { from: minter })

// Balance is 13
balance = new BigNumber(await nft.balanceOf(account2, tokenId3))
assert.strictEqual(balance.toNumber(), 13)

// Batch mint
const didSeed4 = testUtils.generateId()
const tokenId4 = await didRegistry.hashDID(didSeed4, minter)
await didRegistry.methods[
'registerMintableDID(bytes32,address,bytes32,address[],string,uint256,uint256,bool,bytes32,string,string)'
](didSeed4, nft.address, checksum, [], url, 0, 0, false, constants.activities.GENERATED, '', '', { from: minter })

// Also test balance batch
await nft.mintBatch(account2, [tokenId3, tokenId4], [10, 15], data, { from: minter })
balance = new BigNumber(await nft.balanceOf(account2, tokenId3))
let balance2 = new BigNumber(await nft.balanceOf(account2, tokenId4))
assert.strictEqual(balance.toNumber(), 23)
assert.strictEqual(balance2.toNumber(), 15)

const balances = (await nft.balanceOfBatch([account2, account2], [tokenId3, tokenId4])).map(a => new BigNumber(a).toNumber())
assert.strictEqual(balances[0], 23)
assert.strictEqual(balances[1], 15)

await nft.burnBatch(account2, [tokenId3, tokenId4], [22, 14], { from: minter })
balance = new BigNumber(await nft.balanceOf(account2, tokenId3))
balance2 = new BigNumber(await nft.balanceOf(account2, tokenId4))
assert.strictEqual(balance.toNumber(), 1)
assert.strictEqual(balance2.toNumber(), 1)
})

})
})

0 comments on commit 56b64d5

Please sign in to comment.