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

Feat: PoC Basic metadata #3

Merged
merged 8 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ on:
push:
pull_request:
jobs:
check:
build-starknet-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: "2.6.2"
tool-versions: .tool-versions
- run: scarb fmt --check
- run: scarb build
7 changes: 5 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ on:
push:
pull_request:
jobs:
check:
test-starknet-contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: software-mansion/setup-scarb@v1
with:
scarb-version: "2.6.2"
tool-versions: .tool-versions
- uses: foundry-rs/setup-snfoundry@v3
with:
tool-versions: .tool-versions
- run: scarb test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
target
.env
.snfoundry_cache
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
scarb 2.6.2
starknet-foundry 0.19.0
scarb 2.5.4
starknet-foundry 0.18.0
12 changes: 9 additions & 3 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "alexandria_storage"
version = "0.3.0"
source = "git+https://github.com/keep-starknet-strange/alexandria?tag=cairo-v2.5.4#e7b69575f92076e96a83a76efa6bed3c32a15f9f"

[[package]]
name = "graffiti"
version = "0.1.0"
Expand All @@ -10,6 +15,7 @@ source = "git+https://github.com/ponderingdemocritus/graffiti?rev=bc569531791dbc
name = "offset_certificate"
version = "0.1.0"
dependencies = [
"alexandria_storage",
"graffiti",
"openzeppelin",
"snforge_std",
Expand All @@ -18,9 +24,9 @@ dependencies = [
[[package]]
name = "openzeppelin"
version = "0.10.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#89cc02b74258f71a4516e8837593077a1515d137"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.10.0#d77082732daab2690ba50742ea41080eb23299d3"

[[package]]
name = "snforge_std"
version = "0.19.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.19.0#a3391dce5bdda51c63237032e6cfc64fb7a346d4"
version = "0.18.0"
source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.18.0#48f909a56b08cbdc5ca6a21a836b0fbc6c36d55b"
13 changes: 11 additions & 2 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ homepage = "https://github.com/carbonable-labs/poc-offset-certificate"

[[target.starknet-contract]]
sierra = true
casm = true

[dependencies]
starknet = ">=2.6.0"
starknet = ">=2.5.4"
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.10.0" }
graffiti = { git = "https://github.com/ponderingdemocritus/graffiti", rev = "bc569531791dbc71c6cd8d9bc154c34eedad31fe" }
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.19.0" }
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.18.0" }
alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria", tag = "cairo-v2.5.4" }
# alexandria_storage = { git = "https://github.com/tekkac/alexandria", branch = "fix/cairo-version-2.6.2" }

[tool.snforge]
exit_first = true

[scripts]
test = "snforge test"
72 changes: 60 additions & 12 deletions src/contract.cairo
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
use starknet::ContractAddress;

#[starknet::interface]
trait IOffsetCertificate<ContractState> {
fn mint(
ref self: ContractState, to: ContractAddress, token_ids: Span<u256>, values: Span<u256>
) -> u256;
fn name(self: @ContractState) -> ByteArray;
fn symbol(self: @ContractState) -> ByteArray;
fn token_uri(self: @ContractState, token_id: u256) -> ByteArray;
fn tokenURI(self: @ContractState, tokenId: u256) -> ByteArray;
fn get_certificate(self: @ContractState, token_id: u256) -> (Span<u256>, Span<u256>);
}

#[starknet::contract]
mod ERC721 {
use openzeppelin::token::erc721::interface::IERC721Metadata;
mod OffsetCertificatePoc {
use openzeppelin::introspection::src5::SRC5Component;
use openzeppelin::token::erc721::ERC721Component;

use alexandria_storage::list::{List, ListTrait};

use starknet::ContractAddress;

component!(path: ERC721Component, storage: erc721, event: ERC721Event);
Expand All @@ -26,7 +42,10 @@ mod ERC721 {
#[substorage(v0)]
erc721: ERC721Component::Storage,
#[substorage(v0)]
src5: SRC5Component::Storage
src5: SRC5Component::Storage,
certificate_tokens: LegacyMap<u256, List<u256>>,
certificate_values: LegacyMap<u256, List<u256>>,
total_supply: u256
}

#[event]
Expand All @@ -39,26 +58,55 @@ mod ERC721 {
}

#[constructor]
fn constructor(ref self: ContractState, recipient: ContractAddress, token_ids: Span<u256>,) {}
fn constructor(ref self: ContractState) {}


mod Errors {
const LENGTH_MISMATCH: felt252 = 'OC: length mismatch';
const INVALID_CERTIFICATE: felt252 = 'OC: invalid certificate';
}

// Custom Metadata
use offset_certificate::metadata::template::generate;


#[abi(embed_v0)]
impl MetadataImpl of ERC721Component::interface::IERC721Metadata<ContractState> {
impl IOffsetCertifImpl of super::IOffsetCertificate<ContractState> {
fn mint(
ref self: ContractState, to: ContractAddress, token_ids: Span<u256>, values: Span<u256>
) -> u256 {
assert(token_ids.len() == values.len(), Errors::LENGTH_MISMATCH);
let minted_token_id = self.total_supply.read() + 1;
self.erc721._mint(to, minted_token_id);
self.total_supply.write(minted_token_id);
let mut certificate_tokens = self.certificate_tokens.read(minted_token_id.into());
let mut certificate_values = self.certificate_values.read(minted_token_id.into());
let _ = certificate_tokens.append_span(token_ids);
let _ = certificate_values.append_span(values);

minted_token_id
}

fn name(self: @ContractState) -> ByteArray {
""
"OffsetCertificatePoc"
}
fn symbol(self: @ContractState) -> ByteArray {
""
"OCP"
}
fn token_uri(self: @ContractState, token_id: u256) -> ByteArray {
""
generate::generate_uri(token_id)
}
}

#[abi(embed_v0)]
impl MetadataCamelImpl of ERC721Component::interface::IERC721MetadataCamelOnly<ContractState> {
fn tokenURI(self: @ContractState, tokenId: u256) -> ByteArray {
self.token_uri(tokenId)
}

fn get_certificate(self: @ContractState, token_id: u256) -> (Span<u256>, Span<u256>) {
let certificate_tokens = self.certificate_tokens.read(token_id.into());
let certificate_values = self.certificate_values.read(token_id.into());
(
certificate_tokens.array().expect(Errors::INVALID_CERTIFICATE).span(),
certificate_values.array().expect(Errors::INVALID_CERTIFICATE).span()
)
}
}
}
14 changes: 14 additions & 0 deletions src/metadata/lorem_ipsum/contract.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
#[starknet::contract]
mod LoremIpsumMetadata {
use offset_certificate::metadata::template::generate;
#[storage]
struct Storage {}

#[generate_trait]
#[abi(per_item)]
impl LasDeliciasTokenMetadata of ITokenMetadata {
#[external(v0)]
fn token_uri(self: @ContractState, token_id: u256) -> ByteArray {
generate::generate_uri(token_id)
}
}
}
43 changes: 43 additions & 0 deletions src/metadata/template/generate.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,44 @@
use core::array::SpanTrait;
use graffiti::json::JsonImpl;
use starknet::get_contract_address;
use offset_certificate::contract::{IOffsetCertificateDispatcher, IOffsetCertificateDispatcherTrait};

fn get_description(token_id: u256) -> ByteArray {
format!("Dummy description for token #{}", token_id)
}

fn generate_uri(token_id: u256) -> ByteArray {
let contract_address = get_contract_address();
let contract = IOffsetCertificateDispatcher { contract_address };
let name = contract.name();
let project_name = "test";

let metadata = JsonImpl::new()
.add("name", format!("{} #{}", name, token_id))
.add("description", get_description(token_id))
.add("project", project_name);

let mut attributes: Array<ByteArray> = Default::default();

let certificate = contract.get_certificate(token_id);
let (mut token_ids, mut values) = certificate;

loop {
match token_ids.pop_front() {
Option::Some(id) => {
let value = values.pop_front().unwrap();
attributes
.append(
JsonImpl::new()
.add("trait_type", format!("{}", id))
.add("value", format!("{}", value))
.build()
);
},
Option::None => { break (); },
};
};
let metadata: ByteArray = metadata.add_array("attributes", attributes.span()).build();
format!("data:application/json,{}", metadata)
}

31 changes: 31 additions & 0 deletions tests/test_token_uri.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use starknet::ContractAddress;

use snforge_std::{declare, ContractClassTrait};
use snforge_std::{start_prank, CheatTarget, stop_prank};

use openzeppelin::token::erc721::interface::{IERC721Dispatcher, IERC721DispatcherTrait};

use offset_certificate::contract::{IOffsetCertificateDispatcher, IOffsetCertificateDispatcherTrait};


#[test]
fn test_mint_certificate() {
let contract = declare('OffsetCertificatePoc');

let contract_address = contract.deploy(@array![]).unwrap();

let certificate_nft = IOffsetCertificateDispatcher { contract_address };
let erc721 = IERC721Dispatcher { contract_address };

let token_ids: Span<u256> = array![1, 2, 3, 4].span();
let values: Span<u256> = array![10, 20, 30, 40].span();
let token = certificate_nft.mint('0xUser'.try_into().unwrap(), token_ids, values);
assert_eq!(token, 1);

let bal = erc721.balance_of('0xUser'.try_into().unwrap());
assert_eq!(bal, 1);

let metadata = certificate_nft.token_uri(token);
println!("{:}", metadata);
assert_eq!((metadata[0], metadata[1], metadata[2], metadata[3]), ('d', 'a', 't', 'a'));
}
Loading