Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #284 from Nonnyjoe/main
Browse files Browse the repository at this point in the history
Added piggyBank contract example
  • Loading branch information
gianalarcon authored Jan 4, 2024
2 parents 1ec7626 + 2b8f700 commit b22dae2
Show file tree
Hide file tree
Showing 10 changed files with 857 additions and 0 deletions.
2 changes: 2 additions & 0 deletions examples/piggy_bank/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
.snfoundry_cache/
14 changes: 14 additions & 0 deletions examples/piggy_bank/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "piggy_bank"
version = "0.1.0"
dependencies = [
"snforge_std",
]

[[package]]
name = "snforge_std"
version = "0.1.0"
source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.13.1#e1412ed040d10e66be0fd84115f72a667b57a116"
25 changes: 25 additions & 0 deletions examples/piggy_bank/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "piggy_bank"
version = "0.1.0"
edition = "2023_10"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.13.1" }
starknet = "2.4.1"

[[target.starknet-contract]]
casm = true

[[tool.snforge.fork]]
name = "GoerliFork"
url = "https://starknet-testnet.public.blastapi.io/rpc/v0_6"
block_id.tag = "Latest"

[[tool.snforge.fork]]
name = "SepoliaFork"
url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_6"
block_id.tag = "Latest"

RUST_BACKTRACE=1
3 changes: 3 additions & 0 deletions examples/piggy_bank/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod piggy_bank;
mod ownership_component;
mod piggy_factory;
82 changes: 82 additions & 0 deletions examples/piggy_bank/src/ownership_component.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use starknet::ContractAddress;
#[starknet::interface]
trait IOwnable<TContractState> {
fn owner(self: @TContractState) -> ContractAddress;
fn transfer_ownership(ref self: TContractState, new_owner: ContractAddress);
fn renounce_ownership(ref self: TContractState);
}

#[starknet::component]
mod ownable_component {
use starknet::{get_caller_address, ContractAddress, get_contract_address, Zeroable, get_block_timestamp};
use super::Errors;

#[storage]
struct Storage {
owner: ContractAddress
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
OwnershipTransferred: OwnershipTransferred
}

#[derive(Drop, starknet::Event)]
struct OwnershipTransferred {
previous_owner: ContractAddress,
new_owner: ContractAddress,
}

#[embeddable_as(Ownable)]
impl OwnableImpl<
TContractState, +HasComponent<TContractState>> of super::IOwnable<ComponentState<TContractState>> {
fn owner(self: @ComponentState<TContractState>) -> ContractAddress {
self.owner.read()
}

fn transfer_ownership(
ref self: ComponentState<TContractState>, new_owner: ContractAddress
) {
assert(!new_owner.is_zero(), Errors::ZERO_ADDRESS_OWNER);
self.assert_only_owner();
self._transfer_ownership(new_owner);
}

fn renounce_ownership(ref self: ComponentState<TContractState>) {
self.assert_only_owner();
self._transfer_ownership(Zeroable::zero());
}
}

#[generate_trait]
impl InternalImpl<TContractState, +HasComponent<TContractState>> of InternalTrait<TContractState> {
fn initializer(ref self: ComponentState<TContractState>, owner: ContractAddress) {
self._transfer_ownership(owner);
}

fn assert_only_owner(self: @ComponentState<TContractState>) {
let owner: ContractAddress = self.owner.read();
let caller: ContractAddress = get_caller_address();
assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER);
assert(caller == owner, Errors::NOT_OWNER);
}

fn _transfer_ownership(
ref self: ComponentState<TContractState>, new_owner: ContractAddress
) {
let previous_owner: ContractAddress = self.owner.read();
self.owner.write(new_owner);
self
.emit(
OwnershipTransferred { previous_owner: previous_owner, new_owner: new_owner }
);
}
}
}

mod Errors {
const NOT_OWNER: felt252 = 'Caller is not the owner';
const ZERO_ADDRESS_CALLER: felt252 = 'Caller is the zero address';
const ZERO_ADDRESS_OWNER: felt252 = 'New owner is the zero address';
}
203 changes: 203 additions & 0 deletions examples/piggy_bank/src/piggy_bank.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
use starknet::ContractAddress;

#[derive(Drop, Serde, starknet::Store)]
enum target {
blockTime: u128,
amount: u128,
}

