diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 0a86256..e1a2e7a 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -1,5 +1,7 @@ #[starknet::contract] mod EscrowContract { + use starknet::event::EventEmitter; + use crate::interface::ierc20::{IERC20Dispatcher, IERC20DispatcherTrait}; use core::num::traits::Zero; use starknet::{ContractAddress, storage::Map, contract_address_const}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry}; @@ -11,19 +13,17 @@ mod EscrowContract { #[storage] struct Storage { + escrow_id: u64, depositor: ContractAddress, beneficiary: ContractAddress, arbiter: ContractAddress, + token_address: ContractAddress, time_frame: u64, worth_of_asset: u256, - client_address: ContractAddress, - provider_address: ContractAddress, balance: u256, depositor_approve: Map::, arbiter_approve: Map::, - // Track whether an escrow ID has been used escrow_exists: Map::, - // Store escrow amounts escrow_amounts: Map::, deposit_time: Map::, // Track the funded escrows. Start as false and is setted to true when successfully funds. @@ -33,8 +33,11 @@ mod EscrowContract { #[event] #[derive(Drop, starknet::Event)] pub enum Event { + ApproveTransaction: ApproveTransaction, + EscrowEarningsDistributed: EscrowEarningsDistributed, DepositorApproved: DepositorApproved, ArbiterApproved: ArbiterApproved, + EscrowInitialized: EscrowInitialized, EscrowFunded: EscrowFunded, EscrowRefunded: EscrowRefunded, @@ -49,6 +52,13 @@ mod EscrowContract { } #[derive(Drop, starknet::Event)] + struct EscrowEarningsDistributed { + escrow_id: u64, + release_address: ContractAddress, + amount: u256 + } + + // New event for escrow initialization pub struct ArbiterApproved { arbiter: ContractAddress, escrow_id: u64, @@ -65,6 +75,7 @@ mod EscrowContract { timestamp: u64, } + #[derive(Drop, starknet::Event)] pub struct EscrowRefunded { escrow_id: u64, @@ -109,6 +120,7 @@ mod EscrowContract { #[abi(embed_v0)] impl EscrowImpl of IEscrow { + fn get_escrow_details(ref self: ContractState, escrow_id: u64) -> Escrow { // Validate if the escrow exists let exists = self.escrow_exists.read(escrow_id); @@ -117,6 +129,7 @@ mod EscrowContract { let client_address = self.client_address.read(); let provider_address = self.provider_address.read(); let amount = self.escrow_amounts.read(escrow_id); + let balance = self.balance.read(); let escrow = Escrow { @@ -300,6 +313,7 @@ mod EscrowContract { // Check that the correct amount was sent. assert(amount >= expected_amount, 'Amount is less than expected'); + // First modify in-contract state to avoid reentrancy attacks // Set escrow to funded self.escrow_funded.write(escrow_id, true); @@ -310,6 +324,7 @@ mod EscrowContract { // Use the OpenZeppelin ERC20 contract to transfer the funds from the caller address to // the escrow contract. + let token = IERC20Dispatcher { contract_address: token_address }; token.transfer_from(caller_address, contract_address, amount); @@ -362,6 +377,7 @@ mod EscrowContract { self.depositor.read() } + fn get_beneficiary(self: @ContractState) -> ContractAddress { self.beneficiary.read() } @@ -382,4 +398,43 @@ mod EscrowContract { (depositor_approved, arbiter_approved) } } + + #[external(v0)] + fn distribute_escrow_earnings( + ref self: ContractState, escrow_id: u64, release_address: ContractAddress + ) { + assert(self.escrow_id.read() == escrow_id, 'Escrow Contract is not valid'); + + let depositor_approved = self.depositor_approve.entry(self.depositor.read()).read(); + let arbiter_approved = self.arbiter_approve.entry(self.arbiter.read()).read(); + // Verify both approvals + assert(depositor_approved && arbiter_approved, 'Escrow not approved'); + + //Verify token validity + let token_address = self.token_address.read(); + assert(!token_address.is_zero(), 'Invalid token address'); + + //Verify if funds were already distributed or there is enough balance + assert(self.balance.read() > 0, 'Funds already distributed'); + assert(self.balance.read() >= self.worth_of_asset.read(), 'Insufficient funds'); + + // Create token dispatcher + let token_contract = IERC20Dispatcher { contract_address: token_address }; + let depositor = self.depositor.read(); + + // Transfer tokens + let transfer_result = token_contract + .transfer_from(depositor, release_address, self.worth_of_asset.read()); + assert(transfer_result, 'Token transfer failed'); + + // Update balance after successful transfer + self.balance.write(0); + + self + .emit( + EscrowEarningsDistributed { + escrow_id, release_address, amount: self.worth_of_asset.read() + } + ); + } } diff --git a/src/interface.cairo b/src/interface.cairo index 1f03bf5..a4b675d 100644 --- a/src/interface.cairo +++ b/src/interface.cairo @@ -1,2 +1,3 @@ +pub mod ierc20; pub mod iescrow; pub mod ierc20; diff --git a/src/interface/iescrow.cairo b/src/interface/iescrow.cairo index c33abe5..32fa951 100644 --- a/src/interface/iescrow.cairo +++ b/src/interface/iescrow.cairo @@ -10,7 +10,10 @@ pub trait IEscrow { provider_address: ContractAddress, amount: u256, ); + + fn approve(ref self: TContractState, benefeciary: ContractAddress); fn get_escrow_details(ref self: TContractState, escrow_id: u64) -> Escrow; + fn get_depositor(self: @TContractState) -> ContractAddress; fn get_beneficiary(self: @TContractState) -> ContractAddress; fn refundTimer(ref self: TContractState, escrow_id: u64, refund_period: u64);