lib.rs 8.86 KB
Newer Older
1
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

19
#[ink::contract]
20
mod dns {
21
    #[cfg(not(feature = "ink-as-dependency"))]
22
    use ink_storage::{
23
        collections::hashmap::Entry,
24
25
26
        collections::HashMap as StorageHashMap,
        lazy::Lazy,
    };
27
28
29

    /// Emitted whenever a new name is being registered.
    #[ink(event)]
30
    pub struct Register {
31
32
33
34
35
36
37
38
        #[ink(topic)]
        name: Hash,
        #[ink(topic)]
        from: AccountId,
    }

    /// Emitted whenever an address changes.
    #[ink(event)]
39
    pub struct SetAddress {
40
41
42
43
44
45
46
47
48
        #[ink(topic)]
        name: Hash,
        from: AccountId,
        #[ink(topic)]
        old_address: Option<AccountId>,
        #[ink(topic)]
        new_address: AccountId,
    }

49
    /// Emitted whenever a name is being transferred.
50
    #[ink(event)]
51
    pub struct Transfer {
52
53
54
55
56
57
58
59
60
        #[ink(topic)]
        name: Hash,
        from: AccountId,
        #[ink(topic)]
        old_owner: Option<AccountId>,
        #[ink(topic)]
        new_owner: AccountId,
    }

61
62
    /// Domain name service contract inspired by
    /// [this blog post](https://medium.com/@chainx_org/secure-and-decentralized-polkadot-domain-name-system-e06c35c2a48d).
63
64
65
66
67
68
69
70
71
72
    ///
    /// # Note
    ///
    /// This is a port from the blog post's ink! 1.0 based version of the contract
    /// to ink! 2.0.
    ///
    /// # Description
    ///
    /// The main function of this contract is domain name resolution which
    /// refers to the retrieval of numeric values corresponding to readable
73
74
    /// and easily memorable names such as "polka.dot" which can be used
    /// to facilitate transfers, voting and DApp-related operations instead
75
76
    /// of resorting to long IP addresses that are hard to remember.
    #[ink(storage)]
77
    #[derive(Default)]
78
    pub struct DomainNameService {
79
        /// A hashmap to store all name to addresses mapping.
80
        name_to_address: StorageHashMap<Hash, AccountId>,
81
        /// A hashmap to store all name to owners mapping.
82
        name_to_owner: StorageHashMap<Hash, AccountId>,
83
        /// The default address.
84
        default_address: Lazy<AccountId>,
85
86
87
88
    }

    /// Errors that can occur upon calling this contract.
    #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
89
    #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
90
91
92
93
94
95
96
97
98
99
100
101
102
    pub enum Error {
        /// Returned if the name already exists upon registration.
        NameAlreadyExists,
        /// Returned if caller is not owner while required to.
        CallerIsNotOwner,
    }

    /// Type alias for the contract's result type.
    pub type Result<T> = core::result::Result<T, Error>;

    impl DomainNameService {
        /// Creates a new domain name service contract.
        #[ink(constructor)]
103
        pub fn new() -> Self {
104
            Default::default()
105
106
107
108
        }

        /// Register specific name with caller as owner.
        #[ink(message)]
109
        pub fn register(&mut self, name: Hash) -> Result<()> {
110
            let caller = self.env().caller();
111
112
113
114
115
116
117
            let entry = self.name_to_owner.entry(name);
            match entry {
                Entry::Occupied(_) => return Err(Error::NameAlreadyExists),
                Entry::Vacant(vacant) => {
                    vacant.insert(caller);
                    self.env().emit_event(Register { name, from: caller });
                }
118
119
120
121
122
123
            }
            Ok(())
        }

        /// Set address for specific name.
        #[ink(message)]
124
        pub fn set_address(&mut self, name: Hash, new_address: AccountId) -> Result<()> {
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
            let caller = self.env().caller();
            let owner = self.get_owner_or_default(name);
            if caller != owner {
                return Err(Error::CallerIsNotOwner)
            }
            let old_address = self.name_to_address.insert(name, new_address);
            self.env().emit_event(SetAddress {
                name,
                from: caller,
                old_address,
                new_address,
            });
            Ok(())
        }