#[starknet::interface]
trait IERC20<TContractState> {
fn name(self: @TContractState) -> felt252;
fn symbol(self: @TContractState) -> felt252;
fn decimals(self: @TContractState) -> u8;
fn total_supply(self: @TContractState) -> u256;
fn balanceOf(self: @TContractState, account: ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transferFrom(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
}

#[starknet::interface]
trait piggyBankTrait<TContractState> {
fn deposit(ref self: TContractState, _amount: u128);
fn withdraw(ref self: TContractState, _amount: u128);
fn get_balance(self: @TContractState) -> u128;
fn get_Target(self: @TContractState) -> (u128 , piggyBank::targetOption) ;
fn get_owner(self: @TContractState) -> ContractAddress;
fn viewTarget(self: @TContractState) -> target;
}

#[starknet::contract]
mod piggyBank {
use core::option::OptionTrait;
use core::traits::TryInto;
use starknet::{get_caller_address, ContractAddress, get_contract_address, Zeroable, get_block_timestamp};
use super::{IERC20Dispatcher, IERC20DispatcherTrait, target};
use core::traits::Into;
use piggy_bank::ownership_component::ownable_component;
component!(path: ownable_component, storage: ownable, event: OwnableEvent);


#[abi(embed_v0)]
impl OwnableImpl = ownable_component::Ownable<ContractState>;
impl OwnableInternalImpl = ownable_component::InternalImpl<ContractState>;

#[storage]
struct Storage {
token: IERC20Dispatcher,
manager: ContractAddress,
balance: u128,
withdrawalCondition: target,
#[substorage(v0)]
ownable: ownable_component::Storage
}

#[derive(Drop, Serde)]
enum targetOption {
targetTime,
targetAmount,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
Deposit: Deposit,
Withdraw: Withdraw,
PaidProcessingFee: PaidProcessingFee,
OwnableEvent: ownable_component::Event
}

#[derive(Drop, starknet::Event)]
struct Deposit {
#[key]
from: ContractAddress,
#[key]
Amount: u128,
}

#[derive(Drop, starknet::Event)]
struct Withdraw {
#[key]
to: ContractAddress,
#[key]
Amount: u128,
#[key]
ActualAmount: u128,
}

#[derive(Drop, starknet::Event)]
struct PaidProcessingFee {
#[key]
from: ContractAddress,
#[key]
Amount: u128,
}

mod Errors {
const Address_Zero_Owner: felt252 = 'Invalid owner';
const Address_Zero_Token: felt252 = 'Invalid Token';
const UnAuthorized_Caller: felt252 = 'UnAuthorized caller';
const Insufficient_Balance: felt252 = 'Insufficient balance';
}

#[constructor]
fn constructor(ref self: ContractState, _owner: ContractAddress, _token: ContractAddress, _manager: ContractAddress, target: targetOption, targetDetails: u128) {
assert(!_owner.is_zero(), Errors::Address_Zero_Owner);
assert(!_token.is_zero(), Errors::Address_Zero_Token);
self.ownable.owner.write(_owner);
self.token.write(super::IERC20Dispatcher{contract_address: _token});
self.manager.write(_manager);
match target {
targetOption::targetTime => self.withdrawalCondition.write(target::blockTime(targetDetails.into())),
targetOption::targetAmount => self.withdrawalCondition.write(target::amount(targetDetails)),
}
}

#[external(v0)]
impl piggyBankImpl of super::piggyBankTrait<ContractState> {
fn deposit(ref self: ContractState, _amount: u128) {
let (caller, this, currentBalance) = self.getImportantAddresses();
self.balance.write(currentBalance + _amount);

self.token.read().transferFrom(caller, this, _amount.into());

self.emit(Deposit { from: caller, Amount: _amount});
}

fn withdraw(ref self: ContractState, _amount: u128) {
self.ownable.assert_only_owner();
let (caller, this, currentBalance) = self.getImportantAddresses();
assert(self.balance.read() >= _amount, Errors::Insufficient_Balance);

let mut new_amount: u128 = 0;
match self.withdrawalCondition.read() {
target::blockTime(x) => new_amount = self.verifyBlockTime(x, _amount),
target::amount(x) => new_amount = self.verifyTargetAmount(x, _amount),
};

self.balance.write(currentBalance - _amount);
self.token.read().transfer(caller, new_amount.into());

self.emit(Withdraw { to: caller, Amount: _amount, ActualAmount: new_amount});
}

fn get_balance(self: @ContractState) -> u128 {
self.balance.read()
}

fn get_Target(self: @ContractState) -> (u128 , targetOption) {
let condition = self.withdrawalCondition.read();
match condition {
target::blockTime(x) => {return (x, targetOption::targetTime);},
target::amount(x) => {return (x, targetOption::targetAmount);},
}
}

fn get_owner(self: @ContractState) -> ContractAddress {
self.ownable.owner()
}

fn viewTarget(self: @ContractState) -> target {
self.withdrawalCondition.read()
}

}

#[generate_trait]
impl Private of PrivateTrait {
fn verifyBlockTime(ref self: ContractState, blockTime: u128, withdrawalAmount: u128) -> u128 {
if (blockTime <= get_block_timestamp().into()) {
return withdrawalAmount;
} else {
return self.processWithdrawalFee(withdrawalAmount);
}
}

fn verifyTargetAmount(ref self: ContractState, targetAmount: u128, withdrawalAmount: u128) -> u128 {
if (self.balance.read() < targetAmount) {
return self.processWithdrawalFee(withdrawalAmount);
} else {
return withdrawalAmount;
}
}

fn processWithdrawalFee(ref self: ContractState, withdrawalAmount: u128) -> u128 {
let withdrawalCharge: u128 = ((withdrawalAmount * 10) / 100);
self.balance.write(self.balance.read() - withdrawalCharge);
self.token.read().transfer(self.manager.read(), withdrawalCharge.into());
self.emit(PaidProcessingFee{from: get_caller_address(), Amount: withdrawalCharge});
return withdrawalAmount - withdrawalCharge;
}

fn getImportantAddresses(self: @ContractState) -> (ContractAddress, ContractAddress, u128) {
let caller: ContractAddress = get_caller_address();
let this: ContractAddress = get_contract_address();
let currentBalance: u128 = self.balance.read();
(caller, this, currentBalance)
}
}
}
Loading

0 comments on commit b22dae2

Please sign in to comment.