Skip to content

Commit

Permalink
Merge pull request #8 from krisoshea-eth/starknetAttestationServiceCo…
Browse files Browse the repository at this point in the history
…ntract

Starknet attestation service contract
  • Loading branch information
krisoshea-eth authored Jun 23, 2024
2 parents 5f4a929 + 3e3caa1 commit 2d18ff6
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/snfoundry/contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ casm = true

[tool.fmt]
sort-module-level-items = true

[tool.snforge]
234 changes: 234 additions & 0 deletions packages/snfoundry/contracts/src/SAS.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
use core::array::Array;
use core::array::ArrayTrait;
use core::array::SpanTrait;
use core::box::BoxTrait;
use core::byte_array::ByteArray;
use core::byte_array::ByteArrayTrait;
use core::hash::LegacyHash;
use starknet::ContractAddress;
use starknet::SyscallResult;
// use starknet::Store;
use starknet::storage_access::Store;
use starknet::storage_access::{StorageAddress, StorageBaseAddress};
use starknet::syscalls::storage_read_syscall;
use starknet::syscalls::storage_write_syscall;
use starknet::syscalls::call_contract_syscall;
use starknet::{get_block_timestamp, get_caller_address};

// Define structs
#[derive(Drop, Serde, Store)]
struct Attestation {
uid: u128,
schema_uid: u128,
time: u64,
expiration_time: u64,
ref_uid: u128,
recipient: ContractAddress,
attester: ContractAddress,
data: Array<felt252>,
}

#[derive(Drop, Serde)]
struct AttestationRequest {
schema_uid: u128,
recipient: ContractAddress,
expirationTime: u64,
refUID: u128,
data: Array<felt252>,
}

#[derive(Drop, Serde)]
struct AttestationsResult {
used_value: u256,
uids: Array<u128>,
}

#[derive(Drop, Serde, Store)]
pub struct SchemaRecord {
uid: u128, // The unique identifier of the schema.
revocable: bool, // Whether the schema allows revocations explicitly.
schema: ByteArray // Custom specification of the schema
}

impl ArrayFelt252Copy of Copy<Array<felt252>>;

#[starknet::interface]
pub trait ISAS<TContractState> {
fn get_schema_registry(self: @TContractState) -> ContractAddress;
fn attest(ref self: TContractState, request: AttestationRequest) -> u128;
fn timestamp(ref self: TContractState, data: felt252) -> u64;
fn get_attestation(self: @TContractState, uid: u128) -> Attestation;
fn is_attestation_valid(self: @TContractState, uid: u128) -> bool;
fn get_timestamp(self: @TContractState, data: felt252) -> u64;
}

#[starknet::contract]
mod SAS {
use core::array::ArrayTrait;
use core::array::SpanTrait;
use core::box::BoxTrait;
use core::hash::LegacyHash;
use core::option::OptionTrait;
use core::serde::Serde;
use core::traits::TryInto;
use starknet::SyscallResult;
use starknet::storage_access::Store;
use starknet::syscalls::call_contract_syscall;
use starknet::{ContractAddress, get_caller_address, get_block_timestamp};
use super::{Attestation, AttestationRequest};

// Constants
const EMPTY_UID: u128 = 0;
// Errors
#[derive(Drop, starknet::Event)]
enum SASError {
AlreadyRevoked,
AlreadyTimestamped,
AttestationNotFound,
AccessDenied,
DeadlinePassed,
InsufficientValue,
InvalidAttestations,
InvalidExpirationTime,
InvalidOffset,
InvalidRegistry,
InvalidSchema,
InvalidSignature,
InvalidVerifier,
NotPayable,
WrongSchema,
}

// Storage variables
#[storage]
struct Storage {
schema_registry: ContractAddress,
db: LegacyMap::<u128, super::Attestation>,
timestamps: LegacyMap::<felt252, u64>,
schemas: LegacyMap::<felt252, super::SchemaRecord>,
current_uid: u128,
}

// Events
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Attested: Attested,
Revoked: Revoked,
Timestamped: Timestamped,
RevokedOffchain: RevokedOffchain,
}

#[derive(Drop, starknet::Event)]
struct Attested {
#[key]
recipient: ContractAddress,
#[key]
attester: ContractAddress,
uid: u128,
#[key]
schema_uid: u128,
timestamp: u64,
}

#[derive(Drop, starknet::Event)]
struct Revoked {
#[key]
recipient: ContractAddress,
#[key]
attester: ContractAddress,
uid: u128,
#[key]
schema_uid: u128,
}

#[derive(Drop, starknet::Event)]
struct Timestamped {
#[key]
data: felt252,
#[key]
timestamp: u64,
}

#[derive(Drop, starknet::Event)]
struct RevokedOffchain {
#[key]
revoker: ContractAddress,
#[key]
data: felt252,
#[key]
timestamp: u64,
}
// Constructor
#[constructor]
fn constructor(ref self: ContractState, registry: ContractAddress) {
self.schema_registry.write(registry);
}