        /// Transfer owner to another address.
        #[ink(message)]
142
        pub fn transfer(&mut self, name: Hash, to: AccountId) -> Result<()> {
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
            let caller = self.env().caller();
            let owner = self.get_owner_or_default(name);
            if caller != owner {
                return Err(Error::CallerIsNotOwner)
            }
            let old_owner = self.name_to_owner.insert(name, to);
            self.env().emit_event(Transfer {
                name,
                from: caller,
                old_owner,
                new_owner: to,
            });
            Ok(())
        }

        /// Get address for specific name.
        #[ink(message)]
160
        pub fn get_address(&self, name: Hash) -> AccountId {
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
            self.get_address_or_default(name)
        }

        /// Returns the owner given the hash or the default address.
        fn get_owner_or_default(&self, name: Hash) -> AccountId {
            *self
                .name_to_owner
                .get(&name)
                .unwrap_or(&*self.default_address)
        }

        /// Returns the address given the hash or the default address.
        fn get_address_or_default(&self, name: Hash) -> AccountId {
            *self
                .name_to_address
                .get(&name)
                .unwrap_or(&*self.default_address)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;
184
        use ink_lang as ink;
185

186
187
188
189
        const DEFAULT_CALLEE_HASH: [u8; 32] = [0x07; 32];
        const DEFAULT_ENDOWMENT: Balance = 1_000_000;
        const DEFAULT_GAS_LIMIT: Balance = 1_000_000;

190
191
192
        fn default_accounts(
        ) -> ink_env::test::DefaultAccounts<ink_env::DefaultEnvironment> {
            ink_env::test::default_accounts::<ink_env::DefaultEnvironment>()
193
194
195
196
                .expect("off-chain environment should have been initialized already")
        }

        fn set_next_caller(caller: AccountId) {
197
            ink_env::test::push_execution_context::<ink_env::DefaultEnvironment>(
198
199
200
                caller,
                AccountId::from(DEFAULT_CALLEE_HASH),
                DEFAULT_GAS_LIMIT,
201
                DEFAULT_ENDOWMENT,
202
                ink_env::test::CallData::new(ink_env::call::Selector::new([0x00; 4])),
203
204
205
            )
        }

206
        #[ink::test]
207
        fn register_works() {
208
209
            let default_accounts = default_accounts();
            let name = Hash::from([0x99; 32]);
210

211
212
            set_next_caller(default_accounts.alice);
            let mut contract = DomainNameService::new();
213

214
215
            assert_eq!(contract.register(name), Ok(()));
            assert_eq!(contract.register(name), Err(Error::NameAlreadyExists));
216
217
        }

218
        #[ink::test]
219
        fn set_address_works() {
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
            let accounts = default_accounts();
            let name = Hash::from([0x99; 32]);

            set_next_caller(accounts.alice);

            let mut contract = DomainNameService::new();
            assert_eq!(contract.register(name), Ok(()));

            // Caller is not owner, `set_address` should fail.
            set_next_caller(accounts.bob);
            assert_eq!(
                contract.set_address(name, accounts.bob),
                Err(Error::CallerIsNotOwner)
            );

235
            // Caller is owner, set_address will be successful
236
237
238
            set_next_caller(accounts.alice);
            assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
            assert_eq!(contract.get_address(name), accounts.bob);
239
240
        }

241
        #[ink::test]
242
        fn transfer_works() {
243
244
            let accounts = default_accounts();
            let name = Hash::from([0x99; 32]);
245

246
            set_next_caller(accounts.alice);
247

248
249
            let mut contract = DomainNameService::new();
            assert_eq!(contract.register(name), Ok(()));
250

251
252
            // Test transfer of owner.
            assert_eq!(contract.transfer(name, accounts.bob), Ok(()));
253

254
255
256
257
258
            // Owner is bob, alice `set_address` should fail.
            assert_eq!(
                contract.set_address(name, accounts.bob),
                Err(Error::CallerIsNotOwner)
            );
259

260
261
262
263
            set_next_caller(accounts.bob);
            // Now owner is bob, `set_address` should be successful.
            assert_eq!(contract.set_address(name, accounts.bob), Ok(()));
            assert_eq!(contract.get_address(name), accounts.bob);
264
265
266
        }
    }
}