/
registry.cairo
143 lines (128 loc) · 5.75 KB
/
registry.cairo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
////////////////////////////////
// Registry Component
////////////////////////////////
#[starknet::contract]
mod Registry {
use core::result::ResultTrait;
use core::hash::HashStateTrait;
use core::pedersen::PedersenTrait;
use starknet::{
ContractAddress, get_caller_address, syscalls::call_contract_syscall, class_hash::ClassHash,
class_hash::Felt252TryIntoClassHash, syscalls::deploy_syscall, SyscallResultTrait
};
use token_bound_accounts::interfaces::IERC721::{IERC721DispatcherTrait, IERC721Dispatcher};
use token_bound_accounts::interfaces::IRegistry::IRegistry;
#[storage]
struct Storage {
registry_deployed_accounts: LegacyMap<
(ContractAddress, u256), u8
>, // tracks no. of deployed accounts by registry for an NFT
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
AccountCreated: AccountCreated
}
/// @notice Emitted when a new tokenbound account is deployed/created
/// @param account_address the deployed contract address of the tokenbound acccount
/// @param token_contract the contract address of the NFT
/// @param token_id the ID of the NFT
#[derive(Drop, starknet::Event)]
struct AccountCreated {
account_address: ContractAddress,
token_contract: ContractAddress,
token_id: u256,
}
mod Errors {
const CALLER_IS_NOT_OWNER: felt252 = 'Registry: caller is not onwer';
}
#[abi(embed_v0)]
impl IRegistryImpl of IRegistry<ContractState> {
/// @notice deploys a new tokenbound account for an NFT
/// @param implementation_hash the class hash of the reference account
/// @param token_contract the contract address of the NFT
/// @param token_id the ID of the NFT
/// @param salt random salt for deployment
fn create_account(
ref self: ContractState,
implementation_hash: felt252,
token_contract: ContractAddress,
token_id: u256,
salt: felt252
) -> ContractAddress {
let owner = self._get_owner(token_contract, token_id);
assert(owner == get_caller_address(), Errors::CALLER_IS_NOT_OWNER);
let mut constructor_calldata: Array<felt252> = array![
token_contract.into(), token_id.low.into(), token_id.high.into()
];
let class_hash: ClassHash = implementation_hash.try_into().unwrap();
let result = deploy_syscall(class_hash, salt, constructor_calldata.span(), true);
let (account_address, _) = result.unwrap_syscall();
let new_deployment_index: u8 = self
.registry_deployed_accounts
.read((token_contract, token_id))
+ 1_u8;
self.registry_deployed_accounts.write((token_contract, token_id), new_deployment_index);
self.emit(AccountCreated { account_address, token_contract, token_id, });
account_address
}
/// @notice calculates the account address for an existing tokenbound account
/// @param implementation_hash the class hash of the reference account
/// @param token_contract the contract address of the NFT
/// @param token_id the ID of the NFT
/// @param salt random salt for deployment
fn get_account(
self: @ContractState,
implementation_hash: felt252,
token_contract: ContractAddress,
token_id: u256,
salt: felt252
) -> ContractAddress {
let constructor_calldata_hash = PedersenTrait::new(0)
.update(token_contract.into())
.update(token_id.low.into())
.update(token_id.high.into())
.update(3)
.finalize();
let prefix: felt252 = 'STARKNET_CONTRACT_ADDRESS';
let account_address = PedersenTrait::new(0)
.update(prefix)
.update(0)
.update(salt)
.update(implementation_hash)
.update(constructor_calldata_hash)
.update(5)
.finalize();
account_address.try_into().unwrap()
}
/// @notice returns the total no. of deployed tokenbound accounts for an NFT by the registry
/// @param token_contract the contract address of the NFT
/// @param token_id the ID of the NFT
fn total_deployed_accounts(
self: @ContractState, token_contract: ContractAddress, token_id: u256
) -> u8 {
self.registry_deployed_accounts.read((token_contract, token_id))
}
}
#[generate_trait]
impl internalImpl of InternalTrait {
/// @notice internal function for getting NFT owner
/// @param token_contract contract address of NFT
// @param token_id token ID of NFT
// NB: This function aims for compatibility with all contracts (snake or camel case) but do not work as expected on mainnet as low level calls do not return err at the moment. Should work for contracts which implements CamelCase but not snake_case until starknet v0.15.
fn _get_owner(
self: @ContractState, token_contract: ContractAddress, token_id: u256
) -> ContractAddress {
let mut calldata: Array<felt252> = ArrayTrait::new();
Serde::serialize(@token_id, ref calldata);
let mut res = call_contract_syscall(
token_contract, selector!("ownerOf"), calldata.span()
);
if (res.is_err()) {
res = call_contract_syscall(token_contract, selector!("owner_of"), calldata.span());
}
let mut address = res.unwrap();
Serde::<ContractAddress>::deserialize(ref address).unwrap()
}
}
}