near / near-sdk-rs

Rust library for writing NEAR smart contracts
https://near-sdk.io
Apache License 2.0
456 stars 244 forks source link

Enhance #[near_bindgen] to be able to bind to trait implementations - would make composition easy into the contract #303

Open oysterpack opened 3 years ago

oysterpack commented 3 years ago

The goal is to be able to write reusable components that can be used to compose a contract. For example, FT (NEP-141), Account Storage (NEP-145), etc. The rusty way of doing this is to leverage auto-traits, i.e., blanket trait implementations.

    1. Component
      
       trait KeyValueStore { fn get(&self, key: U128) -> Option<U128>; fn set(&mut self, key: U128, value: U128); }

trait HasKeyValueStore

{ fn get_key_value_store(&self) -> &dyn KeyValueStore; fn get_mut_key_value_store(&mut self) -> &mut dyn KeyValueStore; }

impl KeyValueStore for T where T: HasKeyValueStore, { fn get(&self, key: U128) -> Option

{ self.get_key_value_store().get(key) }

fn set(&mut self, key: U128, value: U128)

{ self.get_mut_key_value_store().set(key, value); }

}

type Data = Object<u128, u128>;

[derive(Default)]

struct KeyValueStoreService;

impl KeyValueStore for KeyValueStoreService { fn get(&self, key: U128) -> Option

{ Data::load(&key.0) .unwrap() .map(|object| (*object.value()).into()) }

fn set(&mut self, key: U128, value: U128)

{ Data::new(key.0, value.0).save() }

}


*   the above code provides a `KeyValueStore` implementation for any type thate implements `HasKeyValueStore`
*   `KeyValueStoreService` is the resuable component that implements the `KeyValueStore`

1.  1.  Contract
         ```rust
         #<span class="error">[near_bindgen]</span>
         #<span class="error">[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]</span>
         pub struct Contract { #[borsh_skip] key_value_store: KeyValueStoreService, }

type Data = Object<u128, u128>;

#<span class="error">[near_bindgen]</span>
 impl Contract {
 #<span class="error">[init]</span>
 pub fn init() -> Self {
 assert!(!env::state_exists(), "contract is already initialized");
 Self 

 { key_value_store: KeyValueStoreService, } 

 }
 }

impl HasKeyValueStore for Contract {
 fn get_key_value_store(&self) -> &dyn KeyValueStore 

 { &self.key_value_store } 

 fn get_mut_key_value_store(&mut self) -> &mut dyn KeyValueStore 

 { &mut self.key_value_store } 

}

The work around is to implement the trait directly and delegate manually:

 #<span class="error">[near_bindgen]</span>
 impl KeyValueStore for Contract {
 fn get(&self, key: U128) -> Option<U128> 

 { self.key_value_store.get(key) } 

 fn set(&mut self, key: U128, value: U128) 

 { self.set(key, value) } 

}

Naturally, you don't want to pull in all auto-traits, the trait implementations to pull in should be explicitly defined. I imagine something like this:

 #<span class="error">[near_bindgen(KeyValueStore, FungibleToken)]</span>
 #<span class="error">[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]</span>
 pub struct Contract 

 { #[borsh_skip] key_value_store: KeyValueStoreService, } 
 #<span class="error">[near_bindgen_trait]</span>
 trait KeyValueStore 

 { fn get(&self, key: U128) -> Option<U128>; #[payable] fn set(&mut self, key: U128, value: U128); } 
swfsql commented 3 years ago

I'm currently exploring ways to achieve what's stated in this issue (for now, prototyping in here).