From 3655faa1d3810012a8dc6ade45d3da6f3ed672a2 Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Fri, 24 Jan 2025 08:37:51 -0600 Subject: [PATCH 01/10] place in modules to access the ierc20 interface from escrow contract --- src/interface.cairo | 1 + src/interface/ierc20.cairo | 39 ++++++++++++++++++++------------------ src/lib.cairo | 1 + 3 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 src/interface.cairo diff --git a/src/interface.cairo b/src/interface.cairo new file mode 100644 index 0000000..4e096da --- /dev/null +++ b/src/interface.cairo @@ -0,0 +1 @@ +pub mod ierc20; \ No newline at end of file diff --git a/src/interface/ierc20.cairo b/src/interface/ierc20.cairo index 70a062f..d3784fd 100644 --- a/src/interface/ierc20.cairo +++ b/src/interface/ierc20.cairo @@ -1,19 +1,22 @@ -use starknet::ContractAddress; -#[starknet::interface] -pub trait IERC20 { - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; -} +mod ierc20{ + + use starknet::ContractAddress; + #[starknet::interface] + pub trait IERC20 { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + } -#[starknet::interface] -pub trait IERC20Metadata { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; -} + #[starknet::interface] + pub trait IERC20Metadata { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + } +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index 38f1d7c..e45f8b8 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1 +1,2 @@ pub mod escrow; +pub mod interface; \ No newline at end of file From f41682d90a4617aca8d11d267a752438a82b8aad Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Fri, 24 Jan 2025 17:52:56 -0600 Subject: [PATCH 02/10] distribute earnings logic --- src/escrow/escrow_contract.cairo | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 5fbeb7e..035172e 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -1,11 +1,11 @@ -use starknet::{ContractAddress}; - #[starknet::contract] mod EscrowContract { - use starknet::{ContractAddress, storage::Map}; + use starknet::event::EventEmitter; +use starknet::{ContractAddress, storage::Map}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry,}; use starknet::get_block_timestamp; use core::starknet::{get_caller_address}; + use crate::interface::ierc20:: {IERC20Dispatcher, IERC20DispatcherTrait}; #[storage] @@ -16,7 +16,8 @@ mod EscrowContract { time_frame: u64, worth_of_asset: u256, depositor_approve: Map::, - arbiter_approve: Map:: + arbiter_approve: Map::, + balance: u256 } #[event] @@ -73,4 +74,18 @@ mod EscrowContract { } ); } + + #[external(v0)] + fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ + let despositor_approved = self.depositor_approve.entry(self.depositor.read()).read(); + let arbiter_approved = self.arbiter_approve.entry(self.arbiter.read()).read(); + + if despositor_approved && arbiter_approved{ + let earnings = IERC20Dispatcher::new(caller); + earnings.transfer_from(self.depositor, release_address, self.worth_of_asset); + } + + + + } } From b315237e4e42781a2717f546a3bfbfd2b48cd3eb Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Sat, 25 Jan 2025 10:24:31 -0600 Subject: [PATCH 03/10] error handling and validation for funds --- src/escrow/escrow_contract.cairo | 35 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 035172e..ec9e9f3 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -1,7 +1,8 @@ #[starknet::contract] mod EscrowContract { use starknet::event::EventEmitter; -use starknet::{ContractAddress, storage::Map}; + use core::num::traits::Zero; + use starknet::{ContractAddress, storage::Map}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry,}; use starknet::get_block_timestamp; use core::starknet::{get_caller_address}; @@ -10,14 +11,17 @@ use starknet::{ContractAddress, storage::Map}; #[storage] struct Storage { + escrow_id: u64, depositor: ContractAddress, benefeciary: ContractAddress, arbiter: ContractAddress, + token_address: ContractAddress, time_frame: u64, worth_of_asset: u256, + balance: u256, depositor_approve: Map::, arbiter_approve: Map::, - balance: u256 + escrow_exists: Map:: } #[event] @@ -77,15 +81,30 @@ use starknet::{ContractAddress, storage::Map}; #[external(v0)] fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ - let despositor_approved = self.depositor_approve.entry(self.depositor.read()).read(); + assert(escrow_id == self.escrow_id.read(), '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'); - if despositor_approved && arbiter_approved{ - let earnings = IERC20Dispatcher::new(caller); - earnings.transfer_from(self.depositor, release_address, self.worth_of_asset); - } - + //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); } } From 2d4c05436de489b2599232d43e81637028317ca3 Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Sat, 25 Jan 2025 10:27:19 -0600 Subject: [PATCH 04/10] import ierc20 interface properly --- src/interface/ierc20.cairo | 39 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/interface/ierc20.cairo b/src/interface/ierc20.cairo index d3784fd..70a062f 100644 --- a/src/interface/ierc20.cairo +++ b/src/interface/ierc20.cairo @@ -1,22 +1,19 @@ -mod ierc20{ - - use starknet::ContractAddress; - #[starknet::interface] - pub trait IERC20 { - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256, - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; - } +use starknet::ContractAddress; +#[starknet::interface] +pub trait IERC20 { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} - #[starknet::interface] - pub trait IERC20Metadata { - fn name(self: @TState) -> ByteArray; - fn symbol(self: @TState) -> ByteArray; - fn decimals(self: @TState) -> u8; - } -} \ No newline at end of file +#[starknet::interface] +pub trait IERC20Metadata { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; +} From 7539709d0f0845714aa0198334e432d75797defa Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Sat, 25 Jan 2025 10:40:22 -0600 Subject: [PATCH 05/10] Emit event --- src/escrow/escrow_contract.cairo | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index ec9e9f3..d1abe73 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -28,6 +28,7 @@ mod EscrowContract { #[derive(Drop, starknet::Event)] pub enum Event { ApproveTransaction: ApproveTransaction, + EscrowEarningsDistributed: EscrowEarningsDistributed } #[derive(Drop, starknet::Event)] @@ -37,6 +38,13 @@ mod EscrowContract { time_of_approval: u64, } + #[derive(Drop, starknet::Event)] + struct EscrowEarningsDistributed { + escrow_id: u64, + release_address: ContractAddress, + amount: u256 + } + #[constructor] fn constructor( ref self: ContractState, @@ -106,5 +114,13 @@ mod EscrowContract { // Update balance after successful transfer self.balance.write(0); + + self.emit(Event::EscrowEarningsDistributed( + EscrowEarningsDistributed { + escrow_id: escrow_id, + release_address: release_address, + amount: self.worth_of_asset.read() + } + )); } } From c89a6fbe2c5093f0352b0a07f8da01c80ce7dd22 Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Thu, 13 Feb 2025 12:06:49 -0600 Subject: [PATCH 06/10] single trait for escrow contract definitions --- src/escrow/types.cairo | 12 ------------ src/interface/iescrow.cairo | 3 ++- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/escrow/types.cairo b/src/escrow/types.cairo index 45c7a7e..c7497d2 100644 --- a/src/escrow/types.cairo +++ b/src/escrow/types.cairo @@ -7,15 +7,3 @@ pub struct Escrow { pub amount: u256, pub balance: u256 } - -#[starknet::interface] -pub trait IEscrow { - fn get_escrow(self: @TContractState, escrow_id: u256) -> Escrow; - fn initialize_escrow( - ref self: TContractState, - escrow_id: u64, - beneficiary: ContractAddress, - provider_address: ContractAddress, - amount: u256 - ); -} diff --git a/src/interface/iescrow.cairo b/src/interface/iescrow.cairo index 44092ea..dddd772 100644 --- a/src/interface/iescrow.cairo +++ b/src/interface/iescrow.cairo @@ -11,6 +11,7 @@ pub trait IEscrow { amount: u256 ); fn approve(ref self: TContractState, benefeciary: ContractAddress); - fn get_escrow_details(ref self: TContractState, escrow_id: u256) -> Escrow; + fn get_escrow_details(ref self: TContractState) -> Escrow; fn get_depositor(self: @TContractState) -> ContractAddress; + fn distribute_escrow_earnings(ref self: TContractState, escrow_id: u64, release_address: ContractAddress); } From f1f43a9e08721f163a5eaf63dea3186331aac860 Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Thu, 13 Feb 2025 12:07:25 -0600 Subject: [PATCH 07/10] fix syntax errors --- src/escrow/escrow_contract.cairo | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 4e32849..b17fa0d 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -30,8 +30,8 @@ mod EscrowContract { #[derive(Drop, starknet::Event)] pub enum Event { ApproveTransaction: ApproveTransaction, - EscrowEarningsDistributed: EscrowEarningsDistributed - EscrowInitialized: EscrowInitialized, + EscrowEarningsDistributed: EscrowEarningsDistributed, + EscrowInitialized: EscrowInitialized } #[derive(Drop, starknet::Event)] @@ -73,13 +73,13 @@ mod EscrowContract { #[abi(embed_v0)] impl EscrowImpl of IEscrow { - fn get_escrow_details(ref self: ContractState, escrow_id: u256) -> Escrow { + fn get_escrow_details(ref self: ContractState) -> Escrow { // Validate if the escrow exists let depositor = self.depositor.read(); assert(!depositor.is_zero(), 'Escrow does not exist'); - let client_address = self.client_address.read(); - let provider_address = self.provider_address.read(); + let client_address = self.benefeciary.read(); + let provider_address = self.depositor.read(); let amount = self.worth_of_asset.read(); let balance = self.balance.read(); @@ -177,8 +177,8 @@ mod EscrowContract { fn get_depositor(self: @ContractState) -> ContractAddress { self.depositor.read() } - - fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ + + fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress) -> (){ assert(escrow_id == self.escrow_id.read(), 'Escrow Contract is not valid'); let depositor_approved = self.depositor_approve.entry(self.depositor.read()).read(); @@ -211,6 +211,9 @@ mod EscrowContract { release_address: release_address, amount: self.worth_of_asset.read() } - )); + )); + } + + } } From 0e7776b1d139546edb8b922d386ed2b24436366c Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Tue, 25 Feb 2025 10:42:16 -0600 Subject: [PATCH 08/10] implement escrow distribution on new project structure --- src/escrow/escrow_contract.cairo | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 1e42879..a29ae49 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -188,4 +188,33 @@ mod EscrowContract { } } + + #[external(v0)] + fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ + assert(escrow_id == self.escrow_id.read(), '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); + } } From 051ba6023adc1eeb1015471a48fcd8df952191b8 Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Tue, 25 Feb 2025 11:00:31 -0600 Subject: [PATCH 09/10] Event emmited after successful transaction --- src/escrow/escrow_contract.cairo | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 8184f86..39b974b 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -62,7 +62,7 @@ mod EscrowContract { amount: u256, timestamp: u64, } - + #[derive(Drop, starknet::Event)] pub struct EscrowFunded { @@ -232,7 +232,7 @@ mod EscrowContract { #[external(v0)] fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ - assert(escrow_id == self.escrow_id.read(), 'Escrow Contract is not valid'); + assert(self.escrow_exists.entry(escrow_id).read(), '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(); @@ -257,5 +257,7 @@ mod EscrowContract { // Update balance after successful transfer self.balance.write(0); + + self.emit(EscrowEarningsDistributed {escrow_id, release_address, amount: self.worth_of_asset.read()}); } } From 969dcfd369bf204fcddf32f57ac4135fe5c649df Mon Sep 17 00:00:00 2001 From: JuanPabloRodriguezC Date: Tue, 25 Feb 2025 14:09:52 -0600 Subject: [PATCH 10/10] format source code --- src/escrow/escrow_contract.cairo | 28 +++++++++++++++++----------- src/lib.cairo | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/escrow/escrow_contract.cairo b/src/escrow/escrow_contract.cairo index 39b974b..e10c32e 100644 --- a/src/escrow/escrow_contract.cairo +++ b/src/escrow/escrow_contract.cairo @@ -1,7 +1,7 @@ #[starknet::contract] mod EscrowContract { use starknet::event::EventEmitter; - use crate::interface::ierc20:: {IERC20Dispatcher, IERC20DispatcherTrait}; + 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}; @@ -36,7 +36,6 @@ mod EscrowContract { EscrowEarningsDistributed: EscrowEarningsDistributed, EscrowInitialized: EscrowInitialized, EscrowFunded: EscrowFunded - } #[derive(Drop, starknet::Event)] @@ -205,13 +204,13 @@ mod EscrowContract { assert(depositor == caller_address, 'Only depositor can fund.'); // Check that the correct amount was sended. 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.entry(escrow_id).write(true); - - // Use the OpenZeppelin ERC20 contract to transfer the fund from the caller address to the - // scrow contract. + + // Use the OpenZeppelin ERC20 contract to transfer the fund from the caller address to + // the scrow contract. let token = IERC20Dispatcher { contract_address: token_address }; token.transfer_from(caller_address, contract_address, amount); // Emit Escrow funded Event @@ -227,12 +226,13 @@ mod EscrowContract { fn get_beneficiary(self: @ContractState) -> ContractAddress { self.benefeciary.read() } - } #[external(v0)] - fn distribute_escrow_earnings(ref self: ContractState, escrow_id: u64, release_address: ContractAddress){ - assert(self.escrow_exists.entry(escrow_id).read(), 'Escrow Contract is not valid'); + 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(); @@ -252,12 +252,18 @@ mod EscrowContract { let depositor = self.depositor.read(); // Transfer tokens - let transfer_result = token_contract.transfer_from(depositor, release_address, self.worth_of_asset.read()); + 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()}); + self + .emit( + EscrowEarningsDistributed { + escrow_id, release_address, amount: self.worth_of_asset.read() + } + ); } } diff --git a/src/lib.cairo b/src/lib.cairo index e45f8b8..62a81b1 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,2 +1,2 @@ pub mod escrow; -pub mod interface; \ No newline at end of file +pub mod interface;