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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*
 * SPDX-License-Identifier: Apache-2.0
 */

// Use the Fabric Contract modules
use fabric_contract::contract::*;
use fabric_contract::data::*;
use Expression::Principal;

// Our own asset types
use crate::types::{Asset, AssetPrivate, PriceAgreement, TransferReceipt};

/// Structure for the AssetContract, on which implemenation transaction functions will be added
pub struct AssetTransfer {}

/// Implementation of the contract trait for the AssetContract
/// There are default implementation methods, but can be modified if you wish
///
/// Recommended that the name() function is always modified
impl Contract for AssetTransfer {

    //! Name of the contract
    fn name(&self) -> String {
        format!("AssetTransferPrivate")
    }
}

/// The contract implementation
/// Should be marked with the macro `#[contrant_impl]`
#[Contract_Impl]
impl AssetTransfer {
    pub fn new() -> AssetTransfer {
        AssetTransfer {}
    }

    /// CreateAsset issues a new asset.
    ///
    /// - Public information is put to the world state
    /// - Private data put to the organization's collection
    /// - Endorsement added to public state to permit only owning organization
    ///   to modify
    /// - Must be submitted to the peer of client's organization
    /// Verify the client MSPID and the Peers MSPID are the same
    fn get_verified_client_org(&self) -> Result<String, ContractError> {
        let tx = fabric_contract::blockchain::TransactionContext::current_transaction();

        let peers_msp = tx.get_peer_mspid();
        let client_msp = tx.get_submitting_identity()?.get_mspid();
        if peers_msp != client_msp {
            Err(ContractError::from(
                "Mismatch of Organization names".to_string(),
            ))
        } else {
            Ok(client_msp)
        }
    }

    /// CreateAsset issues a new asset to the world state with given details.

    #[Transaction(submit, transient = "asset_properties")]
    pub fn create_asset(
        &self,
        id: String,
        public_description: String,
        asset_properties: AssetPrivate,
    ) -> Result<(), ContractError> {

        
        let owner_org = self.get_verified_client_org()?;


        // get the collection that is backed by the world state
        let asset_collection = Ledger::access_ledger().get_collection(CollectionName::World);

        match asset_collection.state_exists(id.as_str()) {
            Ok(true) => return Err(ContractError::from("Asset id already present".to_string())),
            Ok(false) => (),
            Err(e) => return Err(ContractError::from(e)),
        }

        let asset = Asset::new(id, owner_org.clone(), public_description);
        let state = asset_collection.create(asset)?;

        // Set the endorsement policy such that an owner org peer is required to endorse future updates
        state.set_endorsment(StateBasedEndorsement::build(Principal(
            owner_org.clone(),
            ROLE::PEER,
        )))?;

        // add to the organizations implicity colletion
        let private_asset_collection =
            Ledger::access_ledger().get_collection(CollectionName::Organization(owner_org.clone()));
        private_asset_collection.create(asset_properties)?;

        Ok(())
    }

    /// Change the public description of an already created asset
    ///
    #[Transaction]
    pub fn change_public_description(
        &self,
        asset_id: String,
        public_desc: String,
    ) -> Result<(), ContractError> {
        self.get_verified_client_org()?;

        let asset_collection = Ledger::access_ledger().get_collection(CollectionName::World);
        let mut asset = asset_collection.retrieve::<Asset>(&asset_id)?;


        asset.set_public_description(public_desc);
        asset_collection.update(asset)?;
      
        Ok(())
    }

    /// Seller submits there agreed selling price
    #[Transaction(transient = "price")]
    pub fn agree_to_sell(&self, asset_id: String, price: u32) -> Result<(), ContractError> {
        let client_msp = self.get_verified_client_org()?;

        let private_collection =
            Ledger::access_ledger().get_collection(CollectionName::Organization(client_msp));
        let asset = private_collection.retrieve::<Asset>(&asset_id)?;

        let client_msp = self.get_verified_client_org()?;
        if asset.get_owner() != client_msp {
            return Err(ContractError::from(
                "Submitting organization does not own asset".to_string(),
            ));
        };

        let sell_price = PriceAgreement::new(asset_id, price);
        let _state = private_collection.create(sell_price);
        Ok(())
    }

