Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TRN-862 Soulbound ERC5484 Tokens #928

Merged
merged 33 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6cefb02
Add token transferable flag to NFT pallet
JasonTulp Feb 11, 2025
4443880
Add token transferable flag to SFT pallet
JasonTulp Feb 11, 2025
63cbdf5
Add benchmarks
JasonTulp Feb 11, 2025
4c7c557
fmt
JasonTulp Feb 12, 2025
244f64e
Add benchmark variability for NFT.transfer
JasonTulp Feb 12, 2025
023bf96
fmt
JasonTulp Feb 12, 2025
adff74d
Change BurnAuthority to Option
JasonTulp Feb 12, 2025
9414cbb
implement extrinsics for issue and accept_issuance
JCSanPedro Feb 17, 2025
f1744f9
add tests for nft soulbound extrinsics
JCSanPedro Feb 19, 2025
1a94671
add soulbound functionality to sft pallet
JCSanPedro Feb 20, 2025
6ab1800
use StorageNMap for storing pending issuances
JCSanPedro Feb 20, 2025
5754a83
store pending issuances as bounded vecs
JCSanPedro Feb 20, 2025
42996eb
update erc721 precompile to support erc5484
JCSanPedro Feb 21, 2025
782da43
update erc11 precompile to support erc5484
JCSanPedro Feb 23, 2025
ef05eae
implement benchmarks
JCSanPedro Feb 23, 2025
9655c9d
fix missing mock value
JCSanPedro Feb 23, 2025
c8eaa54
Update benchmarks for pallet-sft on TRN-862-sbt
actions-user Feb 23, 2025
652613b
fix more missing mock value
JCSanPedro Feb 24, 2025
15b1b0b
Update benchmarks for pallet-nft on TRN-862-sbt
actions-user Feb 24, 2025
504987a
optimise storage read on sft burn
JCSanPedro Feb 25, 2025
21e2961
update TokenBurnAuthority to match erc5484 spec and add implementatio…
JCSanPedro Feb 25, 2025
317a62a
log all issuance ids in a single event for erc721.issue
JCSanPedro Feb 25, 2025
8461d07
avoid iterating twice when inserting pending issuances
JCSanPedro Feb 25, 2025
011323c
refactor to avoid code duplication
JCSanPedro Feb 25, 2025
442ee5d
store quantity with pending nft issuance to allow minting all tokens …
JCSanPedro Feb 25, 2025
09919cf
store serial numbers in sft pending issuances to allow minting all to…
JCSanPedro Feb 25, 2025
0aa94ec
rename issue -> issue_soulbound
JCSanPedro Feb 25, 2025
8a86514
rename accept_issuance -> accept_soulbound_issuance
JCSanPedro Feb 26, 2025
d3703c9
Update benchmarks for pallet-nft on TRN-862-sbt
actions-user Feb 26, 2025
0d26e9b
restore original error behaviour when burning nft
JCSanPedro Feb 26, 2025
231bdea
perform pre_mint on issue to avoid issuances becoming invalid
JCSanPedro Feb 26, 2025
70e9996
rename burn_as_owner -> burn_as_collection_owner
JCSanPedro Feb 26, 2025
746a43b
remove commented code
JCSanPedro Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions e2e/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,15 @@ export const ERC721_PRECOMPILE_ABI = [

// ERC165
...ERC165_ABI,

// ERC5484
"event PendingIssuanceCreated(address indexed to, uint256 issuanceId, uint256 quantity, uint8 burnAuth)",
"event Issued(address indexed from, address indexed to, uint256 indexed tokenId, uint8 burnAuth)",

"function issueSoulbound(address,uint32,uint8)",
"function acceptSoulboundIssuance(uint32)",
"function pendingIssuances(address) external view returns (uint256[] memory, (uint256 quantity, uint8)[] memory)",
"function burnAuth(uint256) external view returns (uint8)",
];