// External functions
#[abi(embed_v0)]
impl SAS of super::ISAS<ContractState> {
fn get_schema_registry(self: @ContractState) -> ContractAddress {
self.schema_registry.read()
}

fn attest(ref self: ContractState, request: AttestationRequest) -> u128 {

let attestation = Attestation {
uid: EMPTY_UID,
schema_uid: request.schema_uid,
ref_uid: request.refUID,
time: get_block_timestamp(),
expiration_time: request.expirationTime,
recipient: request.recipient,
attester: get_caller_address(),
data: request.data,
};

self.current_uid.write(self.current_uid.read() + 1);
let uid = self.current_uid.read();
let timestamp = get_block_timestamp();

self.db.write(uid, attestation);
self.emit(Event::Attested(Attested {
recipient: request.recipient,
attester: get_caller_address(),
uid,
schema_uid: request.schema_uid,
timestamp
}));

uid
}

fn timestamp(ref self: ContractState, data: felt252) -> u64 {
let time = get_block_timestamp();
self._timestamp(data, time);
time
}

fn get_attestation(self: @ContractState, uid: u128) -> Attestation {
self.db.read(uid)
}

fn is_attestation_valid(self: @ContractState, uid: u128) -> bool {
self.db.read(uid).uid != EMPTY_UID
}

fn get_timestamp(self: @ContractState, data: felt252) -> u64 {
self.timestamps.read(data)
}

}

#[generate_trait]
impl InternalFunctions of InternalFunctionsTrait {

fn _timestamp(ref self: ContractState, data: felt252, time: u64) {
assert(self.timestamps.read(data) == 0, 'Already timestamped');
self.timestamps.write(data, time);
self.emit(Event::Timestamped(Timestamped { data, timestamp: time }));
}
}
}

3 changes: 3 additions & 0 deletions packages/snfoundry/contracts/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod SAS;
mod YourContract;
mod SchemaRegistry;
#[cfg(test)]
mod test {
mod TestContract;
mod Test_sas;
}
140 changes: 140 additions & 0 deletions packages/snfoundry/contracts/src/test/Test_sas.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use array::ArrayTrait;
use option::OptionTrait;
use serde::Serde;
use snforge_std::ContractClassTrait;
use traits::TryInto;

// Helper function to deploy the SAS contract
fn deploy_sas(registry_address: ContractAddress) -> ContractAddress {
let contract = declare("SAS");
let mut calldata = array![];
calldata.append_serde(registry_address);
let (contract_address, _) = contract.deploy(@calldata).unwrap();
contract_address
}

// Helper function to create a mock schema registry
fn deploy_mock_schema_registry() -> ContractAddress {
// Implement mock schema registry deployment
// For now, we'll just return a dummy address
starknet::contract_address_const::<0x1234>()
}

#[test]
fn test_sas_deployment() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

assert_eq!(
dispatcher.get_schema_registry(), registry_address, "Should have correct schema registry"
);
}

#[test]
fn test_attest() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

let schema = 0x1234_felt252;
let recipient = starknet::contract_address_const::<0x5678>();
let attestation_request = AttestationRequest {
schema,
data: AttestationRequestData {
recipient, expirationTime: 0, revocable: true, refUID: 0, data: array![], value: 0,
},
};

let uid = dispatcher.attest(attestation_request);
assert_ne!(uid, 0, "Should return a non-zero UID");

let attestation = dispatcher.get_attestation(uid);
assert_eq!(attestation.schema, schema, "Should have correct schema");
assert_eq!(attestation.recipient, recipient, "Should have correct recipient");
}

#[test]
fn test_revoke() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

// First, create an attestation
let schema = 0x1234_felt252;
let attestation_request = AttestationRequest {
schema,
data: AttestationRequestData {
recipient: starknet::contract_address_const::<0x5678>(),
expirationTime: 0,
revocable: true,
refUID: 0,
data: array![],
value: 0,
},
};
let uid = dispatcher.attest(attestation_request);

// Now revoke it
let revocation_request = RevocationRequest {
schema, data: RevocationRequestData { uid, value: 0 },
};
dispatcher.revoke(revocation_request);

let attestation = dispatcher.get_attestation(uid);
assert_ne!(attestation.revocation_time, 0, "Attestation should be revoked");
}

#[test]
fn test_timestamp() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

let data = 0x1234_felt252;
let timestamp = dispatcher.timestamp(data);
assert_ne!(timestamp, 0, "Should return a non-zero timestamp");

let stored_timestamp = dispatcher.get_timestamp(data);
assert_eq!(stored_timestamp, timestamp, "Stored timestamp should match");
}

#[test]
fn test_revoke_offchain() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

let data = 0x1234_felt252;
let timestamp = dispatcher.revoke_offchain(data);
assert_ne!(timestamp, 0, "Should return a non-zero timestamp");

let stored_timestamp = dispatcher.get_revoke_offchain(starknet::get_caller_address(), data);
assert_eq!(stored_timestamp, timestamp, "Stored revocation timestamp should match");
}

#[test]
fn test_attestation_validity() {
let registry_address = deploy_mock_schema_registry();
let sas_address = deploy_sas(registry_address);
let dispatcher = ISASDispatcher { contract_address: sas_address };

let schema = 0x1234_felt252;
let attestation_request = AttestationRequest {
schema,
data: AttestationRequestData {
recipient: starknet::contract_address_const::<0x5678>(),
expirationTime: 0,
revocable: true,
refUID: 0,
data: array![],
value: 0,
},
};
let uid = dispatcher.attest(attestation_request);

assert!(dispatcher.is_attestation_valid(uid), "Attestation should be valid");
assert!(!dispatcher.is_attestation_valid(0), "Empty UID should be invalid");
}
// Additional tests can be added for multi-attestation, delegation, and other complex scenarios

0 comments on commit 2d18ff6

Please sign in to comment.