    #[Transaction(transient = "price")]
    pub fn agree_to_buy(&self, asset_id: String, price: u32) -> Result<(), ContractError> {
        let client_msp = self.get_verified_client_org()?;
        
        let private_collection =
            Ledger::access_ledger().get_collection(CollectionName::Organization(client_msp));

        let buy_price = PriceAgreement::new(asset_id, price);
        match private_collection.create(buy_price) {
            Ok(_) => Ok(()),
            Err(e) => Err(ContractError::from(e)),
        }
    }

    /// Used by a buyer to vlidate that the asset they are planning on buying 
    /// really is as specified
    #[Transaction(transient = "asset_properties")]
    pub fn verify_asset_properties(
        &self,
        asset_id: String,
        asset_properties: AssetPrivate,
    ) -> Result<(), ContractError> {
        // get the collection that is backed by the world state
        let world = Ledger::access_ledger().get_collection(CollectionName::World);
        let asset = world.retrieve::<Asset>(&asset_id)?;

        let private_collection =
            Ledger::access_ledger().get_collection(CollectionName::Organization(asset.get_owner()));

        let state_hash = private_collection.retrieve_state_hash(&asset_id)?;
        match state_hash.verify_consistent(asset_properties) {
            Ok(true) => Ok(()),
            Ok(false) => Err(ContractError::from("Unable to verify asset properties".to_string())),
            Err(e) => Err(ContractError::from(e)),
        }
    }

    /// Issue the actual transfer asset
    #[Transaction(transient = "asset_properties")]
    pub fn transfer_asset(
        &self,
        asset_id: String,
        buyer_orgid: String,
        asset_properties: AssetPrivate
    ) -> Result<(), ContractError> {
        let tx = fabric_contract::blockchain::TransactionContext::current_transaction();
        let client_msp = tx.get_submitting_identity()?.get_mspid();
        
        let collection_seller = Ledger::access_ledger().get_collection(CollectionName::Organization(client_msp.clone()));
        let collection_buyer = Ledger::access_ledger().get_collection(CollectionName::Organization(buyer_orgid.clone()));
        let mut asset = collection_seller.retrieve::<Asset>(&asset_id)?;

        // Verify Transfer pre-conditions
        // 1) Confirm that only the owner can submit the transfer transaction
        if asset.get_owner() != client_msp {
            return Err(ContractError::from(
                "Submitting organization does not own asset".to_string(),
            ));
        };

        // 2) Confirm that the private details do indeed match what is recorded
        let state = collection_seller.retrieve_state_hash(&asset_id)?;
        match state.verify_consistent(asset_properties) {
            Ok(true) => (),
            Ok(false) => return Err(ContractError::from("Unable to verify asset properties".to_string())),
            Err(e) => return Err(ContractError::from(e)),
        };

        // 3) Confirm that the prices recorded for buyer and seller match
        let s_hash = collection_seller.retrieve_state_hash(&PriceAgreement::form_key(&asset_id))?;
        let b_hash = collection_buyer.retrieve_state_hash(&PriceAgreement::form_key(&asset_id))?;
        match s_hash.verify_consistent(b_hash){ 
            Ok(true) => (), 
            Ok(false) => return Err(ContractError::from("Unable to verify matching price agreements".to_string())),
            Err(e) => return Err(ContractError::from(e)),
        };

        // remove these records no longer required
        collection_seller.delete_state(&PriceAgreement::form_key(&asset_id))?;
        collection_buyer.delete_state(&PriceAgreement::form_key(&asset_id))?;       
 
        // Transfer the Asset State

        // 1) Update the actual owner
        asset.update_owner(buyer_orgid.clone());

        // 2) Set the endorsement policy such that new owner org peer is required to endorse future updates
        let s: State = asset.to_state();
        s.set_endorsment(StateBasedEndorsement::build(Principal(
            buyer_orgid.clone(),
            ROLE::PEER,
        )))?;

        // 3) add receipts to the private collections of buyer and seller
        collection_seller.create(TransferReceipt::new(asset_id.clone()))?;
        collection_buyer.create(TransferReceipt::new(asset_id.clone()))?;

        Ok(())
    }

}