export const ERC1155_PRECOMPILE_ABI = [
Expand Down Expand Up @@ -412,6 +421,17 @@ export const ERC1155_PRECOMPILE_ABI = [

// ERC165
...ERC165_ABI,

// ERC5484
"event PendingIssuanceCreated(address indexed to, uint256 issuanceId, uint256[] tokenIds, uint256[] balances)",
"event Issued(address indexed from, address indexed to, uint256 indexed tokenId, uint8 burnAuth)",

"function setBurnAuth(uint256,uint8)",
"function issueSoulbound(address,uint256[],uint256[])",
"function acceptSoulboundIssuance(uint32)",
"function pendingIssuances(address) external view returns (uint256[] memory,(uint256[] memory,uint256[] memory,uint8[] memory)[] memory)",
"function burnAuth(uint256) external view returns (uint8)",
"function burnAsCollectionOwner(address,uint256[],uint256[])",
];

export const FUTUREPASS_REGISTRAR_PRECOMPILE_ABI = [
Expand Down Expand Up @@ -828,3 +848,10 @@ export const getPrefixLength = (encoded: SubmittableExtrinsic<any>): number => {
if (encoded.encodedLength < 66) return 6;
return 8;
};

export enum BurnAuth {
TokenOwner = 0,
CollectionOwner,
Both,
Neither,
}
59 changes: 58 additions & 1 deletion e2e/test/ERC1155/ERC1155Precompile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Web3 from "web3";
import {
ALITH_PRIVATE_KEY,
BOB_PRIVATE_KEY,
BurnAuth,
ERC1155_PRECOMPILE_ABI,
NodeProcess,
ROOT_PRECOMPILE_ADDRESS,
Expand Down Expand Up @@ -65,7 +66,6 @@ describe("ERC1155 Precompile", function () {
erc1155PrecompileAddress = Web3.utils.toChecksumAddress(
`0xBBBBBBBB${collection_id_hex}000000000000000000000000`,
);
console.log(`SFT Collection Address: ${erc1155PrecompileAddress}`);
erc1155Precompile = new Contract(erc1155PrecompileAddress, ERC1155_PRECOMPILE_ABI, bobSigner);
resolve();
}
Expand Down Expand Up @@ -764,4 +764,61 @@ describe("ERC1155 Precompile", function () {
// console.log("TRN1155:", trn1155Id);
// console.log("Ownable:", ownableId);
});

it("can issue and accept issuance of soulbound tokens", async () => {
const receiverAddress = alithSigner.address;

const tokens = [];
for (let i = 0; i < 3; i++) {
const token = await createToken(0);

await erc1155Precompile.setBurnAuth(token, BurnAuth.Both).then((tx: any) => tx.wait());

tokens.push(token);
}

const amounts = tokens.map((_) => 5);

let receipt = await erc1155Precompile.issueSoulbound(receiverAddress, tokens, amounts).then((tx: any) => tx.wait());

expect(receipt).to.emit(erc1155Precompile, "PendingIssuanceCreated").withArgs(receiverAddress, 0, tokens, amounts);

const [issuanceIds, issuances] = await erc1155Precompile.pendingIssuances(receiverAddress);

// check issuance id
expect(issuanceIds[0]).to.eq(0);

// check issuances
expect(issuances[0][0]).to.deep.eq([0, 1, 2]);
expect(issuances[0][1]).to.deep.eq([5, 5, 5]);
expect(issuances[0][2]).to.deep.eq([BurnAuth.Both, BurnAuth.Both, BurnAuth.Both]);

receipt = await erc1155Precompile
.connect(alithSigner)
.acceptSoulboundIssuance(0)
.then((tx: any) => tx.wait());

for (const tokenId of tokens) {
expect(receipt)
.to.emit(erc1155Precompile, "Issued")
.withArgs(bobSigner.address, receiverAddress, tokenId, BurnAuth.Both);

expect(await erc1155Precompile.balanceOf(receiverAddress, tokenId)).to.eq(5);

expect(await erc1155Precompile.burnAuth(tokenId)).to.equal(BurnAuth.Both);
}

// burn as owner
const burnReceipt = await erc1155Precompile
.burnAsCollectionOwner(receiverAddress, tokens, amounts)
.then((tx: any) => tx.wait());

expect(burnReceipt)
.to.emit(erc1155Precompile, "TransferBatch")
.withArgs(bobSigner.address, constants.AddressZero, tokens, amounts);

for (const token of tokens) {
expect(await erc1155Precompile.balanceOf(receiverAddress, token)).to.eq(0);
}
});
});
38 changes: 38 additions & 0 deletions e2e/test/ERC721/ERC721Precompile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Web3 from "web3";
import {
ALITH_PRIVATE_KEY,
BOB_PRIVATE_KEY,
BurnAuth,
ERC721_PRECOMPILE_ABI,
NFT_PRECOMPILE_ABI,
NFT_PRECOMPILE_ADDRESS,
Expand Down Expand Up @@ -732,4 +733,41 @@ describe("ERC721 Precompile", function () {
// console.log("TRN721:", trn721Id);
// console.log("Ownable:", ownableId);
});

it("can issue and accept soulbound tokens", async () => {
const receiverAddress = alithSigner.address;
const quantity = 3;
let receipt = await erc721Precompile
.issueSoulbound(receiverAddress, quantity, BurnAuth.Both)
.then((tx: any) => tx.wait());

expect(receipt)
.to.emit(erc721Precompile, "PendingIssuanceCreated")
.withArgs(receiverAddress, 0, quantity, BurnAuth.Both);

const [issuanceIds, issuances] = await erc721Precompile.pendingIssuances(receiverAddress);
expect(issuanceIds).to.deep.equal([0]);

expect(issuances[0][0]).to.deep.equal(quantity);
expect(issuances[0][1]).to.deep.equal(BurnAuth.Both);

receipt = await erc721Precompile
.connect(alithSigner)
.acceptSoulboundIssuance(0)
.then((tx: any) => tx.wait());

console.log(receipt);

for (let i = 0; i < 3; i++) {
const tokenId = receipt.events[i].args.tokenId;

expect(receipt)
.to.emit(erc721Precompile, "Issued")
.withArgs(bobSigner.address, receiverAddress, tokenId, BurnAuth.Both);

expect(await erc721Precompile.ownerOf(tokenId)).to.eq(receiverAddress);

expect(await erc721Precompile.burnAuth(tokenId)).to.eq(BurnAuth.Both);
}
});
});
Loading
Loading